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