view toys/pending/syslogd.c @ 1024:63b8e54d2c6f

syslogd: cleanup - remove flag macros - remove some unecessary gotos - inline open_udp_socks() and getport() - simplify resulting open_logfiles() Now in the syslog.conf the port numbers for remote hosts are no longer allowed to be hexadecimal. If there is need for hexadecimal port numbers, one can as well accept octal ones and use base 0 in strtoul.
author Felix Janda <felix.janda@posteo.de>
date Wed, 21 Aug 2013 21:24:45 +0200
parents 773e4862e790
children f19286ac3e7f
line wrap: on
line source

/* syslogd.c - a system logging utility.
 *
 * Copyright 2013 Madhur Verma <mad.flexi@gmail.com>
 * Copyright 2013 Kyungwan Han <asura321@gmail.com>
 *
 * No Standard

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))

config SYSLOGD
  bool "syslogd"
  default n
  help
  Usage: syslogd  [-a socket] [-O logfile] [-f config file] [-m interval]
                  [-p socket] [-s SIZE] [-b N] [-R HOST] [-l N] [-nSLKD]

  System logging utility

  -a      Extra unix socket for listen
  -O FILE Default log file <DEFAULT: /var/log/messages>
  -f FILE Config file <DEFAULT: /etc/syslog.conf>
  -p      Alternative unix domain socket <DEFAULT : /dev/log>
  -n      Avoid auto-backgrounding.
  -S      Smaller output
  -m MARK interval <DEFAULT: 20 minutes> (RANGE: 0 to 71582787)
  -R HOST Log to IP or hostname on PORT (default PORT=514/UDP)"
  -L      Log locally and via network (default is network only if -R)"
  -s SIZE Max size (KB) before rotation (default:200KB, 0=off)
  -b N    rotated logs to keep (default:1, max=99, 0=purge)
  -K      Log to kernel printk buffer (use dmesg to read it)
  -l N    Log only messages more urgent than prio(default:8 max:8 min:1)
  -D      Drop duplicates
*/

#define FOR_syslogd
#define SYSLOG_NAMES
#include "toys.h"
#include "toynet.h"

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 arg_list *lsocks;  // list of listen sockets
  struct arg_list *lfiles;  // list of write logfiles
  fd_set rfds;        // fds for reading
  int sd;            // socket for logging remote messeges.
  int sigfd[2];
)


// UNIX Sockets for listening
struct unsocks {
  char *path;
  struct sockaddr_un sdu;
  int sd;
};

// Log file entry to log into.
struct logfile {
  char *filename;
  char *config;
  int isNetwork;
  uint32_t facility[8];
  uint8_t level[LOG_NFACILITIES];
  int logfd;
  struct sockaddr_in saddr;
};

// Adds opened socks to rfds for select()
static int addrfds(void)
{
  struct unsocks *sock;
  int ret = 0;
  struct arg_list *node = TT.lsocks;
  FD_ZERO(&TT.rfds);

  while (node) {
    sock = (struct unsocks*) node->arg;
    if (sock->sd > 2) {
      FD_SET(sock->sd, &TT.rfds);
      ret = sock->sd;
    }
    node = node->next;
  }
  FD_SET(TT.sigfd[0], &TT.rfds);
  return (TT.sigfd[0] > ret) ? TT.sigfd[0] : ret;
}

/*
 * initializes unsock_t structure
 * and opens socket for reading
 * and adds to global lsock list.
 */
static int open_unix_socks(void)
{
  struct arg_list *node;
  struct unsocks *sock;
  int ret = 0;

  for(node = TT.lsocks; node; node = node->next) {
    sock = (struct unsocks*) node->arg;
    sock->sdu.sun_family = AF_UNIX;
    strcpy(sock->sdu.sun_path, sock->path);
    sock->sd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sock->sd < 0) {
      perror_msg("OPEN SOCKS : failed");
      continue;
    }
    unlink(sock->sdu.sun_path);
    if (bind(sock->sd, (struct sockaddr *) &sock->sdu, sizeof(sock->sdu))) {
      perror_msg("BIND SOCKS : failed sock : %s", sock->sdu.sun_path);
      close(sock->sd);
      continue;
    }
    chmod(sock->path, 0777);
    ret++;
  }
  return ret;
}

// Returns node having filename
static struct arg_list *get_file_node(char *filename, struct arg_list *list)
{
  while (list) {
    if (!strcmp(((struct logfile*) list->arg)->filename, filename)) return list;
    list = list->next;
  }
  return list;
}

/*
 * recurses the logfile list and resolves config
 * for evry file and updates facilty and log level bits.
 */
static int resolve_config(struct logfile *file)
{
  char *tk, *fac, *lvl, *tmp, *nfac;
  int count = 0;
  unsigned facval = 0;
  uint8_t set, levval, neg;
  CODE *val = NULL;

  tmp = xstrdup(file->config);
  for (tk = strtok(tmp, "; \0"); tk; tk = strtok(NULL, "; \0")) {
    fac = tk;
    tk = strchr(fac, '.');
    if (!tk) return -1;
    *tk = '\0';
    lvl = tk + 1;

    while(1) {
      count = 0;
      if (*fac == '*') {
        facval = 0xFFFFFFFF;
        fac++;
      }
      nfac = strchr(fac, ',');
      if (nfac) *nfac = '\0';
      while (*fac && ((CODE*) &facilitynames[count])->c_name) {
        val = (CODE*) &facilitynames[count];
        if (!strcmp(fac, val->c_name)) {
          facval |= (1<<LOG_FAC(val->c_val));
          break;
        }
        count++;
      }
      if (((CODE*) &facilitynames[count])->c_val == -1)
        return -1;

      if (nfac) fac = nfac+1;
      else break;
    }

    count = 0;
    set = 0;
    levval = 0;
    neg = 0;
    if (*lvl == '!') {
      neg = 1;
      lvl++;
    }
    if (*lvl == '=') {
      set = 1;
      lvl++;
    }
    if (*lvl == '*') {
      levval = 0xFF;
      lvl++;
    }
    while (*lvl && ((CODE*) &prioritynames[count])->c_name) {
      val = (CODE*) &prioritynames[count];
      if (!strcmp(lvl, val->c_name)) {
        levval |= set ? LOG_MASK(val->c_val):LOG_UPTO(val->c_val);
        if (neg) levval = ~levval;
        break;
      }
      count++;
    }
    if (((CODE*) &prioritynames[count])->c_val == -1) return -1;

    count = 0;
    set = levval;
    while(set) {
      if (set & 0x1) file->facility[count] |= facval;
      set >>= 1;
      count++;
    }
    for (count = 0; count < LOG_NFACILITIES; count++) {
      if (facval & 0x1) file->level[count] |= levval;
      facval >>= 1;
    }
  }
  free(tmp);

  return 0;
}

// Parse config file and update the log file list.
static int parse_config_file(void)
{
  struct logfile *file;
  FILE *fp = NULL;
  char *confline = NULL, *tk = NULL, *tokens[2] = {NULL, NULL};
  int len, linelen, tcount, lineno = 0;
  struct arg_list *node;
  /*
   * if -K then open only /dev/kmsg
   * all other log files are neglected
   * thus no need to open config either.
   */
  if (toys.optflags & FLAG_K) {
    node = xzalloc(sizeof(struct arg_list));
    file = xzalloc(sizeof(struct logfile));
    file->filename = "/dev/kmsg";
    file->config = "*.*";
    memset(file->level, 0xFF, sizeof(file->level));
    memset(file->facility, 0xFFFFFFFF, sizeof(file->facility));
    node->arg = (char*) file;
    TT.lfiles = node;
    return 0;
  }
  /*
   * if -R then add remote host to log list
   * if -L is not provided all other log
   * files are neglected thus no need to
   * open config either so just return.
   */
   if (toys.optflags & FLAG_R) {
     node = xzalloc(sizeof(struct arg_list));
     file = xzalloc(sizeof(struct logfile));
     file->filename = xmsprintf("@%s",TT.remote_log);
     file->isNetwork = 1;
     file->config = "*.*";
     memset(file->level, 0xFF, sizeof(file->level));
     memset(file->facility, 0xFFFFFFFF, sizeof(file->facility));
     node->arg = (char*) file;
     TT.lfiles = node;
     if (!(toys.optflags & FLAG_L))return 0;
   }
  /*
   * Read config file and add logfiles to the list
   * with their configuration.
   */
  fp = fopen(TT.config_file, "r");
  if (!fp && (toys.optflags & FLAG_f))
    perror_exit("can't open '%s'", TT.config_file);

  for (len = 0, linelen = 0; fp;) {
    len = getline(&confline, (size_t*) &linelen, fp);
    if (len <= 0) break;
    lineno++;
    for (; *confline == ' '; confline++, len--) ;
    if ((confline[0] == '#') || (confline[0] == '\n')) continue;
    for (tcount = 0, tk = strtok(confline, " \t"); tk && (tcount < 2); tk =
        strtok(NULL, " \t"), tcount++) {
      if (tcount == 2) {
        error_msg("error in '%s' at line %d", TT.config_file, lineno);
        return -1;
      }
      tokens[tcount] = xstrdup(tk);
    }
    if (tcount <= 1 || tcount > 2) {
      if (tokens[0]) free(tokens[0]);
      error_msg("bad line %d: 1 tokens found, 2 needed", lineno);
      return -1;
    }
    tk = (tokens[1] + (strlen(tokens[1]) - 1));
    if (*tk == '\n') *tk = '\0';
    if (*tokens[1] == '\0') {
      error_msg("bad line %d: 1 tokens found, 2 needed", lineno);
      return -1;
    }
    if (*tokens[1] != '*') {
      node = get_file_node(tokens[1], TT.lfiles);
      if (!node) {
        node = xzalloc(sizeof(struct arg_list));
        file = xzalloc(sizeof(struct logfile));
        file->config = xstrdup(tokens[0]);
        if (resolve_config(file)==-1) {
          error_msg("error in '%s' at line %d", TT.config_file, lineno);
          return -1;
        }
        file->filename = xstrdup(tokens[1]);
        if (*file->filename == '@') file->isNetwork = 1;
        node->arg = (char*) file;
        node->next = TT.lfiles;
        TT.lfiles = node;
      } else {
        file = (struct logfile*) node->arg;
        int rel = strlen(file->config) + strlen(tokens[0]) + 2;
        file->config = xrealloc(file->config, rel);
        sprintf(file->config, "%s;%s", file->config, tokens[0]);
      }
    }
    if (tokens[0]) free(tokens[0]);
    if (tokens[1]) free(tokens[1]);
    free(confline);
    confline = NULL;
  }
  /*
   * Can't open config file or support is not enabled
   * adding default logfile to the head of list.
   */
  if (!fp){
    node = xzalloc(sizeof(struct arg_list));
    file = xzalloc(sizeof(struct logfile));
    file->filename = (toys.optflags & FLAG_O) ?
                     TT.logfile : "/var/log/messages"; //DEFLOGFILE
    file->isNetwork = 0;
    file->config = "*.*";
    memset(file->level, 0xFF, sizeof(file->level));
    memset(file->facility, 0xFFFFFFFF, sizeof(file->facility));
    node->arg = (char*) file;
    node->next = TT.lfiles;
    TT.lfiles = node;
  }
  if (fp) {
    fclose(fp);
    fp = NULL;
  }
  return 0;
}

// open every log file in list.
static void open_logfiles(void)
{
  struct arg_list *node;

  for (node = TT.lfiles; node; node = node->next) {
    struct logfile *tfd = (struct logfile*) node->arg;
    char *p, *tmpfile;
    long port = 514;

    if (tfd->isNetwork) {
      struct addrinfo *info, rp;

      tmpfile = xstrdup(tfd->filename + 1);
      if ((p = strchr(tmpfile, ':'))) {
        char *endptr;

        *p = '\0';
        port = strtol(++p, &endptr, 10);
        if (*endptr || endptr == p || port < 0 || port > 65535)
          error_exit("bad port in %s", tfd->filename);
      }
      memset(&rp, 0, sizeof(rp));
      rp.ai_family = AF_INET;
      rp.ai_socktype = SOCK_DGRAM;
      rp.ai_protocol = IPPROTO_UDP;

      if (getaddrinfo(tmpfile, NULL, &rp, &info) || !info) 
        perror_exit("BAD ADDRESS: can't find : %s ", tmpfile);
      ((struct sockaddr_in*)info->ai_addr)->sin_port = htons(port);
      memcpy(&tfd->saddr, info->ai_addr, info->ai_addrlen);
      freeaddrinfo(info);

      tfd->logfd = xsocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
      free(tmpfile);
    } else tfd->logfd = open(tfd->filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
    if (tfd->logfd < 0) {
      tfd->filename = "/dev/console";
      tfd->logfd = open(tfd->filename, O_APPEND);
    }
  }
}

//write to file with rotation
static int write_rotate(struct logfile *tf, int len)
{
  int size, isreg;
  struct stat statf;
  isreg = (!fstat(tf->logfd, &statf) && S_ISREG(statf.st_mode));
  size = statf.st_size;

  if ((toys.optflags & FLAG_s) || (toys.optflags & FLAG_b)) {
    if (TT.rot_size && isreg && (size + len) > (TT.rot_size*1024)) {
      if (TT.rot_count) { /* always 0..99 */
        int i = strlen(tf->filename) + 3 + 1;
        char old_file[i];
        char new_file[i];
        i = TT.rot_count - 1;
        while (1) {
          sprintf(new_file, "%s.%d", tf->filename, i);
          if (!i) break;
          sprintf(old_file, "%s.%d", tf->filename, --i);
          rename(old_file, new_file);
        }
        rename(tf->filename, new_file);
        unlink(tf->filename);
        close(tf->logfd);
        tf->logfd = open(tf->filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
      }
      ftruncate(tf->logfd, 0);
    }
  }
  return write(tf->logfd, toybuf, len);
}

// Lookup numerical code from name
// Only used in logger
int logger_lookup(int where, char *key)
{
  CODE *w = ((CODE *[]){facilitynames, prioritynames})[where];

  for (; w->c_name; w++)
    if (!strcasecmp(key, w->c_name)) return w->c_val;

  return -1;
}

//search the given name and return its value
static char *dec(int val, CODE *clist)
{
  const CODE *c;

  for (c = clist; c->c_name; c++) 
    if (val == c->c_val) return c->c_name;
  return itoa(val);
}

// Compute priority from "facility.level" pair
static void priority_to_string(int pri, char **facstr, char **lvlstr)
{
  int fac,lev;

  fac = LOG_FAC(pri);
  lev = LOG_PRI(pri);
  *facstr = dec(fac<<3, facilitynames);
  *lvlstr = dec(lev, prioritynames);
}

//Parse messege and write to file.
static void logmsg(char *msg, int len)
{
  time_t now;
  char *p, *ts, *lvlstr, *facstr;
  struct utsname uts;
  int pri = 0;
  struct arg_list *lnode = TT.lfiles;

  char *omsg = msg;
  int olen = len, fac, lvl;
  
  if (*msg == '<') { // Extract the priority no.
    pri = (int) strtoul(msg + 1, &p, 10);
    if (*p == '>') msg = p + 1;
  }
  /* Jan 18 00:11:22 msg...
   * 01234567890123456
   */
  if (len < 16 || msg[3] != ' ' || msg[6] != ' ' || msg[9] != ':'
      || msg[12] != ':' || msg[15] != ' ') {
    time(&now);
    ts = ctime(&now) + 4; /* skip day of week */
  } else {
    now = 0;
    ts = msg;
    msg += 16;
  }
  ts[15] = '\0';
  fac = LOG_FAC(pri);
  lvl = LOG_PRI(pri);

  if (toys.optflags & FLAG_K) len = sprintf(toybuf, "<%d> %s\n", pri, msg);
  else {
    priority_to_string(pri, &facstr, &lvlstr);

    p = "local";
    if (!uname(&uts)) p = uts.nodename;
    if (toys.optflags & FLAG_S) len = sprintf(toybuf, "%s %s\n", ts, msg);
    else len = sprintf(toybuf, "%s %s %s.%s %s\n", ts, p, facstr, lvlstr, msg);
  }
  if (lvl >= TT.log_prio) return;

  while (lnode) {
    struct logfile *tf = (struct logfile*) lnode->arg;
    if (tf->logfd > 0) {
      if ((tf->facility[lvl] & (1 << fac)) && (tf->level[fac] & (1<<lvl))) {
        int wlen;
        if (tf->isNetwork)
          wlen = sendto(tf->logfd, omsg, olen, 0, (struct sockaddr*)&tf->saddr, sizeof(tf->saddr));
        else wlen = write_rotate(tf, len);
        if (wlen < 0) perror_msg("write failed file : %s ", tf->filename + tf->isNetwork);
      }
    }
    lnode = lnode->next;
  }
}

/*
 * closes all read and write fds
 * and frees all nodes and lists
 */
static void cleanup(void)
{
  struct arg_list *fnode;
  while (TT.lsocks) {
    fnode = TT.lsocks;
    if (((struct unsocks*) fnode->arg)->sd >= 0)
      close(((struct unsocks*) fnode->arg)->sd);
    free(fnode->arg);
    TT.lsocks = fnode->next;
    free(fnode);
  }
  unlink("/dev/log");

  while (TT.lfiles) {
    fnode = TT.lfiles;
    if (((struct logfile*) fnode->arg)->logfd >= 0)
      close(((struct logfile*) fnode->arg)->logfd);
    free(fnode->arg);
    TT.lfiles = fnode->next;
    free(fnode);
  }
}

static void signal_handler(int sig)
{
  unsigned char ch = sig;
  if (write(TT.sigfd[1], &ch, 1) != 1) error_msg("can't send signal");
}

void syslogd_main(void)
{
  struct unsocks *tsd;
  int maxfd, retval, last_len=0;
  struct timeval tv;
  struct arg_list *node;
  char *temp, *buffer = (toybuf +2048), *last_buf = (toybuf + 3072); //these two buffs are of 1K each

  if ((toys.optflags & FLAG_p) && (strlen(TT.unix_socket) > 108))
    error_exit("Socket path should not be more than 108");

  TT.config_file = (toys.optflags & FLAG_f) ?
                   TT.config_file : "/etc/syslog.conf"; //DEFCONFFILE
init_jumpin:
  TT.lsocks = xzalloc(sizeof(struct arg_list));
  tsd = xzalloc(sizeof(struct unsocks));

  tsd->path = (toys.optflags & FLAG_p) ? TT.unix_socket : "/dev/log"; // DEFLOGSOCK
  TT.lsocks->arg = (char*) tsd;

  if (toys.optflags & FLAG_a) {
    for (temp = strtok(TT.socket, ":"); temp; temp = strtok(NULL, ":")) {
      struct arg_list *ltemp = xzalloc(sizeof(struct arg_list));
      if (strlen(temp) > 107) temp[108] = '\0';
      tsd = xzalloc(sizeof(struct unsocks));
      tsd->path = temp;
      ltemp->arg = (char*) tsd;
      ltemp->next = TT.lsocks;
      TT.lsocks = ltemp;
    }
  }
  if (!open_unix_socks()) {
    error_msg("Can't open single socket for listenning.");
    goto clean_and_exit;
  }

  // Setup signals
  if (pipe(TT.sigfd) < 0) error_exit("pipe failed\n");

  fcntl(TT.sigfd[1] , F_SETFD, FD_CLOEXEC);
  fcntl(TT.sigfd[0] , F_SETFD, FD_CLOEXEC);
  int flags = fcntl(TT.sigfd[1], F_GETFL);
  fcntl(TT.sigfd[1], F_SETFL, flags | O_NONBLOCK);
  signal(SIGHUP, signal_handler);
  signal(SIGTERM, signal_handler);
  signal(SIGINT, signal_handler);
  signal(SIGQUIT, signal_handler);

  if (parse_config_file() == -1) goto clean_and_exit;
  open_logfiles();
  if (!(toys.optflags & FLAG_n)) {
    //don't daemonize again if SIGHUP received.
    toys.optflags |= FLAG_n;
  }
  {
    int pid_fd = open("/var/run/syslogd.pid", O_CREAT | O_WRONLY | O_TRUNC, 0666);
    if (pid_fd > 0) {
      unsigned pid = getpid();
      int len = sprintf(toybuf, "%u\n", pid);
      write(pid_fd, toybuf, len);
      close(pid_fd);
    }
  }

  logmsg("<46>Toybox: syslogd started", 27); //27 : the length of message
  for (;;) {
    maxfd = addrfds();
    tv.tv_usec = 0;
    tv.tv_sec = TT.interval*60;

    retval = select(maxfd + 1, &TT.rfds, NULL, NULL, (TT.interval)?&tv:NULL);
    if (retval < 0) { /* Some error. */
      if (errno == EINTR) continue;
      perror_msg("Error in select ");
      continue;
    }
    if (!retval) { /* Timed out */
      logmsg("<46>-- MARK --", 14);
      continue;
    }
    if (FD_ISSET(TT.sigfd[0], &TT.rfds)) { /* May be a signal */
      unsigned char sig;

      if (read(TT.sigfd[0], &sig, 1) != 1) {
        error_msg("signal read failed.\n");
        continue;
      }
      switch(sig) {
        case SIGTERM:    /* FALLTHROUGH */
        case SIGINT:     /* FALLTHROUGH */
        case SIGQUIT:
          logmsg("<46>syslogd exiting", 19);
          if (CFG_TOYBOX_FREE ) cleanup();
          signal(sig, SIG_DFL);
          sigset_t ss;
          sigemptyset(&ss);
          sigaddset(&ss, sig);
          sigprocmask(SIG_UNBLOCK, &ss, NULL);
          raise(sig);
          _exit(1);  /* Should not reach it */
          break;
        case SIGHUP:
          logmsg("<46>syslogd exiting", 19);
          cleanup(); //cleanup is done, as we restart syslog.
          goto init_jumpin;
        default: break;
      }
    }
    if (retval > 0) { /* Some activity on listen sockets. */
      node = TT.lsocks;
      while (node) {
        int sd = ((struct unsocks*) node->arg)->sd;
        if (FD_ISSET(sd, &TT.rfds)) {
          int len = read(sd, buffer, 1023); //buffer is of 1K, hence readingonly 1023 bytes, 1 for NUL
          if (len > 0) {
            buffer[len] = '\0';
            if((toys.optflags & FLAG_D) && (len == last_len))
              if (!memcmp(last_buf, buffer, len)) break;

            memcpy(last_buf, buffer, len);
            last_len = len;
            logmsg(buffer, len);
          }
          break;
        }
        node = node->next;
      }
    }
  }
clean_and_exit:
  logmsg("<46>syslogd exiting", 19);
  if (CFG_TOYBOX_FREE ) cleanup();
}