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