Home | History | Annotate | Download | only in pending
      1 /* syslogd.c - a system logging utility.
      2  *
      3  * Copyright 2013 Madhur Verma <mad.flexi (at) gmail.com>
      4  * Copyright 2013 Kyungwan Han <asura321 (at) 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 #define SYSLOG_NAMES
     37 #include "toys.h"
     38 
     39 // UNIX Sockets for listening
     40 struct 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.
     48 struct 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 
     57 GLOBALS(
     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
     75 int 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
     86 static 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  */
     99 static 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.
    153 static 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.
    237 static 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
    279 static 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.
    315 static 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  */
    377 static 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 
    400 static 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 
    406 void 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
    419 init_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   }
    543 clean_and_exit:
    544   logmsg("<46>syslogd exiting", 19);
    545   if (CFG_TOYBOX_FREE ) cleanup();
    546 }
    547