2 * Copyright (c) 2004-2008 Apple Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <sys/types.h>
26 #include <sys/socket.h>
41 #define MY_ID "bsd_out"
43 #define _PATH_WALL "/usr/bin/wall"
44 #define ASL_KEY_FACILITY "Facility"
45 #define FACILITY_KERNEL "kern"
46 #define _PATH_CONSOLE "/dev/console"
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
55 #define CLOSE_ON_IDLE_SEC 60
57 static asl_msg_t
*query
= NULL
;
58 static int reset
= RESET_NONE
;
59 static pthread_mutex_t reset_lock
= PTHREAD_MUTEX_INITIALIZER
;
67 struct sockaddr
*addr
;
74 TAILQ_ENTRY(config_rule
) entries
;
77 static TAILQ_HEAD(cr
, config_rule
) bsd_out_rule
;
79 extern uint32_t asl_core_string_hash(const char *s
, uint32_t inlen
);
82 int bsd_out_network_reset(void);
83 static int _parse_config_file(const char *);
88 pthread_mutex_lock(&reset_lock
);
89 if (reset
== RESET_NONE
)
91 pthread_mutex_unlock(&reset_lock
);
95 if (reset
== RESET_CONFIG
)
98 _parse_config_file(_PATH_SYSLOG_CONF
);
100 else if (reset
== RESET_NETWORK
)
102 bsd_out_network_reset();
107 pthread_mutex_unlock(&reset_lock
);
111 _level_for_name(const char *name
)
113 if (name
== NULL
) return -1;
115 if (!strcasecmp(name
, "emerg")) return ASL_LEVEL_EMERG
;
116 if (!strcasecmp(name
, "panic")) return ASL_LEVEL_EMERG
;
117 if (!strcasecmp(name
, "alert")) return ASL_LEVEL_ALERT
;
118 if (!strcasecmp(name
, "crit")) return ASL_LEVEL_CRIT
;
119 if (!strcasecmp(name
, "err")) return ASL_LEVEL_ERR
;
120 if (!strcasecmp(name
, "error")) return ASL_LEVEL_ERR
;
121 if (!strcasecmp(name
, "warn")) return ASL_LEVEL_WARNING
;
122 if (!strcasecmp(name
, "warning")) return ASL_LEVEL_WARNING
;
123 if (!strcasecmp(name
, "notice")) return ASL_LEVEL_NOTICE
;
124 if (!strcasecmp(name
, "info")) return ASL_LEVEL_INFO
;
125 if (!strcasecmp(name
, "debug")) return ASL_LEVEL_DEBUG
;
126 if (!strcmp(name
, "*")) return ASL_LEVEL_DEBUG
;
129 if (!strcasecmp(name
, "none")) return -2;
135 _syslog_dst_open(struct config_rule
*r
)
139 struct addrinfo hints
, *gai
, *ai
;
141 if (r
== NULL
) return -1;
142 if (r
->fd
!= -1) return 0;
144 if (r
->dst
[0] == '/')
146 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
149 asldebug("%s: open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
153 r
->type
= DST_TYPE_FILE
;
154 if (!strcmp(r
->dst
, _PATH_CONSOLE
)) r
->type
= DST_TYPE_CONS
;
159 if (r
->dst
[0] == '!')
161 r
->type
= DST_TYPE_NOTE
;
166 if (r
->dst
[0] == '@')
168 node
= strdup(r
->dst
+ 1);
169 if (node
== NULL
) return -1;
172 serv
= strrchr(node
, ':');
173 if (serv
!= NULL
) *serv
++ = '\0';
174 else serv
= "syslog";
176 memset(&hints
, 0, sizeof(hints
));
177 hints
.ai_family
= PF_UNSPEC
;
178 hints
.ai_socktype
= SOCK_DGRAM
;
179 i
= getaddrinfo(node
, serv
, &hints
, &gai
);
183 asldebug("%s: getaddrinfo failed for node %s service %s: (%s)\n", MY_ID
, node
, serv
, gai_strerror(i
));
187 for (ai
= gai
; ai
!= NULL
; ai
= ai
->ai_next
)
189 r
->fd
= socket(ai
->ai_family
, ai
->ai_socktype
, ai
->ai_protocol
);
190 if (r
->fd
< 0) continue;
192 r
->addr
= (struct sockaddr
*)malloc(ai
->ai_addrlen
);
193 if (r
->addr
== NULL
) return -1;
195 memcpy(r
->addr
, ai
->ai_addr
, ai
->ai_addrlen
);
204 asldebug("%s: connection failed for %s\n", MY_ID
, (r
->dst
) + 1);
210 if (fcntl(r
->fd
, F_SETFL
, O_NONBLOCK
) < 0)
214 asldebug("%s: couldn't set O_NONBLOCK for fd %d: %s\n", MY_ID
, r
->fd
, strerror(errno
));
220 r
->type
= DST_TYPE_SOCK
;
225 if (strcmp(r
->dst
, "*") == 0)
227 r
->type
= DST_TYPE_WALL
;
232 /* Can't deal with dst! */
233 asldebug("%s: unsupported / unknown output name: %s\n", MY_ID
, r
->dst
);
238 _syslog_dst_close(struct config_rule
*r
)
240 if (r
== NULL
) return;
253 if (r
->fd
>= 0) close(r
->fd
);
260 if (r
->fd
>= 0) close(r
->fd
);
277 _clean_facility_name(char *s
)
282 if (s
== NULL
) return NULL
;
284 if (len
== 0) return NULL
;
288 if ((*s
== '\'') || (*s
== '"'))
292 if (p
[len
- 1] == *s
) len
--;
295 out
= calloc(1, len
+ 1);
296 if (out
== NULL
) return NULL
;
305 char **semi
, **comma
;
306 int i
, j
, n
, lasts
, lastc
, pri
;
307 struct config_rule
*out
;
309 if (s
== NULL
) return -1;
310 while ((*s
== ' ') || (*s
== '\t')) s
++;
311 if (*s
== '#') return -1;
313 semi
= explode(s
, "; \t");
315 if (semi
== NULL
) return -1;
316 out
= (struct config_rule
*)calloc(1, sizeof(struct config_rule
));
317 if (out
== NULL
) return -1;
322 for (i
= 0; semi
[i
] != NULL
; i
++)
324 if (semi
[i
][0] == '\0') continue;
329 out
->dst
= strdup(semi
[lasts
]);
330 if (out
->dst
== NULL
) return -1;
332 for (i
= 0; i
< lasts
; i
++)
334 if (semi
[i
][0] == '\0') continue;
335 comma
= explode(semi
[i
], ",.");
337 for (j
= 0; comma
[j
] != NULL
; j
++)
339 if (comma
[j
][0] == '\0') continue;
343 for (j
= 0; j
< lastc
; j
++)
345 if (comma
[j
][0] == '\0') continue;
346 pri
= _level_for_name(comma
[lastc
]);
347 if (pri
== -1) continue;
351 out
->facility
= (char **)calloc(1, sizeof(char *));
352 out
->pri
= (int *)calloc(1, sizeof(int));
356 out
->facility
= (char **)reallocf(out
->facility
, (out
->count
+ 1) * sizeof(char *));
357 out
->pri
= (int *)reallocf(out
->pri
, (out
->count
+ 1) * sizeof(int));
360 if (out
->facility
== NULL
) return -1;
361 if (out
->pri
== NULL
) return -1;
363 out
->facility
[out
->count
] = _clean_facility_name(comma
[j
]);
364 if (out
->facility
[out
->count
] == NULL
) return -1;
366 out
->pri
[out
->count
] = pri
;
375 TAILQ_INSERT_TAIL(&bsd_out_rule
, out
, entries
);
381 bsd_log_string(const char *msg
)
383 uint32_t i
, len
, outlen
;
387 if (msg
== NULL
) return NULL
;
390 while ((len
> 0) && (msg
[len
- 1] == '\n')) len
--;
392 if (len
== 0) return NULL
;
395 for (i
= 0; i
< len
; i
++)
398 if (isascii(c
) && iscntrl(c
) && (c
!= '\t')) outlen
++;
401 out
= malloc(outlen
);
402 if (out
== NULL
) return NULL
;
406 for (i
= 0; i
< len
; i
++)
410 if (isascii(c
) && iscntrl(c
))
439 _syslog_send_repeat_msg(struct config_rule
*r
)
445 if (r
== NULL
) return -1;
446 if (r
->type
!= DST_TYPE_FILE
) return 0;
447 if (r
->last_count
== 0) return 0;
451 memset(vt
, 0, sizeof(vt
));
456 asprintf(&msg
, "%s: --- last message repeated %u time%s ---\n", vt
+ 4, r
->last_count
, (r
->last_count
== 1) ? "" : "s");
457 if (msg
== NULL
) return -1;
460 status
= write(r
->fd
, msg
, len
);
461 if ((status
< 0) || (status
< len
))
463 asldebug("%s: error writing repeat message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
465 /* Try re-opening the file (once) and write again */
467 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
470 asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
475 status
= write(r
->fd
, msg
, len
);
476 if ((status
< 0) || (status
< len
))
478 asldebug("%s: error re-writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
489 _syslog_send(asl_msg_t
*msg
, struct config_rule
*r
, char **out
, char **fwd
, time_t now
)
491 char vt
[16], tstr
[32], *so
, *sf
, *outmsg
;
492 const char *vtime
, *vhost
, *vident
, *vpid
, *vmsg
, *vlevel
, *vfacility
, *vrefproc
, *vrefpid
;
495 int pf
, fc
, status
, is_dup
, do_write
;
499 if (out
== NULL
) return -1;
500 if (fwd
== NULL
) return -1;
501 if (r
== NULL
) return -1;
503 if (r
->type
== DST_TYPE_NOTE
)
505 notify_post(r
->dst
+1);
512 /* Build output string if it hasn't been built by a previous rule-match */
516 vtime
= asl_get(msg
, ASL_KEY_TIME
);
519 /* aslmsg_verify converts time to seconds, but use current time if something went sour */
521 if (tick
== 0) tick
= now
;
524 memset(tstr
, 0, sizeof(tstr
));
525 ctime_r(&tick
, tstr
);
526 memcpy(vt
, tstr
+4, 15);
529 vhost
= asl_get(msg
, ASL_KEY_HOST
);
530 if (vhost
== NULL
) vhost
= "localhost";
532 vident
= asl_get(msg
, ASL_KEY_SENDER
);
533 if ((vident
!= NULL
) && (!strcmp(vident
, "Unknown"))) vident
= NULL
;
535 vpid
= asl_get(msg
, ASL_KEY_PID
);
536 if ((vpid
!= NULL
) && (!strcmp(vpid
, "-1"))) vpid
= NULL
;
538 if ((vpid
!= NULL
) && (vident
== NULL
)) vident
= "Unknown";
540 vrefproc
= asl_get(msg
, ASL_KEY_REF_PROC
);
541 vrefpid
= asl_get(msg
, ASL_KEY_REF_PID
);
543 vmsg
= asl_get(msg
, ASL_KEY_MSG
);
544 if (vmsg
!= NULL
) outmsg
= bsd_log_string(vmsg
);
548 n
+= (strlen(vt
) + 1);
551 if (vhost
!= NULL
) n
+= (strlen(vhost
) + 1);
554 if (vident
!= NULL
) n
+= strlen(vident
);
557 if (vpid
!= NULL
) n
+= (strlen(vpid
) + 2);
560 if ((vrefproc
!= NULL
) || (vrefpid
!= NULL
)) n
+= 2;
563 if (vrefproc
!= NULL
) n
+= strlen(vrefproc
);
566 if (vrefpid
!= NULL
) n
+= (strlen(vrefpid
) + 2);
569 if ((vrefproc
!= NULL
) || (vrefpid
!= NULL
)) n
+= 1;
575 if (outmsg
!= NULL
) n
+= strlen(outmsg
);
577 if (n
== 0) return -1;
583 if (so
== NULL
) return -1;
605 if ((vrefproc
!= NULL
) || (vrefpid
!= NULL
))
609 if (vrefproc
!= NULL
) strcat(so
, vrefproc
);
634 /* check if message is a duplicate of the last message, and inside the dup time window */
636 if ((global
.bsd_max_dup_time
> 0) && (*out
!= NULL
) && (r
->last_msg
!= NULL
))
638 msg_hash
= asl_core_string_hash(*out
+ 16, strlen(*out
+ 16));
639 if ((r
->last_hash
== msg_hash
) && (!strcmp(r
->last_msg
, *out
+ 16)))
641 if ((now
- r
->last_time
) < global
.bsd_max_dup_time
) is_dup
= 1;
645 if ((*fwd
== NULL
) && (r
->type
== DST_TYPE_SOCK
))
648 vlevel
= asl_get(msg
, ASL_KEY_LEVEL
);
649 if (vlevel
!= NULL
) pf
= atoi(vlevel
);
651 fc
= asl_syslog_faciliy_name_to_num(asl_get(msg
, ASL_KEY_FACILITY
));
652 if (fc
> 0) pf
|= fc
;
655 asprintf(&sf
, "<%d>%s", pf
, *out
);
656 if (sf
== NULL
) return -1;
662 if (r
->type
== DST_TYPE_SOCK
) outlen
= strlen(*fwd
);
663 else outlen
= strlen(*out
);
667 if ((r
->type
== DST_TYPE_FILE
) || (r
->type
== DST_TYPE_CONS
))
670 * If current message is NOT a duplicate and r->last_count > 0
671 * we need to write a "last message was repeated N times" log entry
673 if ((r
->type
== DST_TYPE_FILE
) && (is_dup
== 0) && (r
->last_count
> 0)) _syslog_send_repeat_msg(r
);
678 * Special case for kernel messages.
679 * Don't write kernel messages to /dev/console.
680 * The kernel printf routine already sends them to /dev/console
681 * so writing them here would cause duplicates.
683 vfacility
= asl_get(msg
, ASL_KEY_FACILITY
);
684 if ((vfacility
!= NULL
) && (!strcmp(vfacility
, FACILITY_KERNEL
)) && (r
->type
== DST_TYPE_CONS
)) do_write
= 0;
685 if ((do_write
== 1) && (r
->type
== DST_TYPE_FILE
) && (is_dup
== 1)) do_write
= 0;
687 if (do_write
== 0) status
= outlen
;
688 else status
= write(r
->fd
, *out
, outlen
);
690 if ((status
< 0) || (status
< outlen
))
692 asldebug("%s: error writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
694 /* Try re-opening the file (once) and write again */
696 r
->fd
= open(r
->dst
, O_WRONLY
| O_APPEND
| O_CREAT
| O_NOCTTY
, 0644);
699 asldebug("%s: re-open failed for file: %s (%s)\n", MY_ID
, r
->dst
, strerror(errno
));
703 status
= write(r
->fd
, *out
, outlen
);
704 if ((status
< 0) || (status
< outlen
))
706 asldebug("%s: error re-writing message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
710 else if ((r
->type
== DST_TYPE_SOCK
) && (r
->addr
!= NULL
))
712 status
= sendto(r
->fd
, *fwd
, outlen
, 0, r
->addr
, r
->addr
->sa_len
);
713 if (status
< 0) asldebug("%s: error sending message (%s): %s\n", MY_ID
, r
->dst
, strerror(errno
));
715 else if (r
->type
== DST_TYPE_WALL
)
717 pw
= popen(_PATH_WALL
, "w");
720 asldebug("%s: error sending wall message: %s\n", MY_ID
, strerror(errno
));
724 fprintf(pw
, "%s", *out
);
734 if (r
->last_msg
!= NULL
) free(r
->last_msg
);
737 if (*out
!= NULL
) r
->last_msg
= strdup(*out
+ 16);
739 r
->last_hash
= msg_hash
;
748 _syslog_rule_match(asl_msg_t
*msg
, struct config_rule
*r
)
750 uint32_t i
, test
, f
, pri
;
753 if (msg
== NULL
) return 0;
754 if (r
== NULL
) return 0;
755 if (r
->count
== 0) return 0;
759 for (i
= 0; i
< r
->count
; i
++)
761 if (r
->pri
[i
] == -1) continue;
763 if ((test
== 1) && (r
->pri
[i
] >= 0)) continue;
764 if ((test
== 0) && (r
->pri
[i
] == -2)) continue;
767 if (strcmp(r
->facility
[i
], "*") == 0) f
= 1;
770 val
= asl_get(msg
, ASL_KEY_FACILITY
);
771 if ((val
!= NULL
) && (strcasecmp(r
->facility
[i
], val
) == 0)) f
= 1;
774 if (f
== 0) continue;
776 /* Turn off matching facility with priority "none" */
783 val
= asl_get(msg
, ASL_KEY_LEVEL
);
784 if (val
== NULL
) continue;
787 if (pri
< 0) continue;
789 if (pri
<= r
->pri
[i
]) test
= 1;
796 bsd_out_sendmsg(asl_msg_t
*msg
, const char *outid
)
798 struct config_rule
*r
;
803 if (reset
!= RESET_NONE
) _do_reset();
805 if (msg
== NULL
) return -1;
811 global
.bsd_flush_time
= 0;
813 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
815 if (_syslog_rule_match(msg
, r
) == 1) _syslog_send(msg
, r
, &out
, &fwd
, tick
);
816 if ((r
->type
== DST_TYPE_FILE
) && (r
->last_count
> 0))
818 delta
= tick
- r
->last_time
;
819 if (delta
< global
.bsd_max_dup_time
)
821 delta
= global
.bsd_max_dup_time
- delta
;
822 if (global
.bsd_flush_time
== 0) global
.bsd_flush_time
= delta
;
823 else if (delta
< global
.bsd_flush_time
) global
.bsd_flush_time
= delta
;
828 if (out
!= NULL
) free(out
);
829 if (fwd
!= NULL
) free(fwd
);
835 bsd_close_idle_files(time_t now
)
837 struct config_rule
*r
;
840 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
842 /* only applies to files */
843 if (r
->type
!= DST_TYPE_FILE
) continue;
846 * If the last message repeat count is non-zero, a bsd_flush_duplicates()
847 * call will occur within 30 seconds. Don't bother closing the file.
849 if (r
->last_count
> 0) continue;
851 delta
= now
- r
->last_time
;
852 if (delta
> CLOSE_ON_IDLE_SEC
) _syslog_dst_close(r
);
857 bsd_flush_duplicates(time_t now
)
859 struct config_rule
*r
;
862 global
.bsd_flush_time
= 0;
864 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
866 if (r
->type
!= DST_TYPE_FILE
) continue;
868 if (r
->last_count
> 0)
870 delta
= now
- r
->last_time
;
871 if (delta
< global
.bsd_max_dup_time
)
873 delta
= global
.bsd_max_dup_time
- delta
;
874 if (global
.bsd_flush_time
== 0) global
.bsd_flush_time
= delta
;
875 else if (delta
< global
.bsd_flush_time
) global
.bsd_flush_time
= delta
;
880 _syslog_send_repeat_msg(r
);
889 _parse_config_file(const char *confname
)
894 cf
= fopen(confname
, "r");
895 if (cf
== NULL
) return 1;
897 while (NULL
!= (line
= get_line_from_file(cf
)))
911 asldebug("%s: init\n", MY_ID
);
913 TAILQ_INIT(&bsd_out_rule
);
915 query
= asl_new(ASL_TYPE_QUERY
);
916 aslevent_addmatch(query
, MY_ID
);
917 aslevent_addoutput(bsd_out_sendmsg
, MY_ID
);
919 _parse_config_file(_PATH_SYSLOG_CONF
);
926 reset
= global
.reset
;
933 struct config_rule
*r
, *n
;
937 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= n
)
939 n
= r
->entries
.tqe_next
;
941 if (r
->dst
!= NULL
) free(r
->dst
);
942 if (r
->fd
> 0) close(r
->fd
);
943 if (r
->addr
!= NULL
) free(r
->addr
);
944 if (r
->last_msg
!= NULL
) free(r
->last_msg
);
945 if (r
->facility
!= NULL
)
947 for (i
= 0; i
< r
->count
; i
++)
949 if (r
->facility
[i
] != NULL
) free(r
->facility
[i
]);
953 if (r
->pri
!= NULL
) free(r
->pri
);
955 TAILQ_REMOVE(&bsd_out_rule
, r
, entries
);
963 bsd_out_network_reset(void)
965 struct config_rule
*r
;
967 for (r
= bsd_out_rule
.tqh_first
; r
!= NULL
; r
= r
->entries
.tqe_next
)
969 if (r
->type
== DST_TYPE_SOCK
)