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