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