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
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
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;struct logfile * lfiles;int sigfd[2];)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
dec(int val,CODE * clist,char * buf)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 */
resolve_config(struct logfile * file,char * config)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.
parse_config_file(void)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.
open_logfiles(void)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
write_rotate(struct logfile * tf,int len)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.
logmsg(char * msg,int len)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 */
cleanup(void)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
signal_handler(int sig)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
syslogd_main(void)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