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