1 /* syslogd.c - a system logging utility.
2  *
3  * Copyright 2013 Madhur Verma <mad.flexi@gmail.com>
4  * Copyright 2013 Kyungwan Han <asura321@gmail.com>
5  *
6  * No Standard
7 
8 USE_SYSLOGD(NEWTOY(syslogd,">0l#<1>8=8R:b#<0>99=1s#<0=200m#<0>71582787=20O:p:f:a:nSKLD", TOYFLAG_SBIN|TOYFLAG_STAYROOT))
9 
10 config SYSLOGD
11   bool "syslogd"
12   default n
13   help
14   usage: syslogd  [-a socket] [-O logfile] [-f config file] [-m interval]
15                   [-p socket] [-s SIZE] [-b N] [-R HOST] [-l N] [-nSLKD]
16 
17   System logging utility
18 
19   -a      Extra unix socket for listen
20   -O FILE Default log file <DEFAULT: /var/log/messages>
21   -f FILE Config file <DEFAULT: /etc/syslog.conf>
22   -p      Alternative unix domain socket <DEFAULT : /dev/log>
23   -n      Avoid auto-backgrounding
24   -S      Smaller output
25   -m MARK interval <DEFAULT: 20 minutes> (RANGE: 0 to 71582787)
26   -R HOST Log to IP or hostname on PORT (default PORT=514/UDP)"
27   -L      Log locally and via network (default is network only if -R)"
28   -s SIZE Max size (KB) before rotation (default:200KB, 0=off)
29   -b N    rotated logs to keep (default:1, max=99, 0=purge)
30   -K      Log to kernel printk buffer (use dmesg to read it)
31   -l N    Log only messages more urgent than prio(default:8 max:8 min:1)
32   -D      Drop duplicates
33 */
34 
35 #define FOR_syslogd
36 #include "toys.h"
37 
38 // UNIX Sockets for listening
39 struct unsocks {
40   struct unsocks *next;
41   char *path;
42   struct sockaddr_un sdu;
43   int sd;
44 };
45 
46 // Log file entry to log into.
47 struct logfile {
48   struct logfile *next;
49   char *filename;
50   uint32_t facility[8];
51   uint8_t level[LOG_NFACILITIES];
52   int logfd;
53   struct sockaddr_in saddr;
54 };
55 
GLOBALS(char * socket;char * config_file;char * unix_socket;char * logfile;long interval;long rot_size;long rot_count;char * remote_log;long log_prio;struct unsocks * lsocks;struct logfile * lfiles;int sigfd[2];)56 GLOBALS(
57   char *socket;
58   char *config_file;
59   char *unix_socket;
60   char *logfile;
61   long interval;
62   long rot_size;
63   long rot_count;
64   char *remote_log;
65   long log_prio;
66 
67   struct unsocks *lsocks;  // list of listen sockets
68   struct logfile *lfiles;  // list of write logfiles
69   int sigfd[2];
70 )
71 
72 // Lookup numerical code from name
73 // Also used in logger
74 int logger_lookup(int where, char *key)
75 {
76   CODE *w = ((CODE *[]){facilitynames, prioritynames})[where];
77 
78   for (; w->c_name; w++)
79     if (!strcasecmp(key, w->c_name)) return w->c_val;
80 
81   return -1;
82 }
83 
84 //search the given name and return its value
dec(int val,CODE * clist,char * buf)85 static char *dec(int val, CODE *clist, char *buf)
86 {
87   for (; clist->c_name; clist++)
88     if (val == clist->c_val) return clist->c_name;
89   sprintf(buf, "%u", val);
90 
91   return buf;
92 }
93 
94 /*
95  * recurses the logfile list and resolves config
96  * for evry file and updates facilty and log level bits.
97  */
resolve_config(struct logfile * file,char * config)98 static int resolve_config(struct logfile *file, char *config)
99 {
100   char *tk;
101 
102   for (tk = strtok(config, "; \0"); tk; tk = strtok(NULL, "; \0")) {
103     char *fac = tk, *lvl;
104     int i = 0;
105     unsigned facval = 0;
106     uint8_t set, levval, bits = 0;
107 
108     tk = strchr(fac, '.');
109     if (!tk) return -1;
110     *tk = '\0';
111     lvl = tk + 1;
112 
113     for (;;) {
114       char *nfac = strchr(fac, ',');
115 
116       if (nfac) *nfac = '\0';
117       if (*fac == '*') {
118         facval = 0xFFFFFFFF;
119         if (fac[1]) return -1;
120       } else {
121         if ((i = logger_lookup(0, fac)) == -1) return -1;
122         facval |= (1 << LOG_FAC(i));
123       }
124       if (nfac) fac = nfac + 1;
125       else break;
126     }
127 
128     levval = 0;
129     for (tk = "!=*"; *tk; tk++, bits <<= 1) {
130       if (*lvl == *tk) {
131         bits++;
132         lvl++;
133       }
134     }
135     if (bits & 2) levval = 0xff;
136     if (*lvl) {
137       if ((i = logger_lookup(1, lvl)) == -1) return -1;
138       levval |= (bits & 4) ? LOG_MASK(i) : LOG_UPTO(i);
139       if (bits & 8) levval = ~levval;
140     }
141 
142     for (i = 0, set = levval; set; set >>= 1, i++)
143       if (set & 0x1) file->facility[i] |= ~facval;
144     for (i = 0; i < LOG_NFACILITIES; facval >>= 1, i++)
145       if (facval & 0x1) file->level[i] |= ~levval;
146   }
147 
148   return 0;
149 }
150 
151 // Parse config file and update the log file list.
parse_config_file(void)152 static int parse_config_file(void)
153 {
154   struct logfile *file;
155   FILE *fp;
156   char *confline, *tk[2];
157   int len, lineno = 0;
158   size_t linelen;
159   /*
160    * if -K then open only /dev/kmsg
161    * all other log files are neglected
162    * thus no need to open config either.
163    */
164   if (toys.optflags & FLAG_K) {
165     file = xzalloc(sizeof(struct logfile));
166     file->filename = xstrdup("/dev/kmsg");
167     TT.lfiles = file;
168     return 0;
169   }
170   /*
171    * if -R then add remote host to log list
172    * if -L is not provided all other log
173    * files are neglected thus no need to
174    * open config either so just return.
175    */
176   if (toys.optflags & FLAG_R) {
177     file = xzalloc(sizeof(struct logfile));
178     file->filename = xmprintf("@%s",TT.remote_log);
179     TT.lfiles = file;
180     if (!(toys.optflags & FLAG_L)) return 0;
181   }
182   /*
183    * Read config file and add logfiles to the list
184    * with their configuration.
185    */
186   if (!(fp = fopen(TT.config_file, "r")) && (toys.optflags & FLAG_f))
187     perror_exit("can't open '%s'", TT.config_file);
188 
189   for (linelen = 0; fp;) {
190     confline = NULL;
191     len = getline(&confline, &linelen, fp);
192     if (len <= 0) break;
193     lineno++;
194     for (; *confline == ' '; confline++, len--) ;
195     if ((confline[0] == '#') || (confline[0] == '\n')) continue;
196     tk[0] = confline;
197     for (; len && !(*tk[0]==' ' || *tk[0]=='\t'); tk[0]++, len--);
198     for (tk[1] = tk[0]; len && (*tk[1]==' ' || *tk[1]=='\t'); tk[1]++, len--);
199     if (!len || (len == 1 && *tk[1] == '\n')) {
200       error_msg("error in '%s' at line %d", TT.config_file, lineno);
201       return -1;
202     }
203     else if (*(tk[1] + len - 1) == '\n') *(tk[1] + len - 1) = '\0';
204     *tk[0] = '\0';
205     if (*tk[1] != '*') {
206       file = TT.lfiles;
207       while (file && strcmp(file->filename, tk[1])) file = file->next;
208       if (!file) {
209         file = xzalloc(sizeof(struct logfile));
210         file->filename = xstrdup(tk[1]);
211         file->next = TT.lfiles;
212         TT.lfiles = file;
213       }
214       if (resolve_config(file, confline) == -1) {
215         error_msg("error in '%s' at line %d", TT.config_file, lineno);
216         return -1;
217       }
218     }
219     free(confline);
220   }
221   /*
222    * Can't open config file or support is not enabled
223    * adding default logfile to the head of list.
224    */
225   if (!fp){
226     file = xzalloc(sizeof(struct logfile));
227     file->filename = xstrdup((toys.optflags & FLAG_O) ?
228                      TT.logfile : "/var/log/messages"); //DEFLOGFILE
229     file->next = TT.lfiles;
230     TT.lfiles = file;
231   } else fclose(fp);
232   return 0;
233 }
234 
235 // open every log file in list.
open_logfiles(void)236 static void open_logfiles(void)
237 {
238   struct logfile *tfd;
239 
240   for (tfd = TT.lfiles; tfd; tfd = tfd->next) {
241     char *p, *tmpfile;
242     long port = 514;
243 
244     if (*tfd->filename == '@') { // network
245       struct addrinfo *info, rp;
246 
247       tmpfile = xstrdup(tfd->filename + 1);
248       if ((p = strchr(tmpfile, ':'))) {
249         char *endptr;
250 
251         *p = '\0';
252         port = strtol(++p, &endptr, 10);
253         if (*endptr || endptr == p || port < 0 || port > 65535)
254           error_exit("bad port in %s", tfd->filename);
255       }
256       memset(&rp, 0, sizeof(rp));
257       rp.ai_family = AF_INET;
258       rp.ai_socktype = SOCK_DGRAM;
259       rp.ai_protocol = IPPROTO_UDP;
260 
261       if (getaddrinfo(tmpfile, NULL, &rp, &info) || !info)
262         perror_exit("BAD ADDRESS: can't find : %s ", tmpfile);
263       ((struct sockaddr_in*)info->ai_addr)->sin_port = htons(port);
264       memcpy(&tfd->saddr, info->ai_addr, info->ai_addrlen);
265       freeaddrinfo(info);
266 
267       tfd->logfd = xsocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
268       free(tmpfile);
269     } else tfd->logfd = open(tfd->filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
270     if (tfd->logfd < 0) {
271       tfd->filename = "/dev/console";
272       tfd->logfd = open(tfd->filename, O_APPEND);
273     }
274   }
275 }
276 
277 //write to file with rotation
write_rotate(struct logfile * tf,int len)278 static int write_rotate(struct logfile *tf, int len)
279 {
280   int size, isreg;
281   struct stat statf;
282   isreg = (!fstat(tf->logfd, &statf) && S_ISREG(statf.st_mode));
283   size = statf.st_size;
284 
285   if ((toys.optflags & FLAG_s) || (toys.optflags & FLAG_b)) {
286     if (TT.rot_size && isreg && (size + len) > (TT.rot_size*1024)) {
287       if (TT.rot_count) { /* always 0..99 */
288         int i = strlen(tf->filename) + 3 + 1;
289         char old_file[i];
290         char new_file[i];
291         i = TT.rot_count - 1;
292         while (1) {
293           sprintf(new_file, "%s.%d", tf->filename, i);
294           if (!i) break;
295           sprintf(old_file, "%s.%d", tf->filename, --i);
296           rename(old_file, new_file);
297         }
298         rename(tf->filename, new_file);
299         unlink(tf->filename);
300         close(tf->logfd);
301         tf->logfd = open(tf->filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
302         if (tf->logfd < 0) {
303           perror_msg("can't open %s", tf->filename);
304           return -1;
305         }
306       }
307       ftruncate(tf->logfd, 0);
308     }
309   }
310   return write(tf->logfd, toybuf, len);
311 }
312 
313 //Parse messege and write to file.
logmsg(char * msg,int len)314 static void logmsg(char *msg, int len)
315 {
316   time_t now;
317   char *p, *ts, *lvlstr, *facstr;
318   struct utsname uts;
319   int pri = 0;
320   struct logfile *tf = TT.lfiles;
321 
322   char *omsg = msg;
323   int olen = len, fac, lvl;
324 
325   if (*msg == '<') { // Extract the priority no.
326     pri = (int) strtoul(msg + 1, &p, 10);
327     if (*p == '>') msg = p + 1;
328   }
329   /* Jan 18 00:11:22 msg...
330    * 01234567890123456
331    */
332   if (len < 16 || msg[3] != ' ' || msg[6] != ' ' || msg[9] != ':'
333       || msg[12] != ':' || msg[15] != ' ') {
334     time(&now);
335     ts = ctime(&now) + 4; /* skip day of week */
336   } else {
337     now = 0;
338     ts = msg;
339     msg += 16;
340   }
341   ts[15] = '\0';
342   fac = LOG_FAC(pri);
343   lvl = LOG_PRI(pri);
344 
345   if (toys.optflags & FLAG_K) len = sprintf(toybuf, "<%d> %s\n", pri, msg);
346   else {
347     char facbuf[12], pribuf[12];
348 
349     facstr = dec(pri & LOG_FACMASK, facilitynames, facbuf);
350     lvlstr = dec(LOG_PRI(pri), prioritynames, pribuf);
351 
352     p = "local";
353     if (!uname(&uts)) p = uts.nodename;
354     if (toys.optflags & FLAG_S) len = sprintf(toybuf, "%s %s\n", ts, msg);
355     else len = sprintf(toybuf, "%s %s %s.%s %s\n", ts, p, facstr, lvlstr, msg);
356   }
357   if (lvl >= TT.log_prio) return;
358 
359   for (; tf; tf = tf->next) {
360     if (tf->logfd > 0) {
361       if (!((tf->facility[lvl] & (1 << fac)) || (tf->level[fac] & (1<<lvl)))) {
362         int wlen, isNetwork = *tf->filename == '@';
363         if (isNetwork)
364           wlen = sendto(tf->logfd, omsg, olen, 0, (struct sockaddr*)&tf->saddr, sizeof(tf->saddr));
365         else wlen = write_rotate(tf, len);
366         if (wlen < 0) perror_msg("write failed file : %s ", tf->filename + isNetwork);
367       }
368     }
369   }
370 }
371 
372 /*
373  * closes all read and write fds
374  * and frees all nodes and lists
375  */
cleanup(void)376 static void cleanup(void)
377 {
378   while (TT.lsocks) {
379     struct unsocks *fnode = TT.lsocks;
380 
381     if (fnode->sd >= 0) {
382       close(fnode->sd);
383       unlink(fnode->path);
384     }
385     TT.lsocks = fnode->next;
386     free(fnode);
387   }
388 
389   while (TT.lfiles) {
390     struct logfile *fnode = TT.lfiles;
391 
392     free(fnode->filename);
393     if (fnode->logfd >= 0) close(fnode->logfd);
394     TT.lfiles = fnode->next;
395     free(fnode);
396   }
397 }
398 
signal_handler(int sig)399 static void signal_handler(int sig)
400 {
401   unsigned char ch = sig;
402   if (write(TT.sigfd[1], &ch, 1) != 1) error_msg("can't send signal");
403 }
404 
syslogd_main(void)405 void syslogd_main(void)
406 {
407   struct unsocks *tsd;
408   int nfds, retval, last_len=0;
409   struct timeval tv;
410   fd_set rfds;        // fds for reading
411   char *temp, *buffer = (toybuf +2048), *last_buf = (toybuf + 3072); //these two buffs are of 1K each
412 
413   if ((toys.optflags & FLAG_p) && (strlen(TT.unix_socket) > 108))
414     error_exit("Socket path should not be more than 108");
415 
416   TT.config_file = (toys.optflags & FLAG_f) ?
417                    TT.config_file : "/etc/syslog.conf"; //DEFCONFFILE
418 init_jumpin:
419   tsd = xzalloc(sizeof(struct unsocks));
420 
421   tsd->path = (toys.optflags & FLAG_p) ? TT.unix_socket : "/dev/log"; // DEFLOGSOCK
422   TT.lsocks = tsd;
423 
424   if (toys.optflags & FLAG_a) {
425     for (temp = strtok(TT.socket, ":"); temp; temp = strtok(NULL, ":")) {
426       if (strlen(temp) > 107) temp[108] = '\0';
427       tsd = xzalloc(sizeof(struct unsocks));
428       tsd->path = temp;
429       tsd->next = TT.lsocks;
430       TT.lsocks = tsd;
431     }
432   }
433   /*
434    * initializes unsock_t structure
435    * and opens socket for reading
436    * and adds to global lsock list.
437   */
438   nfds = 0;
439   for (tsd = TT.lsocks; tsd; tsd = tsd->next) {
440     tsd->sdu.sun_family = AF_UNIX;
441     strcpy(tsd->sdu.sun_path, tsd->path);
442     tsd->sd = socket(AF_UNIX, SOCK_DGRAM, 0);
443     if (tsd->sd < 0) {
444       perror_msg("OPEN SOCKS : failed");
445       continue;
446     }
447     unlink(tsd->sdu.sun_path);
448     if (bind(tsd->sd, (struct sockaddr *) &tsd->sdu, sizeof(tsd->sdu))) {
449       perror_msg("BIND SOCKS : failed sock : %s", tsd->sdu.sun_path);
450       close(tsd->sd);
451       continue;
452     }
453     chmod(tsd->path, 0777);
454     nfds++;
455   }
456   if (!nfds) {
457     error_msg("Can't open single socket for listenning.");
458     goto clean_and_exit;
459   }
460 
461   // Setup signals
462   xpipe(TT.sigfd);
463 
464   fcntl(TT.sigfd[1] , F_SETFD, FD_CLOEXEC);
465   fcntl(TT.sigfd[0] , F_SETFD, FD_CLOEXEC);
466   int flags = fcntl(TT.sigfd[1], F_GETFL);
467   fcntl(TT.sigfd[1], F_SETFL, flags | O_NONBLOCK);
468   signal(SIGHUP, signal_handler);
469   signal(SIGTERM, signal_handler);
470   signal(SIGINT, signal_handler);
471   signal(SIGQUIT, signal_handler);
472 
473   if (parse_config_file() == -1) goto clean_and_exit;
474   open_logfiles();
475   if (!(toys.optflags & FLAG_n)) {
476     daemon(0, 0);
477     //don't daemonize again if SIGHUP received.
478     toys.optflags |= FLAG_n;
479   }
480   xpidfile("syslogd");
481 
482   logmsg("<46>Toybox: syslogd started", 27); //27 : the length of message
483   for (;;) {
484     // Add opened socks to rfds for select()
485     FD_ZERO(&rfds);
486     for (tsd = TT.lsocks; tsd; tsd = tsd->next) FD_SET(tsd->sd, &rfds);
487     FD_SET(TT.sigfd[0], &rfds);
488     tv.tv_usec = 0;
489     tv.tv_sec = TT.interval*60;
490 
491     retval = select(TT.sigfd[0] + 1, &rfds, NULL, NULL, (TT.interval)?&tv:NULL);
492     if (retval < 0) {
493       if (errno != EINTR) perror_msg("Error in select ");
494     }
495     else if (!retval) logmsg("<46>-- MARK --", 14);
496     else if (FD_ISSET(TT.sigfd[0], &rfds)) { /* May be a signal */
497       unsigned char sig;
498 
499       if (read(TT.sigfd[0], &sig, 1) != 1) {
500         error_msg("signal read failed.\n");
501         continue;
502       }
503       switch(sig) {
504         case SIGTERM:    /* FALLTHROUGH */
505         case SIGINT:     /* FALLTHROUGH */
506         case SIGQUIT:
507           logmsg("<46>syslogd exiting", 19);
508           if (CFG_TOYBOX_FREE ) cleanup();
509           signal(sig, SIG_DFL);
510           sigset_t ss;
511           sigemptyset(&ss);
512           sigaddset(&ss, sig);
513           sigprocmask(SIG_UNBLOCK, &ss, NULL);
514           raise(sig);
515           _exit(1);  /* Should not reach it */
516           break;
517         case SIGHUP:
518           logmsg("<46>syslogd exiting", 19);
519           cleanup(); //cleanup is done, as we restart syslog.
520           goto init_jumpin;
521         default: break;
522       }
523     } else { /* Some activity on listen sockets. */
524       for (tsd = TT.lsocks; tsd; tsd = tsd->next) {
525         int sd = tsd->sd;
526         if (FD_ISSET(sd, &rfds)) {
527           int len = read(sd, buffer, 1023); //buffer is of 1K, hence readingonly 1023 bytes, 1 for NUL
528           if (len > 0) {
529             buffer[len] = '\0';
530             if((toys.optflags & FLAG_D) && (len == last_len))
531               if (!memcmp(last_buf, buffer, len)) break;
532 
533             memcpy(last_buf, buffer, len);
534             last_len = len;
535             logmsg(buffer, len);
536           }
537           break;
538         }
539       }
540     }
541   }
542 clean_and_exit:
543   logmsg("<46>syslogd exiting", 19);
544   if (CFG_TOYBOX_FREE ) cleanup();
545 }
546