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