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