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