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