1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
|
/*
* Copyright (C) 1997-2009 Andre Noll <maan@systemlinux.org>
*
* Licensed under the GPL v2. For licencing details see COPYING.
*/
/** \file daemon.c Some helpers for programs that detach from the console. */
#include "para.h"
#include "daemon.h"
#include <pwd.h>
#include <sys/types.h> /* getgrnam() */
#include <grp.h>
#include "string.h"
#include "color.h"
/** The internal state of the daemon. */
struct daemon {
/** See \ref daemon_flags. */
unsigned flags;
/** Set by \ref daemon_set_logfile(). */
char *logfile_name;
/** Current loglevel, see \ref daemon_set_loglevel(). */
int loglevel;
/** Used by \ref server_uptime() and \ref uptime_str(). */
time_t startuptime;
/** The file pointer if the logfile is open. */
FILE *logfile;
/** Used by para_log() if \p DF_LOG_HOSTNAME is set. */
char *hostname;
/** Used for colored log messages. */
char log_colors[NUM_LOGLEVELS][COLOR_MAXLEN];
};
static struct daemon the_daemon, *me = &the_daemon;
/**
* Activate default log colors.
*
* This should be called early if color support is wanted.
*/
void daemon_set_default_log_colors(void)
{
int i;
static const char *default_log_colors[NUM_LOGLEVELS] = {
[LL_DEBUG] = "normal",
[LL_INFO] = "normal",
[LL_NOTICE] = "normal",
[LL_WARNING] = "yellow",
[LL_ERROR] = "red",
[LL_CRIT] = "magenta bold",
[LL_EMERG] = "red bold",
};
for (i = 0; i < NUM_LOGLEVELS; i++) {
int ret = color_parse(default_log_colors[i], me->log_colors[i]);
assert(ret >= 0);
}
}
/**
* Set the color for one loglevel.
*
* \param arg The loglevel/color specifier.
*
* \a arg must be of the form "ll:[fg [bg]] [attr]".
*
* \return 1 On success, -1 on errors.
*/
int daemon_set_log_color(char const *arg)
{
char *p = strchr(arg, ':');
int ret, ll;
if (!p)
goto err;
ret = get_loglevel_by_name(arg);
if (ret < 0)
goto err;
ll = ret;
p++;
ret = color_parse(p, me->log_colors[ll]);
if (ret < 0)
goto err;
return 1;
err:
PARA_ERROR_LOG("%s: color syntax error\n", arg);
return -1;
}
/**
* Init or change the name of the log file.
*
* \param logfile_name The full path of the logfile.
*/
void daemon_set_logfile(char *logfile_name)
{
free(me->logfile_name);
me->logfile_name = NULL;
if (logfile_name)
me->logfile_name = para_strdup(logfile_name);
}
/**
* Suppress log messages with severity lower than the given loglevel.
*
* \param loglevel The smallest level that should be logged.
*/
void daemon_set_loglevel(char *loglevel)
{
int ret = get_loglevel_by_name(loglevel);
assert(ret >= 0);
me->loglevel = ret;
}
/**
* Set one of the daemon config flags.
*
* \param flag The flag to set.
*
* \sa \ref daemon_flags.
*/
void daemon_set_flag(unsigned flag)
{
me->flags |= flag;
}
/**
* Clear one of the daemon config flags.
*
* \param flag The flag to clear.
*
* \sa \ref daemon_flags.
*/
void daemon_clear_flag(unsigned flag)
{
me->flags &= ~flag;
}
static unsigned daemon_test_flag(unsigned flag)
{
return me->flags & flag;
}
/**
* Do the usual stuff to become a daemon.
*
* Fork, become session leader, dup fd 0, 1, 2 to /dev/null.
*
* \sa fork(2), setsid(2), dup(2).
*/
void daemonize(void)
{
pid_t pid;
int null;
PARA_INFO_LOG("daemonizing\n");
pid = fork();
if (pid < 0)
goto err;
if (pid)
exit(EXIT_SUCCESS); /* parent exits */
/* become session leader */
if (setsid() < 0)
goto err;
if (chdir("/") < 0)
goto err;
null = open("/dev/null", O_RDONLY);
if (null < 0)
goto err;
if (dup2(null, STDIN_FILENO) < 0)
goto err;
if (dup2(null, STDOUT_FILENO) < 0)
goto err;
if (dup2(null, STDERR_FILENO) < 0)
goto err;
close(null);
return;
err:
PARA_EMERG_LOG("fatal: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
/**
* Close the log file of the daemon.
*/
void daemon_close_log(void)
{
if (!me->logfile)
return;
PARA_INFO_LOG("closing logfile\n");
fclose(me->logfile);
me->logfile = NULL;
}
/**
* fopen() the logfile in append mode.
*
* \return Either succeeds or exits.
*/
void daemon_open_log_or_die(void)
{
daemon_close_log();
if (!me->logfile_name)
return;
me->logfile = fopen(me->logfile_name, "a");
if (!me->logfile) {
PARA_EMERG_LOG("can not open %s: %s\n", me->logfile_name,
strerror(errno));
exit(EXIT_FAILURE);
}
setlinebuf(me->logfile);
}
/**
* Log the startup message containing the paraslash version.
*/
void log_welcome(const char *whoami)
{
PARA_INFO_LOG("welcome to %s " PACKAGE_VERSION " ("BUILD_DATE")\n",
whoami);
}
/**
* Give up superuser privileges.
*
* \param username The user to switch to.
* \param groupname The group to switch to.
*
* This function returns immediately if not invoked with EUID zero. Otherwise,
* it tries to obtain the GID of \a groupname and the UID of \a username. On
* success, effective and real GID/UID and the saved set-group-ID/set-user-ID
* are all set accordingly. On errors, an appropriate message is logged and
* exit() is called to terminate the process.
*
* \sa getpwnam(3), getuid(2), setuid(2), getgrnam(2), setgid(2)
*/
void drop_privileges_or_die(const char *username, const char *groupname)
{
struct passwd *p;
char *tmp;
if (geteuid())
return;
if (groupname) {
struct group *g = getgrnam(groupname);
if (!g) {
PARA_EMERG_LOG("failed to get group %s: %s\n",
groupname, strerror(errno));
exit(EXIT_FAILURE);
}
if (setgid(g->gr_gid) < 0) {
PARA_EMERG_LOG("failed to set group id %d: %s\n",
(int)g->gr_gid, strerror(errno));
exit(EXIT_FAILURE);
}
}
if (!username) {
PARA_EMERG_LOG("root privileges, but no user option given\n");
exit(EXIT_FAILURE);
}
tmp = para_strdup(username);
p = getpwnam(tmp);
free(tmp);
if (!p) {
PARA_EMERG_LOG("%s: no such user\n", username);
exit(EXIT_FAILURE);
}
PARA_INFO_LOG("dropping root privileges\n");
if (setuid(p->pw_uid) < 0) {
PARA_EMERG_LOG("failed to set effective user ID (%s)",
strerror(errno));
exit(EXIT_FAILURE);
}
PARA_DEBUG_LOG("uid: %d, euid: %d\n", (int)getuid(), (int)geteuid());
}
/**
* Set/get the server uptime.
*
* \param set_or_get Chose one of the two modes.
*
* This should be called at startup time with \a set_or_get equal to \p
* UPTIME_SET which sets the uptime to zero. Subsequent calls with \a
* set_or_get equal to \p UPTIME_GET return the uptime.
* \return Zero if called with \a set_or_get equal to \p UPTIME_SET, the number
* of seconds elapsed since the last reset otherwise.
*
* \sa time(2), difftime(3).
*/
time_t server_uptime(enum uptime set_or_get)
{
time_t now;
double diff;
if (set_or_get == UPTIME_SET) {
time(&me->startuptime);
return 0;
}
time(&now);
diff = difftime(now, me->startuptime);
return (time_t) diff;
}
/**
* Construct string containing uptime.
*
* \return A dynamically allocated string of the form "days:hours:minutes".
*
* \sa server_uptime.
*/
__malloc char *uptime_str(void)
{
long t = server_uptime(UPTIME_GET);
return make_message("%li:%02li:%02li", t / 86400,
(t / 3600) % 24, (t / 60) % 60);
}
/**
* The log function for para_server and para_audiod.
*
* \param ll The log level.
* \param fmt The format string describing the log message.
*/
__printf_2_3 void para_log(int ll, const char* fmt,...)
{
va_list argp;
FILE *fp;
struct tm *tm;
time_t t1;
char *color;
ll = PARA_MIN(ll, NUM_LOGLEVELS - 1);
ll = PARA_MAX(ll, LL_DEBUG);
if (ll < me->loglevel)
return;
fp = me->logfile? me->logfile : stderr;
color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL;
if (color)
fprintf(fp, "%s", color);
if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */
char str[100];
time(&t1);
tm = localtime(&t1);
strftime(str, sizeof(str), "%b %d %H:%M:%S", tm);
fprintf(fp, "%s ", str);
}
if (daemon_test_flag(DF_LOG_HOSTNAME)) {
if (!me->hostname)
me->hostname = para_hostname();
fprintf(fp, "%s ", me->hostname);
}
if (daemon_test_flag(DF_LOG_LL)) /* log loglevel */
fprintf(fp, "(%d) ", ll);
if (daemon_test_flag(DF_LOG_PID)) { /* log pid */
pid_t mypid = getpid();
fprintf(fp, "(%d) ", (int)mypid);
}
va_start(argp, fmt);
vfprintf(fp, fmt, argp);
va_end(argp);
if (color)
fprintf(fp, "%s", COLOR_RESET);
}
|