You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
546 lines
15 KiB
546 lines
15 KiB
/* 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
|
|
#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<<lvl)))) {
|
|
int wlen, isNetwork = *tf->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();
|
|
}
|