]> git.saurik.com Git - apple/syslog.git/blob - syslogd.tproj/bsd_out.c
a24fdaf9578de8ec75df5b16e65b10a753bf4d68
[apple/syslog.git] / syslogd.tproj / bsd_out.c
1 /*
2 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * "Portions Copyright (c) 2004 Apple Computer, Inc. All Rights
7 * Reserved. This file contains Original Code and/or Modifications of
8 * Original Code as defined in and that are subject to the Apple Public
9 * Source License Version 1.0 (the 'License'). You may not use this file
10 * except in compliance with the License. Please obtain a copy of the
11 * License at http://www.apple.com/publicsource and read it before using
12 * this file.
13 *
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
19 * License for the specific language governing rights and limitations
20 * under the License."
21 *
22 * @APPLE_LICENSE_HEADER_END@
23 */
24
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/socket.h>
28 #include <sys/un.h>
29 #include <sys/uio.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <fcntl.h>
35 #include <errno.h>
36 #include <netdb.h>
37 #include <notify.h>
38 #include "daemon.h"
39
40 #define MY_ID "bsd_out"
41
42 #define _PATH_WALL "/usr/bin/wall"
43 #define ASL_KEY_FACILITY "Facility"
44 #define FACILITY_KERNEL "kern"
45 #define _PATH_CONSOLE "/dev/console"
46 #define IndexNull ((uint32_t)-1)
47
48 #define DST_TYPE_NONE 0
49 #define DST_TYPE_FILE 1
50 #define DST_TYPE_CONS 2
51 #define DST_TYPE_SOCK 3
52 #define DST_TYPE_WALL 4
53 #define DST_TYPE_NOTE 5
54
55 static asl_msg_t *query = NULL;
56 static int reset = 0;
57
58 struct config_rule
59 {
60 uint32_t count;
61 char *dst;
62 int fd;
63 int type;
64 struct sockaddr *addr;
65 char **facility;
66 int *pri;
67 TAILQ_ENTRY(config_rule) entries;
68 };
69
70 static TAILQ_HEAD(cr, config_rule) bsd_out_rule;
71
72 int bsd_out_close();
73 static int _parse_config_file(const char *);
74
75 static void
76 _do_reset(void)
77 {
78 bsd_out_close();
79 _parse_config_file(_PATH_SYSLOG_CONF);
80 }
81
82 static char **
83 _insertString(char *s, char **l, uint32_t x)
84 {
85 int i, len;
86
87 if (s == NULL) return l;
88 if (l == NULL)
89 {
90 l = (char **)malloc(2 * sizeof(char *));
91 l[0] = strdup(s);
92 l[1] = NULL;
93 return l;
94 }
95
96 for (i = 0; l[i] != NULL; i++);
97 len = i + 1; /* count the NULL on the end of the list too! */
98
99 l = (char **)realloc(l, (len + 1) * sizeof(char *));
100
101 if ((x >= (len - 1)) || (x == IndexNull))
102 {
103 l[len - 1] = strdup(s);
104 l[len] = NULL;
105 return l;
106 }
107
108 for (i = len; i > x; i--) l[i] = l[i - 1];
109 l[x] = strdup(s);
110 return l;
111 }
112
113 static char **
114 _explode(char *s, char *delim)
115 {
116 char **l = NULL;
117 char *p, *t;
118 int i, n;
119
120 if (s == NULL) return NULL;
121
122 p = s;
123 while (p[0] != '\0')
124 {
125 for (i = 0; ((p[i] != '\0') && (strchr(delim, p[i]) == NULL)); i++);
126 n = i;
127 t = malloc(n + 1);
128 for (i = 0; i < n; i++) t[i] = p[i];
129 t[n] = '\0';
130 l = _insertString(t, l, IndexNull);
131 free(t);
132 t = NULL;
133 if (p[i] == '\0') return l;
134 if (p[i + 1] == '\0') l = _insertString("", l, IndexNull);
135 p = p + i + 1;
136 }
137
138 return l;
139 }
140
141 static void
142 _freeList(char **l)
143 {
144 int i;
145
146 if (l == NULL) return;
147 for (i = 0; l[i] != NULL; i++) free(l[i]);
148 free(l);
149 }
150
151 static int
152 _level_for_name(const char *name)
153 {
154 if (name == NULL) return -1;
155
156 if (!strcasecmp(name, "emerg")) return ASL_LEVEL_EMERG;
157 if (!strcasecmp(name, "panic")) return ASL_LEVEL_EMERG;
158 if (!strcasecmp(name, "alert")) return ASL_LEVEL_ALERT;
159 if (!strcasecmp(name, "crit")) return ASL_LEVEL_CRIT;
160 if (!strcasecmp(name, "err")) return ASL_LEVEL_ERR;
161 if (!strcasecmp(name, "error")) return ASL_LEVEL_ERR;
162 if (!strcasecmp(name, "warn")) return ASL_LEVEL_WARNING;
163 if (!strcasecmp(name, "warning")) return ASL_LEVEL_WARNING;
164 if (!strcasecmp(name, "notice")) return ASL_LEVEL_NOTICE;
165 if (!strcasecmp(name, "info")) return ASL_LEVEL_INFO;
166 if (!strcasecmp(name, "debug")) return ASL_LEVEL_DEBUG;
167 if (!strcmp(name, "*")) return ASL_LEVEL_DEBUG;
168
169 /* special case */
170 if (!strcasecmp(name, "none")) return -2;
171
172 return -1;
173 }
174
175 static int
176 _syslog_dst_open(struct config_rule *r)
177 {
178 int i;
179 char *node, *serv;
180 struct addrinfo hints, *gai, *ai;
181
182 if (r == NULL) return -1;
183
184 r->fd = -1;
185
186 if (r->dst[0] == '/')
187 {
188 r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT, 0644);
189 if (r->fd < 0)
190 {
191 asldebug("%s: open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno));
192 return -1;
193 }
194
195 r->type = DST_TYPE_FILE;
196 if (!strcmp(r->dst, _PATH_CONSOLE)) r->type = DST_TYPE_CONS;
197
198 return 0;
199 }
200
201 if (r->dst[0] == '!')
202 {
203 r->type = DST_TYPE_NOTE;
204 return 0;
205 }
206
207 if (r->dst[0] == '@')
208 {
209 node = strdup(r->dst + 1);
210 if (node == NULL) return -1;
211
212 serv = NULL;
213 serv = strrchr(node, ':');
214 if (serv != NULL) *serv++ = '\0';
215 else serv = "syslog";
216
217 memset(&hints, 0, sizeof(hints));
218 hints.ai_family = PF_UNSPEC;
219 hints.ai_socktype = SOCK_DGRAM;
220 i = getaddrinfo(node, serv, &hints, &gai);
221 free(node);
222 if (i != 0)
223 {
224 asldebug("%s: getaddrinfo failed for node %s service %s: (%s)\n", MY_ID, node, serv, gai_strerror(i));
225 return -1;
226 }
227
228 for (ai = gai; ai != NULL; ai = ai->ai_next)
229 {
230 r->fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
231 if (r->fd < 0) continue;
232
233 r->addr = (struct sockaddr *)calloc(1, ai->ai_addrlen);
234 memcpy(r->addr, ai->ai_addr, ai->ai_addrlen);
235
236 break;
237 }
238
239 freeaddrinfo(gai);
240
241 if (r->fd < 0)
242 {
243 asldebug("%s: connection failed for %s\n", MY_ID, (r->dst) + 1);
244 return -1;
245 }
246
247 if (fcntl(r->fd, F_SETFL, O_NONBLOCK) < 0)
248 {
249 close(r->fd);
250 r->fd = -1;
251 asldebug("%s: couldn't set O_NONBLOCK for fd %d: %s\n", MY_ID, r->fd, strerror(errno));
252 return -1;
253 }
254
255 r->type = DST_TYPE_SOCK;
256 return 0;
257
258 }
259
260 if (strcmp(r->dst, "*") == 0)
261 {
262 r->type = DST_TYPE_WALL;
263 return 0;
264 }
265
266 /* Can't deal with dst! */
267 asldebug("%s: unsupported / unknown output name: %s\n", MY_ID, r->dst);
268 return -1;
269 }
270
271 static int
272 _parse_line(char *s)
273 {
274 char **semi, **comma;
275 int i, j, n, lasts, lastc, pri;
276 struct config_rule *out;
277
278 if (s == NULL) return -1;
279 while ((*s == ' ') || (*s == '\t')) s++;
280 if (*s == '#') return -1;
281
282 semi = _explode(s, "; \t");
283
284 if (semi == NULL) return -1;
285 out = (struct config_rule *)calloc(1, sizeof(struct config_rule));
286 if (out == NULL) return -1;
287
288 n = 0;
289 lasts = -1;
290 for (i = 0; semi[i] != NULL; i++)
291 {
292 if (semi[i][0] == '\0') continue;
293 n++;
294 lasts = i;
295 }
296
297 out->dst = strdup(semi[lasts]);
298 _syslog_dst_open(out);
299
300 for (i = 0; i < lasts; i++)
301 {
302 if (semi[i][0] == '\0') continue;
303 comma = _explode(semi[i], ",.");
304 lastc = -1;
305 for (j = 0; comma[j] != NULL; j++)
306 {
307 if (comma[j][0] == '\0') continue;
308 lastc = j;
309 }
310
311 for (j = 0; j < lastc; j++)
312 {
313 if (comma[j][0] == '\0') continue;
314 pri = _level_for_name(comma[lastc]);
315 if (pri == -1) continue;
316
317 if (out->count == 0)
318 {
319 out->facility = (char **)calloc(1, sizeof(char *));
320 out->pri = (int *)calloc(1, sizeof(int));
321 }
322 else
323 {
324 out->facility = (char **)realloc(out->facility, (out->count + 1) * sizeof(char *));
325 out->pri = (int *)realloc(out->pri, (out->count + 1) * sizeof(int));
326 }
327 out->facility[out->count] = strdup(comma[j]);
328 out->pri[out->count] = pri;
329 out->count++;
330 }
331
332 _freeList(comma);
333 }
334
335 _freeList(semi);
336
337 TAILQ_INSERT_TAIL(&bsd_out_rule, out, entries);
338
339 return 0;
340 }
341
342 static int
343 _syslog_send(asl_msg_t *msg, struct config_rule *r, char **out, char **fwd)
344 {
345 char *so, *sf, *vt, *p;
346 const char *vtime, *vhost, *vident, *vpid, *vmsg, *vlevel, *vfacility;
347 size_t outlen, n;
348 int pf, fc, status;
349 FILE *pw;
350 time_t tick;
351
352 if (out == NULL) return -1;
353 if (fwd == NULL) return -1;
354
355 if (r->type == DST_TYPE_NOTE)
356 {
357 notify_post(r->dst+1);
358 return 0;
359 }
360
361 vt = NULL;
362
363 /* Build output string if it hasn't been built by a previous rule-match */
364 if (*out == NULL)
365 {
366 vtime = asl_get(msg, ASL_KEY_TIME);
367 if (vtime != NULL)
368 {
369 tick = asl_parse_time(vtime);
370 if (tick != (time_t)-1)
371 {
372 p = ctime(&tick);
373 vt = malloc(16);
374 if (vt == NULL) return -1;
375 memcpy(vt, p+4, 15);
376 vt[15] = '\0';
377 }
378 }
379 else if (strlen(vtime) < 24) vt = strdup(vtime);
380 else
381 {
382 vt = malloc(16);
383 if (vt == NULL) return -1;
384 memcpy(vt, vtime+4, 15);
385 vt[15] = '\0';
386 }
387
388 if (vt == NULL)
389 {
390 tick = time(NULL);
391 p = ctime(&tick);
392 vt = malloc(16);
393 if (vt == NULL) return -1;
394 memcpy(vt, p+4, 15);
395 vt[15] = '\0';
396 }
397
398 vhost = asl_get(msg, ASL_KEY_HOST);
399 if (vhost == NULL) vhost = "localhost";
400
401 vident = asl_get(msg, ASL_KEY_SENDER);
402 if ((vident != NULL) && (!strcmp(vident, "Unknown"))) vident = NULL;
403
404 vpid = asl_get(msg, ASL_KEY_PID);
405 if ((vpid != NULL) && (!strcmp(vpid, "-1"))) vpid = NULL;
406
407 if ((vpid != NULL) && (vident == NULL)) vident = "Unknown";
408
409 vmsg = asl_get(msg, ASL_KEY_MSG);
410
411 n = 0;
412 if (vt != NULL) n += (strlen(vt) + 1);
413 if (vhost != NULL) n += (strlen(vhost) + 1);
414 if (vident != NULL) n += strlen(vident);
415 n += 2;
416 if (vpid != NULL) n += (strlen(vpid) + 2);
417 if (vmsg != NULL) n += strlen(vmsg);
418
419 if (n == 0) return -1;
420 n += 2;
421
422 so = calloc(1, n);
423 if (so == NULL) return -1;
424
425 if (vt != NULL)
426 {
427 strcat(so, vt);
428 strcat(so, " ");
429 }
430
431 if (vhost != NULL)
432 {
433 strcat(so, vhost);
434 strcat(so, " ");
435 }
436
437 if (vident != NULL)
438 {
439 strcat(so, vident);
440 if (vpid != NULL)
441 {
442 strcat(so, "[");
443 strcat(so, vpid);
444 strcat(so, "]");
445 }
446 }
447
448 strcat(so, ": ");
449
450 if (vmsg != NULL)
451 {
452 strcat(so, vmsg);
453 strcat(so, "\n");
454 }
455
456 free(vt);
457 *out = so;
458 }
459
460 if ((*fwd == NULL) && (r->type == DST_TYPE_SOCK))
461 {
462 pf = 7;
463 vlevel = asl_get(msg, ASL_KEY_LEVEL);
464 if (vlevel != NULL) pf = atoi(vlevel);
465
466 fc = asl_syslog_faciliy_name_to_num(asl_get(msg, ASL_KEY_FACILITY));
467 if (fc > 0) pf |= fc;
468
469 sf = NULL;
470 asprintf(&sf, "<%d>%s", pf, *out);
471 if (sf == NULL) return -1;
472 *fwd = sf;
473 }
474
475 outlen = 0;
476 if (r->type == DST_TYPE_SOCK) outlen = strlen(*fwd);
477 else outlen = strlen(*out);
478
479 if ((r->type == DST_TYPE_FILE) || (r->type == DST_TYPE_CONS))
480 {
481 /*
482 * Special case for kernel messages.
483 * Don't write kernel messages to /dev/console.
484 * The kernel printf routine already sends them to /dev/console
485 * so writing them here would cause duplicates.
486 */
487 vfacility = asl_get(msg, ASL_KEY_FACILITY);
488 if ((vfacility != NULL) && (!strcmp(vfacility, FACILITY_KERNEL)) && (r->type == DST_TYPE_CONS)) return 0;
489
490 status = write(r->fd, *out, outlen);
491 if ((status < 0) || (status < outlen))
492 {
493 asldebug("%s: error writing message (%s): %s\n", MY_ID, r->dst, strerror(errno));
494
495 /* Try re-opening the file (once) and write again */
496 close(r->fd);
497 r->fd = open(r->dst, O_WRONLY | O_APPEND | O_CREAT, 0644);
498 if (r->fd < 0)
499 {
500 asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID, r->dst, strerror(errno));
501 return -1;
502 }
503
504 status = write(r->fd, *out, outlen);
505 if ((status < 0) || (status < outlen))
506 {
507 asldebug("%s: error re-writing message (%s): %s\n", MY_ID, r->dst, strerror(errno));
508 }
509 }
510 }
511 else if (r->type == DST_TYPE_SOCK)
512 {
513 status = sendto(r->fd, *fwd, outlen, 0, r->addr, r->addr->sa_len);
514 if (status < 0) asldebug("%s: error sending message (%s): %s\n", MY_ID, r->dst, strerror(errno));
515 }
516 else if (r->type == DST_TYPE_WALL)
517 {
518 pw = popen(_PATH_WALL, "w");
519 if (pw < 0)
520 {
521 asldebug("%s: error sending wall message: %s\n", MY_ID, strerror(errno));
522 return -1;
523 }
524
525 fprintf(pw, *out);
526 pclose(pw);
527 }
528
529 return 0;
530 }
531
532 static int
533 _syslog_rule_match(asl_msg_t *msg, struct config_rule *r)
534 {
535 uint32_t i, test, f, pri;
536 const char *val;
537
538 if (msg == NULL) return 0;
539 if (r == NULL) return 0;
540 if (r->count == 0) return 0;
541
542 test = 0;
543
544 for (i = 0; i < r->count; i++)
545 {
546 if (r->pri[i] == -1) continue;
547
548 if ((test == 1) && (r->pri[i] >= 0)) continue;
549 if ((test == 0) && (r->pri[i] == -2)) continue;
550
551 f = 0;
552 if (strcmp(r->facility[i], "*") == 0) f = 1;
553 else
554 {
555 val = asl_get(msg, ASL_KEY_FACILITY);
556 if ((val != NULL) && (strcasecmp(r->facility[i], val) == 0)) f = 1;
557 }
558
559 if (f == 0) continue;
560
561 /* Turn off matching facility with priority "none" */
562 if (r->pri[i] == -2)
563 {
564 test = 0;
565 continue;
566 }
567
568 val = asl_get(msg, ASL_KEY_LEVEL);
569 if (val == NULL) continue;
570
571 pri = atoi(val);
572 if (pri < 0) continue;
573
574 if (pri <= r->pri[i]) test = 1;
575 }
576
577 return test;
578 }
579
580 int
581 bsd_out_sendmsg(asl_msg_t *msg, const char *outid)
582 {
583 struct config_rule *r;
584 char *out, *fwd;
585
586 if (reset != 0)
587 {
588 _do_reset();
589 reset = 0;
590 }
591
592 if (msg == NULL) return -1;
593
594 out = NULL;
595 fwd = NULL;
596
597 for (r = bsd_out_rule.tqh_first; r != NULL; r = r->entries.tqe_next)
598 {
599 if (_syslog_rule_match(msg, r) == 1) _syslog_send(msg, r, &out, &fwd);
600 }
601
602 if (out != NULL) free(out);
603 if (fwd != NULL) free(fwd);
604
605 return 0;
606 }
607
608 static int
609 _parse_config_file(const char *confname)
610 {
611 FILE *cf;
612 char *line;
613
614 cf = fopen(confname, "r");
615 if (cf == NULL) return 1;
616
617 while (NULL != (line = get_line_from_file(cf)))
618 {
619 _parse_line(line);
620 free(line);
621 }
622
623 fclose(cf);
624
625 return 0;
626 }
627
628 int
629 bsd_out_init(void)
630 {
631 asldebug("%s: init\n", MY_ID);
632
633 TAILQ_INIT(&bsd_out_rule);
634
635 query = asl_new(ASL_TYPE_QUERY);
636 aslevent_addmatch(query, MY_ID);
637 aslevent_addoutput(bsd_out_sendmsg, MY_ID);
638
639 _parse_config_file(_PATH_SYSLOG_CONF);
640 return 0;
641 }
642
643 int
644 bsd_out_reset(void)
645 {
646 reset = 1;
647 return 0;
648 }
649
650 int
651 bsd_out_close(void)
652 {
653 struct config_rule *r, *n;
654 int i;
655
656 n = NULL;
657 for (r = bsd_out_rule.tqh_first; r != NULL; r = n)
658 {
659 n = r->entries.tqe_next;
660
661 if (r->dst != NULL) free(r->dst);
662 if (r->fd > 0) close(r->fd);
663 if (r->addr != NULL) free(r->addr);
664 if (r->facility != NULL)
665 {
666 for (i = 0; i < r->count; i++)
667 {
668 if (r->facility[i] != NULL) free(r->facility[i]);
669 }
670 free(r->facility);
671 }
672 if (r->pri != NULL) free(r->pri);
673
674 TAILQ_REMOVE(&bsd_out_rule, r, entries);
675 free(r);
676 }
677
678 return 0;
679 }