/* syslogd.c - a system logging utility. * * Copyright 2013 Madhur Verma * Copyright 2013 Kyungwan Han * * 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 -f FILE Config file -p Alternative unix domain socket -n Avoid auto-backgrounding -S Smaller output -m MARK interval (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 #include "toys.h" // UNIX Sockets for listening struct unsocks { struct unsocks *next; char *path; struct sockaddr_un sdu; int sd; }; // Log file entry to log into. struct logfile { struct logfile *next; char *filename; uint32_t facility[8]; uint8_t level[LOG_NFACILITIES]; int logfd; struct sockaddr_in saddr; }; 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; // list of listen sockets struct logfile *lfiles; // list of write logfiles int sigfd[2]; ) // Lookup numerical code from name // Also 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, char *buf) { for (; clist->c_name; clist++) if (val == clist->c_val) return clist->c_name; sprintf(buf, "%u", val); return buf; } /* * 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 *config) { char *tk; for (tk = strtok(config, "; \0"); tk; tk = strtok(NULL, "; \0")) { char *fac = tk, *lvl; int i = 0; unsigned facval = 0; uint8_t set, levval, bits = 0; tk = strchr(fac, '.'); if (!tk) return -1; *tk = '\0'; lvl = tk + 1; for (;;) { char *nfac = strchr(fac, ','); if (nfac) *nfac = '\0'; if (*fac == '*') { facval = 0xFFFFFFFF; if (fac[1]) return -1; } else { if ((i = logger_lookup(0, fac)) == -1) return -1; facval |= (1 << LOG_FAC(i)); } if (nfac) fac = nfac + 1; else break; } levval = 0; for (tk = "!=*"; *tk; tk++, bits <<= 1) { if (*lvl == *tk) { bits++; lvl++; } } if (bits & 2) levval = 0xff; if (*lvl) { if ((i = logger_lookup(1, lvl)) == -1) return -1; levval |= (bits & 4) ? LOG_MASK(i) : LOG_UPTO(i); if (bits & 8) levval = ~levval; } for (i = 0, set = levval; set; set >>= 1, i++) if (set & 0x1) file->facility[i] |= ~facval; for (i = 0; i < LOG_NFACILITIES; facval >>= 1, i++) if (facval & 0x1) file->level[i] |= ~levval; } return 0; } // Parse config file and update the log file list. static int parse_config_file(void) { struct logfile *file; FILE *fp; char *confline, *tk[2]; int len, lineno = 0; size_t linelen; /* * 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) { file = xzalloc(sizeof(struct logfile)); file->filename = xstrdup("/dev/kmsg"); TT.lfiles = file; 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) { file = xzalloc(sizeof(struct logfile)); file->filename = xmprintf("@%s",TT.remote_log); TT.lfiles = file; if (!(toys.optflags & FLAG_L)) return 0; } /* * Read config file and add logfiles to the list * with their configuration. */ if (!(fp = fopen(TT.config_file, "r")) && (toys.optflags & FLAG_f)) perror_exit("can't open '%s'", TT.config_file); for (linelen = 0; fp;) { confline = NULL; len = getline(&confline, &linelen, fp); if (len <= 0) break; lineno++; for (; *confline == ' '; confline++, len--) ; if ((confline[0] == '#') || (confline[0] == '\n')) continue; tk[0] = confline; for (; len && !(*tk[0]==' ' || *tk[0]=='\t'); tk[0]++, len--); for (tk[1] = tk[0]; len && (*tk[1]==' ' || *tk[1]=='\t'); tk[1]++, len--); if (!len || (len == 1 && *tk[1] == '\n')) { error_msg("error in '%s' at line %d", TT.config_file, lineno); return -1; } else if (*(tk[1] + len - 1) == '\n') *(tk[1] + len - 1) = '\0'; *tk[0] = '\0'; if (*tk[1] != '*') { file = TT.lfiles; while (file && strcmp(file->filename, tk[1])) file = file->next; if (!file) { file = xzalloc(sizeof(struct logfile)); file->filename = xstrdup(tk[1]); file->next = TT.lfiles; TT.lfiles = file; } if (resolve_config(file, confline) == -1) { error_msg("error in '%s' at line %d", TT.config_file, lineno); return -1; } } free(confline); } /* * Can't open config file or support is not enabled * adding default logfile to the head of list. */ if (!fp){ file = xzalloc(sizeof(struct logfile)); file->filename = xstrdup((toys.optflags & FLAG_O) ? TT.logfile : "/var/log/messages"); //DEFLOGFILE file->next = TT.lfiles; TT.lfiles = file; } else fclose(fp); return 0; } // open every log file in list. static void open_logfiles(void) { struct logfile *tfd; for (tfd = TT.lfiles; tfd; tfd = tfd->next) { char *p, *tmpfile; long port = 514; if (*tfd->filename == '@') { // network 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); if (tf->logfd < 0) { perror_msg("can't open %s", tf->filename); return -1; } } ftruncate(tf->logfd, 0); } } return write(tf->logfd, toybuf, len); } //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 logfile *tf = 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 { char facbuf[12], pribuf[12]; facstr = dec(pri & LOG_FACMASK, facilitynames, facbuf); lvlstr = dec(LOG_PRI(pri), prioritynames, pribuf); 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; for (; tf; tf = tf->next) { if (tf->logfd > 0) { if (!((tf->facility[lvl] & (1 << fac)) || (tf->level[fac] & (1<filename == '@'; if (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 + isNetwork); } } } } /* * closes all read and write fds * and frees all nodes and lists */ static void cleanup(void) { while (TT.lsocks) { struct unsocks *fnode = TT.lsocks; if (fnode->sd >= 0) { close(fnode->sd); unlink(fnode->path); } TT.lsocks = fnode->next; free(fnode); } while (TT.lfiles) { struct logfile *fnode = TT.lfiles; free(fnode->filename); if (fnode->logfd >= 0) close(fnode->logfd); 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 nfds, retval, last_len=0; struct timeval tv; fd_set rfds; // fds for reading 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: tsd = xzalloc(sizeof(struct unsocks)); tsd->path = (toys.optflags & FLAG_p) ? TT.unix_socket : "/dev/log"; // DEFLOGSOCK TT.lsocks = tsd; if (toys.optflags & FLAG_a) { for (temp = strtok(TT.socket, ":"); temp; temp = strtok(NULL, ":")) { if (strlen(temp) > 107) temp[108] = '\0'; tsd = xzalloc(sizeof(struct unsocks)); tsd->path = temp; tsd->next = TT.lsocks; TT.lsocks = tsd; } } /* * initializes unsock_t structure * and opens socket for reading * and adds to global lsock list. */ nfds = 0; for (tsd = TT.lsocks; tsd; tsd = tsd->next) { tsd->sdu.sun_family = AF_UNIX; strcpy(tsd->sdu.sun_path, tsd->path); tsd->sd = socket(AF_UNIX, SOCK_DGRAM, 0); if (tsd->sd < 0) { perror_msg("OPEN SOCKS : failed"); continue; } unlink(tsd->sdu.sun_path); if (bind(tsd->sd, (struct sockaddr *) &tsd->sdu, sizeof(tsd->sdu))) { perror_msg("BIND SOCKS : failed sock : %s", tsd->sdu.sun_path); close(tsd->sd); continue; } chmod(tsd->path, 0777); nfds++; } if (!nfds) { error_msg("Can't open single socket for listenning."); goto clean_and_exit; } // Setup signals xpipe(TT.sigfd); 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)) { daemon(0, 0); //don't daemonize again if SIGHUP received. toys.optflags |= FLAG_n; } xpidfile("syslogd"); logmsg("<46>Toybox: syslogd started", 27); //27 : the length of message for (;;) { // Add opened socks to rfds for select() FD_ZERO(&rfds); for (tsd = TT.lsocks; tsd; tsd = tsd->next) FD_SET(tsd->sd, &rfds); FD_SET(TT.sigfd[0], &rfds); tv.tv_usec = 0; tv.tv_sec = TT.interval*60; retval = select(TT.sigfd[0] + 1, &rfds, NULL, NULL, (TT.interval)?&tv:NULL); if (retval < 0) { if (errno != EINTR) perror_msg("Error in select "); } else if (!retval) logmsg("<46>-- MARK --", 14); else if (FD_ISSET(TT.sigfd[0], &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; } } else { /* Some activity on listen sockets. */ for (tsd = TT.lsocks; tsd; tsd = tsd->next) { int sd = tsd->sd; if (FD_ISSET(sd, &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; } } } } clean_and_exit: logmsg("<46>syslogd exiting", 19); if (CFG_TOYBOX_FREE ) cleanup(); }