Commit | Line | Data |
---|---|---|
d9898ee8 | 1 | /* |
2 | ** Copyright 1998 - 2006 Double Precision, Inc. | |
3 | ** See COPYING for distribution information. | |
4 | */ | |
5 | ||
6 | #if HAVE_CONFIG_H | |
7 | #include "config.h" | |
8 | #endif | |
9 | #include <string.h> | |
10 | #include <stdio.h> | |
11 | #include <stdlib.h> | |
12 | #include <sys/types.h> | |
13 | #include <sys/wait.h> | |
14 | #include <signal.h> | |
15 | #include <errno.h> | |
16 | #include <fcntl.h> | |
17 | #include <pwd.h> | |
18 | #include <grp.h> | |
19 | #if HAVE_UNISTD_H | |
20 | #include <unistd.h> | |
21 | #endif | |
22 | #if HAVE_SYSLOG_H | |
23 | #include <syslog.h> | |
24 | #else | |
25 | #undef TESTMODE | |
26 | #define TESTMODE 1 | |
27 | #endif | |
b0322a85 CE |
28 | #include "liblock/config.h" |
29 | #include "liblock/liblock.h" | |
30 | #include "numlib/numlib.h" | |
d9898ee8 | 31 | |
d9898ee8 | 32 | |
33 | static int do_logger(const char *name, int facility, FILE *f) | |
34 | { | |
35 | char buf[512]; | |
36 | char *p; | |
37 | int c; | |
38 | ||
39 | #if TESTMODE | |
40 | ||
41 | #else | |
42 | openlog(name, 0 | |
43 | ||
44 | #ifdef LOG_NDELAY | |
45 | | LOG_NDELAY | |
46 | #else | |
47 | | LOG_NOWAIT | |
48 | #endif | |
49 | ||
50 | , facility); | |
51 | #endif | |
52 | if (chdir("/") < 0) | |
53 | { | |
54 | perror("chdir(\"/\")"); | |
55 | exit(1); | |
56 | } | |
57 | ||
58 | while (fgets(buf, sizeof(buf), f)) | |
59 | { | |
60 | if ((p=strchr(buf, '\n')) != 0) *p=0; | |
61 | else | |
62 | while ((c=getchar()) != EOF && c != '\n') | |
63 | ; | |
64 | ||
65 | #if TESTMODE | |
66 | fprintf(stderr, "%s: %s\n", name, buf); | |
67 | #else | |
68 | c=LOG_INFO; | |
69 | if (strncmp(buf, "ERR:", 4) == 0) | |
70 | { | |
71 | c=LOG_ERR; | |
72 | p=buf+4; | |
73 | } | |
74 | else if (strncmp(buf, "WARN:", 5) == 0) | |
75 | { | |
76 | c=LOG_WARNING; | |
77 | p=buf+5; | |
78 | } | |
79 | else if (strncmp(buf, "ALERT:", 6) == 0) | |
80 | { | |
81 | c=LOG_ALERT; | |
82 | p=buf+6; | |
83 | } | |
84 | else if (strncmp(buf, "CRIT:", 5) == 0) | |
85 | { | |
86 | c=LOG_CRIT; | |
87 | p=buf+5; | |
88 | } | |
89 | else if (strncmp(buf, "DEBUG:", 6) == 0) | |
90 | { | |
91 | c=LOG_DEBUG; | |
92 | p=buf+6; | |
93 | } | |
94 | else if (strncmp(buf, "INFO:", 5) == 0) | |
95 | p=buf+5; | |
96 | else p=buf; | |
97 | ||
98 | while (*p == ' ') | |
99 | ++p; | |
100 | ||
101 | syslog(c, "%s", p); | |
102 | #endif | |
103 | } | |
104 | return (0); | |
105 | } | |
106 | ||
107 | static const char *namearg=0; | |
108 | static const char *pidarg=0; | |
109 | static char *lockfilename=0; | |
110 | ||
111 | struct lognames | |
112 | { | |
113 | char *name; | |
114 | int value; | |
115 | }; | |
116 | ||
117 | static struct lognames facilitynames[] = | |
118 | { | |
119 | #ifdef LOG_AUTH | |
120 | { "auth", LOG_AUTH }, | |
121 | #endif | |
122 | #ifdef LOG_AUTHPRIV | |
123 | { "authpriv", LOG_AUTHPRIV }, | |
124 | #endif | |
125 | #ifdef LOG_CONSOLE | |
126 | { "console", LOG_CONSOLE }, | |
127 | #endif | |
128 | #ifdef LOG_CRON | |
129 | { "cron", LOG_CRON }, | |
130 | #endif | |
131 | #ifdef LOG_DAEMON | |
132 | { "daemon", LOG_DAEMON }, | |
133 | #endif | |
134 | #ifdef LOG_FTP | |
135 | { "ftp", LOG_FTP }, | |
136 | #endif | |
137 | #ifdef LOG_KERN | |
138 | { "kern", LOG_KERN }, | |
139 | #endif | |
140 | #ifdef LOG_LPR | |
141 | { "lpr", LOG_LPR }, | |
142 | #endif | |
143 | #ifdef LOG_MAIL | |
144 | { "mail", LOG_MAIL }, | |
145 | #endif | |
146 | #ifdef LOG_AUTHPRIV | |
147 | { "news", LOG_NEWS }, | |
148 | #endif | |
149 | #ifdef LOG_SECURITY | |
150 | { "security", LOG_SECURITY }, | |
151 | #endif | |
152 | #ifdef LOG_USER | |
153 | { "user", LOG_USER }, | |
154 | #endif | |
155 | #ifdef LOG_UUCP | |
156 | { "uucp", LOG_UUCP }, | |
157 | #endif | |
158 | #ifdef LOG_LOCAL0 | |
159 | { "local0", LOG_LOCAL0 }, | |
160 | #endif | |
161 | #ifdef LOG_LOCAL1 | |
162 | { "local1", LOG_LOCAL1 }, | |
163 | #endif | |
164 | #ifdef LOG_LOCAL2 | |
165 | { "local2", LOG_LOCAL2 }, | |
166 | #endif | |
167 | #ifdef LOG_LOCAL3 | |
168 | { "local3", LOG_LOCAL3 }, | |
169 | #endif | |
170 | #ifdef LOG_LOCAL4 | |
171 | { "local4", LOG_LOCAL4 }, | |
172 | #endif | |
173 | #ifdef LOG_LOCAL5 | |
174 | { "local5", LOG_LOCAL5 }, | |
175 | #endif | |
176 | #ifdef LOG_LOCAL6 | |
177 | { "local6", LOG_LOCAL6 }, | |
178 | #endif | |
179 | #ifdef LOG_LOCAL7 | |
180 | { "local7", LOG_LOCAL7 }, | |
181 | #endif | |
182 | { 0, 0 } | |
183 | }; | |
184 | ||
185 | static int hup_restart = 0; | |
186 | static int respawn = 0; | |
187 | static int child_pid = -1; | |
188 | ||
189 | static RETSIGTYPE sighup(int n) | |
190 | { | |
191 | if (child_pid > 0) | |
192 | { | |
193 | /* The child may respond to HUP by dying. If so it will | |
194 | close its stderr and do_logger will terminate; at that | |
195 | point we need to restart it */ | |
196 | hup_restart = 1; | |
197 | kill (child_pid, SIGHUP); | |
198 | } | |
199 | signal(SIGHUP, sighup); | |
200 | #if RETSIGTYPE != void | |
201 | return (1); | |
202 | #endif | |
203 | } | |
204 | ||
205 | static RETSIGTYPE sigalrm(int n) | |
206 | { | |
207 | if (child_pid > 0) kill(child_pid, SIGKILL); | |
208 | #if RETSIGTYPE != void | |
209 | return (1); | |
210 | #endif | |
211 | } | |
212 | ||
213 | static RETSIGTYPE sigterm(int n) | |
214 | { | |
215 | if (child_pid > 0) | |
216 | { | |
217 | hup_restart = 0; | |
218 | respawn = 0; | |
219 | kill(child_pid, SIGTERM); | |
220 | signal(SIGALRM, sigalrm); /* kill after 8 secs */ | |
221 | alarm(8); | |
222 | } | |
223 | else exit(0); | |
224 | #if RETSIGTYPE != void | |
225 | return (1); | |
226 | #endif | |
227 | } | |
228 | ||
229 | static void checkfd(int actual, int exp) | |
230 | { | |
231 | if (actual == exp) return; | |
232 | fprintf(stderr, "Bad fd, got %d, expected %d\n", actual, exp); | |
233 | if (actual < 0) perror("error"); | |
234 | exit(1); | |
235 | } | |
236 | ||
237 | static int isid(const char *p) | |
238 | { | |
239 | while (*p) | |
240 | { | |
241 | if (*p < '0' || *p > '9') return (0); | |
242 | ++p; | |
243 | } | |
244 | return (1); | |
245 | } | |
246 | ||
247 | static void setuidgid(const char *userarg, | |
248 | const char *grouparg) | |
249 | { | |
250 | if (grouparg) | |
251 | { | |
252 | gid_t gid = 0; | |
253 | struct group *gr; | |
254 | ||
255 | if (isid(grouparg)) | |
256 | gid=atoi(grouparg); | |
257 | else if ((gr=getgrnam(grouparg)) == 0) | |
258 | { | |
259 | fprintf(stderr, "Group not found: %s\n", grouparg); | |
260 | exit(1); | |
261 | } | |
262 | else gid=gr->gr_gid; | |
263 | ||
264 | libmail_changegroup(gid); | |
265 | } | |
266 | ||
267 | if (userarg) | |
268 | { | |
269 | uid_t uid; | |
270 | ||
271 | if (isid(userarg)) | |
272 | { | |
273 | uid=atoi(userarg); | |
274 | libmail_changeuidgid(uid, getgid()); | |
275 | } | |
276 | else | |
277 | { | |
278 | gid_t g=getgid(), *gp=0; | |
279 | ||
280 | if (grouparg) gp= &g; | |
281 | libmail_changeusername(userarg, gp); | |
282 | } | |
283 | } | |
284 | } | |
285 | ||
286 | ||
287 | static void startchild(char **argv, const char *userarg, const char *grouparg) | |
288 | { | |
289 | pid_t p; | |
290 | int pipefd[2]; | |
291 | int tmp; | |
292 | ||
293 | signal(SIGTERM, sigterm); | |
294 | signal(SIGHUP, sighup); | |
295 | ||
296 | /* Make sure the pipefds are at least 3 and 4. If we have an open | |
297 | stderr then keep it around for debugging purposes. */ | |
298 | close(0); | |
299 | checkfd(open("/dev/null", O_RDWR), 0); | |
300 | close(1); | |
301 | checkfd(dup(0), 1); | |
302 | if ((tmp = dup(0)) > 2) close(tmp); | |
303 | ||
304 | if (pipe(pipefd) < 0) | |
305 | { | |
306 | perror("pipe"); | |
307 | exit(1); | |
308 | } | |
309 | p = fork(); | |
310 | if (p < 0) | |
311 | { | |
312 | perror("fork"); | |
313 | exit(1); | |
314 | } | |
315 | if (p == 0) | |
316 | { | |
317 | ||
318 | close(pipefd[0]); | |
319 | close(2); | |
320 | checkfd(dup(pipefd[1]), 2); | |
321 | close(pipefd[1]); | |
322 | setuidgid(userarg, grouparg); | |
323 | execvp(argv[0], argv); | |
324 | perror("exec"); | |
325 | exit(1); | |
326 | } | |
327 | ||
328 | // We can close stderr now | |
329 | ||
330 | close(2); | |
331 | checkfd(dup(0), 2); | |
332 | ||
333 | ||
334 | close(pipefd[1]); | |
335 | close(0); | |
336 | checkfd(dup(pipefd[0]), 0); | |
337 | close(pipefd[0]); | |
338 | child_pid = p; | |
339 | } | |
340 | /* | |
341 | * Note that we now support several modes of operation: | |
342 | * | |
343 | * (1) standalone logger, just collect messages on stdin and feed to syslog | |
344 | * courierlogger foo | |
345 | * courierlogger -name=foo | |
346 | * | |
347 | * (2) run a child process, collect its stderr messages and feed to syslog | |
348 | * courierlogger [-name=foo] foo arg1 arg2 | |
349 | * | |
350 | * (3) start a detached daemon with a child process | |
351 | * courierlogger [-name=foo] -pid=/var/run/foo.pid -start foo arg1 arg2 | |
352 | * | |
353 | * (4) stop or restart a detached daemon | |
354 | * courierlogger -pid=/var/run/foo.pid -stop (or receive a SIGTERM) | |
355 | * courierlogger -pid=/var/run/foo.pid -restart (or receive a SIGHUP) | |
356 | */ | |
357 | ||
358 | int main(int argc, char **argv) | |
359 | { | |
360 | int facility = LOG_DEST; | |
361 | int daemon = 0; | |
362 | int lockfd = -1; | |
363 | const char *userarg = 0; | |
364 | const char *grouparg = 0; | |
365 | int droproot=0; | |
366 | ||
367 | if (argc == 2 && argv[1][0] != '-') | |
368 | { | |
369 | /* backwards-compatibility mode */ | |
370 | close(1); | |
371 | close(2); | |
372 | checkfd(open("/dev/null", O_WRONLY), 1); | |
373 | checkfd(open("/dev/null", O_WRONLY), 2); | |
374 | do_logger(argv[1], facility, stdin); | |
375 | exit(0); | |
376 | } | |
377 | ||
378 | if (argc <= 1) | |
379 | { | |
380 | fprintf(stderr, | |
381 | ||
382 | "Usage: courierlogger [-name=name] [-pid=pidfile] [-facility=type]\n" | |
383 | " [-start|-stop|-restart] [cmd [args...]]\n" | |
384 | ); | |
385 | exit(1); | |
386 | } | |
387 | ||
388 | argv++, argc--; | |
389 | while (argc > 0 && argv[0][0]=='-') | |
390 | { | |
391 | if (strncmp(argv[0],"-pid=",5) == 0 && argv[0][5]) | |
392 | { | |
393 | pidarg=&argv[0][5]; | |
394 | lockfilename=malloc(strlen(pidarg)+sizeof(".lock")); | |
395 | if (!lockfilename) | |
396 | { | |
397 | perror("malloc"); | |
398 | exit(1); | |
399 | } | |
400 | strcat(strcpy(lockfilename, pidarg), ".lock"); | |
401 | } | |
402 | else | |
403 | if (strncmp(argv[0],"-name=",6) == 0 && argv[0][6]) | |
404 | namearg=&argv[0][6]; | |
405 | else if (strncmp(argv[0],"-user=",6) == 0) | |
406 | userarg=argv[0]+6; | |
407 | else if (strncmp(argv[0],"-group=",7) == 0) | |
408 | grouparg=argv[0]+7; | |
409 | else if (strcmp(argv[0], "-droproot") == 0) | |
410 | droproot=1; | |
411 | else | |
412 | if (strncmp(argv[0],"-facility=",10) == 0) | |
413 | { | |
414 | struct lognames *p = facilitynames; | |
415 | ||
416 | while (p->name && strcmp(p->name, &argv[0][10])) | |
417 | p++; | |
418 | if (p->name == 0) | |
419 | { | |
420 | fprintf(stderr, "Unknown facility name '%s'\n", | |
421 | &argv[0][10]); | |
422 | exit(1); | |
423 | } | |
424 | facility = p->value; | |
425 | } | |
426 | else | |
427 | if (strcmp(argv[0],"-start") == 0) | |
428 | daemon = 1; | |
429 | else | |
430 | if (strcmp(argv[0],"-stop") == 0) | |
431 | daemon = 2; | |
432 | else | |
433 | if (strcmp(argv[0],"-restart") == 0) | |
434 | daemon = 3; | |
435 | else | |
436 | if (strcmp(argv[0],"-respawn") == 0) | |
437 | respawn = 1; | |
438 | else | |
439 | { | |
440 | fprintf(stderr, "Unknown option '%s'\n", argv[0]); | |
441 | exit(1); | |
442 | } | |
443 | argv++, argc--; | |
444 | } | |
445 | ||
446 | if (daemon && !pidarg) | |
447 | { | |
448 | fprintf(stderr, "-pid argument required\n"); | |
449 | exit(1); | |
450 | } | |
451 | ||
452 | if (!daemon && pidarg) | |
453 | daemon = 1; /* -start implied */ | |
454 | ||
455 | if (!namearg && daemon != 2 && daemon != 3) | |
456 | { | |
457 | /* choose a default name based on the program we're running */ | |
458 | if (argc <= 0 || !argv[0] || !argv[0][0]) | |
459 | { | |
460 | fprintf(stderr, "-name option required for standalone logger\n"); | |
461 | exit(1); | |
462 | } | |
463 | namearg = strrchr(argv[0],'/'); | |
464 | namearg = namearg ? namearg+1 : argv[0]; | |
465 | } | |
466 | ||
467 | switch (daemon) | |
468 | { | |
469 | case 1: /* start */ | |
470 | if (argc <= 0 || !argv[0] || !argv[0][0]) | |
471 | { | |
472 | fprintf(stderr, "-start must be followed by a command to execute\n"); | |
473 | exit(1); | |
474 | } | |
475 | lockfd=ll_daemon_start(lockfilename); | |
476 | if (lockfd < 0) | |
477 | { | |
478 | perror("ll_daemon_start"); | |
479 | exit(1); | |
480 | } | |
481 | startchild(argv, droproot ? userarg:NULL, | |
482 | droproot ? grouparg:NULL); | |
483 | ll_daemon_started(pidarg, lockfd); | |
484 | break; | |
485 | case 2: /* stop */ | |
486 | exit(ll_daemon_stop(lockfilename, pidarg)); | |
487 | case 3: /* restart */ | |
488 | exit(ll_daemon_restart(lockfilename, pidarg)); | |
489 | default: /* run in foreground, with or without a child process */ | |
490 | ||
491 | if (argc > 0) | |
492 | startchild(argv, droproot ? userarg:NULL, | |
493 | droproot ? grouparg:NULL); | |
494 | } | |
495 | ||
496 | setuidgid(userarg, grouparg); | |
497 | ||
498 | while (1) | |
499 | { | |
500 | int waitstat; | |
501 | pid_t p2; | |
502 | FILE *f = fdopen(0, "r"); | |
503 | ||
504 | do_logger(namearg, facility, f); | |
505 | fclose(f); | |
506 | if (child_pid < 0) break; | |
507 | while ((p2=wait(&waitstat)) != child_pid && | |
508 | (p2 != -1 || errno != ECHILD)) | |
509 | ; | |
510 | if (hup_restart) | |
511 | hup_restart = 0; | |
512 | else if (respawn) | |
513 | sleep (5); | |
514 | else | |
515 | break; | |
516 | startchild(argv, NULL, NULL); | |
517 | } | |
518 | exit(0); | |
519 | } |