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