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