3ba2398828db8eeccc9bf99a952ed56784a065c8
[oweals/busybox.git] / sysklogd / syslogd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini syslogd implementation for busybox
4  *
5  * Copyright (C) 1999-2003 by Erik Andersen <andersen@codepoet.org>
6  *
7  * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org>
8  *
9  * "circular buffer" Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>
10  *
11  * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  * General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26  *
27  */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <fcntl.h>
34 #include <netdb.h>
35 #include <paths.h>
36 #include <signal.h>
37 #include <stdarg.h>
38 #include <time.h>
39 #include <string.h>
40 #include <unistd.h>
41 #include <sys/socket.h>
42 #include <sys/types.h>
43 #include <sys/un.h>
44 #include <sys/param.h>
45
46 #include "busybox.h"
47
48 /* SYSLOG_NAMES defined to pull some extra junk from syslog.h */
49 #define SYSLOG_NAMES
50 #include <sys/syslog.h>
51 #include <sys/uio.h>
52
53 /* Path for the file where all log messages are written */
54 #define __LOG_FILE "/var/log/messages"
55
56 /* Path to the unix socket */
57 static char lfile[MAXPATHLEN];
58
59 static const char *logFilePath = __LOG_FILE;
60
61 /* interval between marks in seconds */
62 static int MarkInterval = 20 * 60;
63
64 /* localhost's name */
65 static char LocalHostName[64];
66
67 #ifdef CONFIG_FEATURE_REMOTE_LOG
68 #include <netinet/in.h>
69 /* udp socket for logging to remote host */
70 static int remotefd = -1;
71
72 /* where do we log? */
73 static char *RemoteHost;
74
75 /* what port to log to? */
76 static int RemotePort = 514;
77
78 /* To remote log or not to remote log, that is the question. */
79 static int doRemoteLog = FALSE;
80 static int local_logging = FALSE;
81 #endif
82
83
84 #define MAXLINE         1024    /* maximum line length */
85
86
87 /* circular buffer variables/structures */
88 #ifdef CONFIG_FEATURE_IPC_SYSLOG
89 #include <sys/ipc.h>
90 #include <sys/sem.h>
91 #include <sys/shm.h>
92
93 /* our shared key */
94 static const long KEY_ID = 0x414e4547;  /*"GENA" */
95
96 // Semaphore operation structures
97 static struct shbuf_ds {
98         int size;                       // size of data written
99         int head;                       // start of message list
100         int tail;                       // end of message list
101         char data[1];           // data/messages
102 } *buf = NULL;                  // shared memory pointer
103
104 static struct sembuf SMwup[1] = { {1, -1, IPC_NOWAIT} };        // set SMwup
105 static struct sembuf SMwdn[3] = { {0, 0}, {1, 0}, {1, +1} };    // set SMwdn
106
107 static int shmid = -1;  // ipc shared memory id
108 static int s_semid = -1;        // ipc semaphore id
109 static int data_size = 16000;   // default data size
110 static int circular_logging = FALSE;
111
112 /*
113  * sem_up - up()'s a semaphore.
114  */
115 static inline void sem_up(int semid)
116 {
117         if (semop(semid, SMwup, 1) == -1) {
118                 bb_perror_msg_and_die("semop[SMwup]");
119         }
120 }
121
122 /*
123  * sem_down - down()'s a semaphore
124  */
125 static inline void sem_down(int semid)
126 {
127         if (semop(semid, SMwdn, 3) == -1) {
128                 bb_perror_msg_and_die("semop[SMwdn]");
129         }
130 }
131
132
133 void ipcsyslog_cleanup(void)
134 {
135         printf("Exiting Syslogd!\n");
136         if (shmid != -1) {
137                 shmdt(buf);
138         }
139
140         if (shmid != -1) {
141                 shmctl(shmid, IPC_RMID, NULL);
142         }
143         if (s_semid != -1) {
144                 semctl(s_semid, 0, IPC_RMID, 0);
145         }
146 }
147
148 void ipcsyslog_init(void)
149 {
150         if (buf == NULL) {
151                 if ((shmid = shmget(KEY_ID, data_size, IPC_CREAT | 1023)) == -1) {
152                         bb_perror_msg_and_die("shmget");
153                 }
154
155                 if ((buf = shmat(shmid, NULL, 0)) == NULL) {
156                         bb_perror_msg_and_die("shmat");
157                 }
158
159                 buf->size = data_size - sizeof(*buf);
160                 buf->head = buf->tail = 0;
161
162                 // we'll trust the OS to set initial semval to 0 (let's hope)
163                 if ((s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023)) == -1) {
164                         if (errno == EEXIST) {
165                                 if ((s_semid = semget(KEY_ID, 2, 0)) == -1) {
166                                         bb_perror_msg_and_die("semget");
167                                 }
168                         } else {
169                                 bb_perror_msg_and_die("semget");
170                         }
171                 }
172         } else {
173                 printf("Buffer already allocated just grab the semaphore?");
174         }
175 }
176
177 /* write message to buffer */
178 void circ_message(const char *msg)
179 {
180         int l = strlen(msg) + 1;        /* count the whole message w/ '\0' included */
181
182         sem_down(s_semid);
183
184         /*
185          * Circular Buffer Algorithm:
186          * --------------------------
187          *
188          * Start-off w/ empty buffer of specific size SHM_SIZ
189          * Start filling it up w/ messages. I use '\0' as separator to break up messages.
190          * This is also very handy since we can do printf on message.
191          *
192          * Once the buffer is full we need to get rid of the first message in buffer and
193          * insert the new message. (Note: if the message being added is >1 message then
194          * we will need to "remove" >1 old message from the buffer). The way this is done
195          * is the following:
196          *      When we reach the end of the buffer we set a mark and start from the beginning.
197          *      Now what about the beginning and end of the buffer? Well we have the "head"
198          *      index/pointer which is the starting point for the messages and we have "tail"
199          *      index/pointer which is the ending point for the messages. When we "display" the
200          *      messages we start from the beginning and continue until we reach "tail". If we
201          *      reach end of buffer, then we just start from the beginning (offset 0). "head" and
202          *      "tail" are actually offsets from the beginning of the buffer.
203          *
204          * Note: This algorithm uses Linux IPC mechanism w/ shared memory and semaphores to provide
205          *       a threasafe way of handling shared memory operations.
206          */
207         if ((buf->tail + l) < buf->size) {
208                 /* before we append the message we need to check the HEAD so that we won't
209                    overwrite any of the message that we still need and adjust HEAD to point
210                    to the next message! */
211                 if (buf->tail < buf->head) {
212                         if ((buf->tail + l) >= buf->head) {
213                                 /* we need to move the HEAD to point to the next message
214                                  * Theoretically we have enough room to add the whole message to the
215                                  * buffer, because of the first outer IF statement, so we don't have
216                                  * to worry about overflows here!
217                                  */
218                                 int k = buf->tail + l - buf->head;      /* we need to know how many bytes
219                                                                                                            we are overwriting to make
220                                                                                                            enough room */
221                                 char *c =
222                                         memchr(buf->data + buf->head + k, '\0',
223                                                    buf->size - (buf->head + k));
224                                 if (c != NULL) {        /* do a sanity check just in case! */
225                                         buf->head = c - buf->data + 1;  /* we need to convert pointer to
226                                                                                                            offset + skip the '\0' since
227                                                                                                            we need to point to the beginning
228                                                                                                            of the next message */
229                                         /* Note: HEAD is only used to "retrieve" messages, it's not used
230                                            when writing messages into our buffer */
231                                 } else {        /* show an error message to know we messed up? */
232                                         printf("Weird! Can't find the terminator token??? \n");
233                                         buf->head = 0;
234                                 }
235                         }
236                 }
237
238                 /* in other cases no overflows have been done yet, so we don't care! */
239                 /* we should be ok to append the message now */
240                 strncpy(buf->data + buf->tail, msg, l); /* append our message */
241                 buf->tail += l; /* count full message w/ '\0' terminating char */
242         } else {
243                 /* we need to break up the message and "circle" it around */
244                 char *c;
245                 int k = buf->tail + l - buf->size;      /* count # of bytes we don't fit */
246
247                 /* We need to move HEAD! This is always the case since we are going
248                  * to "circle" the message.
249                  */
250                 c = memchr(buf->data + k, '\0', buf->size - k);
251
252                 if (c != NULL) {        /* if we don't have '\0'??? weird!!! */
253                         /* move head pointer */
254                         buf->head = c - buf->data + 1;
255
256                         /* now write the first part of the message */
257                         strncpy(buf->data + buf->tail, msg, l - k - 1);
258
259                         /* ALWAYS terminate end of buffer w/ '\0' */
260                         buf->data[buf->size - 1] = '\0';
261
262                         /* now write out the rest of the string to the beginning of the buffer */
263                         strcpy(buf->data, &msg[l - k - 1]);
264
265                         /* we need to place the TAIL at the end of the message */
266                         buf->tail = k + 1;
267                 } else {
268                         printf
269                                 ("Weird! Can't find the terminator token from the beginning??? \n");
270                         buf->head = buf->tail = 0;      /* reset buffer, since it's probably corrupted */
271                 }
272
273         }
274         sem_up(s_semid);
275 }
276 #endif                                                  /* CONFIG_FEATURE_IPC_SYSLOG */
277
278 /* Note: There is also a function called "message()" in init.c */
279 /* Print a message to the log file. */
280 static void message(char *fmt, ...) __attribute__ ((format(printf, 1, 2)));
281 static void message(char *fmt, ...)
282 {
283         int fd;
284         struct flock fl;
285         va_list arguments;
286
287         fl.l_whence = SEEK_SET;
288         fl.l_start = 0;
289         fl.l_len = 1;
290
291 #ifdef CONFIG_FEATURE_IPC_SYSLOG
292         if ((circular_logging == TRUE) && (buf != NULL)) {
293                 char b[1024];
294
295                 va_start(arguments, fmt);
296                 vsnprintf(b, sizeof(b) - 1, fmt, arguments);
297                 va_end(arguments);
298                 circ_message(b);
299
300         } else
301 #endif
302         if ((fd =
303                          device_open(logFilePath,
304                                                          O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND |
305                                                          O_NONBLOCK)) >= 0) {
306                 fl.l_type = F_WRLCK;
307                 fcntl(fd, F_SETLKW, &fl);
308                 va_start(arguments, fmt);
309                 vdprintf(fd, fmt, arguments);
310                 va_end(arguments);
311                 fl.l_type = F_UNLCK;
312                 fcntl(fd, F_SETLKW, &fl);
313                 close(fd);
314         } else {
315                 /* Always send console messages to /dev/console so people will see them. */
316                 if ((fd =
317                          device_open(_PATH_CONSOLE,
318                                                  O_WRONLY | O_NOCTTY | O_NONBLOCK)) >= 0) {
319                         va_start(arguments, fmt);
320                         vdprintf(fd, fmt, arguments);
321                         va_end(arguments);
322                         close(fd);
323                 } else {
324                         fprintf(stderr, "Bummer, can't print: ");
325                         va_start(arguments, fmt);
326                         vfprintf(stderr, fmt, arguments);
327                         fflush(stderr);
328                         va_end(arguments);
329                 }
330         }
331 }
332
333 static void logMessage(int pri, char *msg)
334 {
335         time_t now;
336         char *timestamp;
337         static char res[20] = "";
338         CODE *c_pri, *c_fac;
339
340         if (pri != 0) {
341                 for (c_fac = facilitynames;
342                          c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3); c_fac++);
343                 for (c_pri = prioritynames;
344                          c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++);
345                 if (c_fac->c_name == NULL || c_pri->c_name == NULL) {
346                         snprintf(res, sizeof(res), "<%d>", pri);
347                 } else {
348                         snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name);
349                 }
350         }
351
352         if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' ||
353                 msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') {
354                 time(&now);
355                 timestamp = ctime(&now) + 4;
356                 timestamp[15] = '\0';
357         } else {
358                 timestamp = msg;
359                 timestamp[15] = '\0';
360                 msg += 16;
361         }
362
363         /* todo: supress duplicates */
364
365 #ifdef CONFIG_FEATURE_REMOTE_LOG
366         /* send message to remote logger */
367         if (-1 != remotefd) {
368                 static const int IOV_COUNT = 2;
369                 struct iovec iov[IOV_COUNT];
370                 struct iovec *v = iov;
371
372                 memset(&res, 0, sizeof(res));
373                 snprintf(res, sizeof(res), "<%d>", pri);
374                 v->iov_base = res;
375                 v->iov_len = strlen(res);
376                 v++;
377
378                 v->iov_base = msg;
379                 v->iov_len = strlen(msg);
380           writev_retry:
381                 if ((-1 == writev(remotefd, iov, IOV_COUNT)) && (errno == EINTR)) {
382                         goto writev_retry;
383                 }
384         }
385         if (local_logging == TRUE)
386 #endif
387                 /* now spew out the message to wherever it is supposed to go */
388                 message("%s %s %s %s\n", timestamp, LocalHostName, res, msg);
389 }
390
391 static void quit_signal(int sig)
392 {
393         logMessage(LOG_SYSLOG | LOG_INFO, "System log daemon exiting.");
394         unlink(lfile);
395 #ifdef CONFIG_FEATURE_IPC_SYSLOG
396         ipcsyslog_cleanup();
397 #endif
398
399         exit(TRUE);
400 }
401
402 static void domark(int sig)
403 {
404         if (MarkInterval > 0) {
405                 logMessage(LOG_SYSLOG | LOG_INFO, "-- MARK --");
406                 alarm(MarkInterval);
407         }
408 }
409
410 /* This must be a #define, since when CONFIG_DEBUG and BUFFERS_GO_IN_BSS are
411  * enabled, we otherwise get a "storage size isn't constant error. */
412 static int serveConnection(char *tmpbuf, int n_read)
413 {
414         char *p = tmpbuf;
415
416         while (p < tmpbuf + n_read) {
417
418                 int pri = (LOG_USER | LOG_NOTICE);
419                 int num_lt = 0;
420                 char line[MAXLINE + 1];
421                 unsigned char c;
422                 char *q = line;
423
424                 while ((c = *p) && q < &line[sizeof(line) - 1]) {
425                         if (c == '<' && num_lt == 0) {
426                                 /* Parse the magic priority number. */
427                                 num_lt++;
428                                 pri = 0;
429                                 while (isdigit(*(++p))) {
430                                         pri = 10 * pri + (*p - '0');
431                                 }
432                                 if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) {
433                                         pri = (LOG_USER | LOG_NOTICE);
434                                 }
435                         } else if (c == '\n') {
436                                 *q++ = ' ';
437                         } else if (iscntrl(c) && (c < 0177)) {
438                                 *q++ = '^';
439                                 *q++ = c ^ 0100;
440                         } else {
441                                 *q++ = c;
442                         }
443                         p++;
444                 }
445                 *q = '\0';
446                 p++;
447                 /* Now log it */
448                 logMessage(pri, line);
449         }
450         return n_read;
451 }
452
453
454 #ifdef CONFIG_FEATURE_REMOTE_LOG
455 static void init_RemoteLog(void)
456 {
457
458         struct sockaddr_in remoteaddr;
459         struct hostent *hostinfo;
460         int len = sizeof(remoteaddr);
461
462         memset(&remoteaddr, 0, len);
463
464         remotefd = socket(AF_INET, SOCK_DGRAM, 0);
465
466         if (remotefd < 0) {
467                 bb_error_msg_and_die("cannot create socket");
468         }
469
470         hostinfo = xgethostbyname(RemoteHost);
471
472         remoteaddr.sin_family = AF_INET;
473         remoteaddr.sin_addr = *(struct in_addr *) *hostinfo->h_addr_list;
474         remoteaddr.sin_port = htons(RemotePort);
475
476         /* Since we are using UDP sockets, connect just sets the default host and port
477          * for future operations
478          */
479         if (0 != (connect(remotefd, (struct sockaddr *) &remoteaddr, len))) {
480                 bb_error_msg_and_die("cannot connect to remote host %s:%d", RemoteHost,
481                                                   RemotePort);
482         }
483
484 }
485 #endif
486
487 static void doSyslogd(void) __attribute__ ((noreturn));
488 static void doSyslogd(void)
489 {
490         struct sockaddr_un sunx;
491         socklen_t addrLength;
492
493         int sock_fd;
494         fd_set fds;
495
496         /* Set up signal handlers. */
497         signal(SIGINT, quit_signal);
498         signal(SIGTERM, quit_signal);
499         signal(SIGQUIT, quit_signal);
500         signal(SIGHUP, SIG_IGN);
501         signal(SIGCHLD, SIG_IGN);
502 #ifdef SIGCLD
503         signal(SIGCLD, SIG_IGN);
504 #endif
505         signal(SIGALRM, domark);
506         alarm(MarkInterval);
507
508         /* Create the syslog file so realpath() can work. */
509         if (realpath(_PATH_LOG, lfile) != NULL) {
510                 unlink(lfile);
511         }
512
513         memset(&sunx, 0, sizeof(sunx));
514         sunx.sun_family = AF_UNIX;
515         strncpy(sunx.sun_path, lfile, sizeof(sunx.sun_path));
516         if ((sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
517                 bb_perror_msg_and_die("Couldn't get file descriptor for socket "
518                                                    _PATH_LOG);
519         }
520
521         addrLength = sizeof(sunx.sun_family) + strlen(sunx.sun_path);
522         if (bind(sock_fd, (struct sockaddr *) &sunx, addrLength) < 0) {
523                 bb_perror_msg_and_die("Could not connect to socket " _PATH_LOG);
524         }
525
526         if (chmod(lfile, 0666) < 0) {
527                 bb_perror_msg_and_die("Could not set permission on " _PATH_LOG);
528         }
529 #ifdef CONFIG_FEATURE_IPC_SYSLOG
530         if (circular_logging == TRUE) {
531                 ipcsyslog_init();
532         }
533 #endif
534
535 #ifdef CONFIG_FEATURE_REMOTE_LOG
536         if (doRemoteLog == TRUE) {
537                 init_RemoteLog();
538         }
539 #endif
540
541         logMessage(LOG_SYSLOG | LOG_INFO, "syslogd started: " BB_BANNER);
542
543         for (;;) {
544
545                 FD_ZERO(&fds);
546                 FD_SET(sock_fd, &fds);
547
548                 if (select(sock_fd + 1, &fds, NULL, NULL, NULL) < 0) {
549                         if (errno == EINTR) {
550                                 /* alarm may have happened. */
551                                 continue;
552                         }
553                         bb_perror_msg_and_die("select error");
554                 }
555
556                 if (FD_ISSET(sock_fd, &fds)) {
557                         int i;
558
559                         RESERVE_CONFIG_BUFFER(tmpbuf, MAXLINE + 1);
560
561                         memset(tmpbuf, '\0', MAXLINE + 1);
562                         if ((i = recv(sock_fd, tmpbuf, MAXLINE, 0)) > 0) {
563                                 serveConnection(tmpbuf, i);
564                         } else {
565                                 bb_perror_msg_and_die("UNIX socket error");
566                         }
567                         RELEASE_CONFIG_BUFFER(tmpbuf);
568                 }                               /* FD_ISSET() */
569         }                                       /* for main loop */
570 }
571
572 extern int syslogd_main(int argc, char **argv)
573 {
574         int opt;
575
576         int doFork = TRUE;
577
578         char *p;
579
580         /* do normal option parsing */
581         while ((opt = getopt(argc, argv, "m:nO:R:LC::")) > 0) {
582                 switch (opt) {
583                 case 'm':
584                         MarkInterval = atoi(optarg) * 60;
585                         break;
586                 case 'n':
587                         doFork = FALSE;
588                         break;
589                 case 'O':
590                         logFilePath = optarg;
591                         break;
592 #ifdef CONFIG_FEATURE_REMOTE_LOG
593                 case 'R':
594                         RemoteHost = bb_xstrdup(optarg);
595                         if ((p = strchr(RemoteHost, ':'))) {
596                                 RemotePort = atoi(p + 1);
597                                 *p = '\0';
598                         }
599                         doRemoteLog = TRUE;
600                         break;
601                 case 'L':
602                         local_logging = TRUE;
603                         break;
604 #endif
605 #ifdef CONFIG_FEATURE_IPC_SYSLOG
606                 case 'C':
607                         if (optarg) {
608                                 int buf_size = atoi(optarg);
609                                 if (buf_size >= 4) {
610                                         data_size = buf_size;
611                                 }
612                         }
613                         circular_logging = TRUE;
614                         break;
615 #endif
616                 default:
617                         bb_show_usage();
618                 }
619         }
620
621 #ifdef CONFIG_FEATURE_REMOTE_LOG
622         /* If they have not specified remote logging, then log locally */
623         if (doRemoteLog == FALSE)
624                 local_logging = TRUE;
625 #endif
626
627
628         /* Store away localhost's name before the fork */
629         gethostname(LocalHostName, sizeof(LocalHostName));
630         if ((p = strchr(LocalHostName, '.'))) {
631                 *p = '\0';
632         }
633
634         umask(0);
635
636         if (doFork == TRUE) {
637                 if(daemon(0, 1) < 0)
638                 bb_perror_msg_and_die("daemon");
639 #if defined(__uClinux__)
640                 vfork_daemon_rexec(argc, argv, "-n");
641 #endif
642         }
643         doSyslogd();
644
645         return EXIT_SUCCESS;
646 }
647
648 /*
649 Local Variables
650 c-file-style: "linux"
651 c-basic-offset: 4
652 tab-width: 4
653 End:
654 */