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