]> git.saurik.com Git - apple/syslog.git/blobdiff - syslogd.tproj/daemon.c
syslog-100.2.tar.gz
[apple/syslog.git] / syslogd.tproj / daemon.c
index 775a0d79f59972eaac508e3d19ecb23358d438d2..ca428fcc0e8a387273c5e21610f7349cbf09fd76 100644 (file)
@@ -1,23 +1,22 @@
 /*
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2004-2009 Apple Inc. All rights reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  * 
- * "Portions Copyright (c) 2004 Apple Computer, Inc.  All Rights
- * Reserved.  This file contains Original Code and/or Modifications of
- * Original Code as defined in and that are subject to the Apple Public
- * Source License Version 1.0 (the 'License').  You may not use this file
- * except in compliance with the License.  Please obtain a copy of the
- * License at http://www.apple.com/publicsource and read it before using
- * this file.
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
  * 
  * The Original Code and all software distributed under the License are
  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
- * License for the specific language governing rights and limitations
- * under the License."
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
  * 
  * @APPLE_LICENSE_HEADER_END@
  */
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
-#include <sys/queue.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #define SYSLOG_NAMES
 #include <syslog.h>
+#include <sys/fslog.h>
+#include <vproc.h>
+#include <pthread.h>
+#include <vproc_priv.h>
+#include <mach/mach.h>
+#include <assert.h>
+#include <libkern/OSAtomic.h>
 #include "daemon.h"
 
+#define LIST_SIZE_DELTA 256
+
 #define streq(A,B) (strcmp(A,B)==0)
+#define forever for(;;)
+#define IndexNull ((uint32_t)-1)
+
+#define ASL_MSG_TYPE_MASK 0x0000000f
+#define ASL_TYPE_ERROR 2
+
+#define ASL_KEY_FACILITY "Facility"
+
+#define FACILITY_USER "user"
+#define FACILITY_CONSOLE "com.apple.console"
+#define SYSTEM_RESERVED "com.apple.system"
+#define SYSTEM_RESERVED_LEN 16
 
+#define VERIFY_STATUS_OK 0
+#define VERIFY_STATUS_INVALID_MESSAGE 1
+#define VERIFY_STATUS_EXCEEDED_QUOTA 2
+
+extern void disaster_message(asl_msg_t *m);
 static char myname[MAXHOSTNAMELEN + 1] = {0};
-static int gotname = 0;
-
-struct aslevent
-{
-       int fd;
-       unsigned char read:1; 
-       unsigned char write:1; 
-       unsigned char except:1;
-       aslreadfn readfn;
-       aslwritefn writefn;
-       aslexceptfn exceptfn;
-       char *sender;
-       uid_t uid;
-       gid_t gid;
-       TAILQ_ENTRY(aslevent) entries;
+
+static OSSpinLock count_lock = 0;
+
+#ifndef CONFIG_IPHONE
+static vproc_transaction_t vproc_trans = {0};
+#endif
+
+#define QUOTA_TABLE_SIZE 8192
+#define QUOTA_TABLE_SLOTS 8
+
+#define QUOTA_EXCEEDED_MESSAGE "*** process %d exceeded %d log message per second limit  -  remaining messages this second discarded ***"
+#define QUOTA_EXCEEDED_LEVEL "3"
+
+static time_t quota_table_time = 0;
+static pid_t quota_table_pid[QUOTA_TABLE_SIZE];
+static int32_t quota_table_quota[QUOTA_TABLE_SIZE];
+
+static const char *kern_notify_key[] = 
+{
+       "com.apple.system.log.kernel.emergency",
+       "com.apple.system.log.kernel.alert",
+       "com.apple.system.log.kernel.critical",
+       "com.apple.system.log.kernel.error",
+       "com.apple.system.log.kernel.warning",
+       "com.apple.system.log.kernel.notice",
+       "com.apple.system.log.kernel.info",
+       "com.apple.system.log.kernel.debug"
 };
 
 struct asloutput
@@ -75,6 +111,239 @@ TAILQ_HEAD(ae, aslevent) Eventq;
 TAILQ_HEAD(ao, asloutput) Outq;
 TAILQ_HEAD(am, aslmatch) Matchq;
 
+static char **
+_insertString(char *s, char **l, uint32_t x)
+{
+       int i, len;
+
+       if (s == NULL) return l;
+       if (l == NULL) 
+       {
+               l = (char **)malloc(2 * sizeof(char *));
+               if (l == NULL) return NULL;
+
+               l[0] = strdup(s);
+               if (l[0] == NULL)
+               {
+                       free(l);
+                       return NULL;
+               }
+
+               l[1] = NULL;
+               return l;
+       }
+
+       for (i = 0; l[i] != NULL; i++);
+       len = i + 1; /* count the NULL on the end of the list too! */
+
+       l = (char **)reallocf(l, (len + 1) * sizeof(char *));
+       if (l == NULL) return NULL;
+
+       if ((x >= (len - 1)) || (x == IndexNull))
+       {
+               l[len - 1] = strdup(s);
+               if (l[len - 1] == NULL)
+               {
+                       free(l);
+                       return NULL;
+               }
+
+               l[len] = NULL;
+               return l;
+       }
+
+       for (i = len; i > x; i--) l[i] = l[i - 1];
+       l[x] = strdup(s);
+       if (l[x] == NULL) return NULL;
+
+       return l;
+}
+
+char **
+explode(const char *s, const char *delim)
+{
+       char **l = NULL;
+       const char *p;
+       char *t, quote;
+       int i, n;
+
+       if (s == NULL) return NULL;
+
+       quote = '\0';
+
+       p = s;
+       while (p[0] != '\0')
+       {
+               /* scan forward */
+               for (i = 0; p[i] != '\0'; i++)
+               {
+                       if (quote == '\0')
+                       {
+                               /* not inside a quoted string: check for delimiters and quotes */
+                               if (strchr(delim, p[i]) != NULL) break;
+                               else if (p[i] == '\'') quote = p[i];
+                               else if (p[i] == '"') quote = p[i];
+                       }
+                       else
+                       {
+                               /* inside a quoted string - look for matching quote */
+                               if (p[i] == quote) quote = '\0';
+                       }
+               }
+
+               n = i;
+               t = malloc(n + 1);
+               if (t == NULL) return NULL;
+
+               for (i = 0; i < n; i++) t[i] = p[i];
+               t[n] = '\0';
+               l = _insertString(t, l, IndexNull);
+               free(t);
+               t = NULL;
+               if (p[i] == '\0') return l;
+               if (p[i + 1] == '\0') l = _insertString("", l, IndexNull);
+               p = p + i + 1;
+       }
+
+       return l;
+}
+
+void
+freeList(char **l)
+{
+       int i;
+
+       if (l == NULL) return;
+       for (i = 0; l[i] != NULL; i++) free(l[i]);
+       free(l);
+}
+
+/*
+ * Quotas are maintained using a very fast fixed-size table.
+ * We hash into the pid table (quota_table_pid) using the last 10
+ * bits of the pid, so the table has 1024 "buckets".  The table is
+ * actually just an array with 8 entry slots (for collisions) per bucket.
+ * If there are more than 8 pids that hash to the same bucket, we
+ * re-use the one with the lowest message usage (highest remaining
+ * quota).  This can lead to "generosity: if there are nine or more
+ * pids with the same last 10 bits all logging like crazy, we may
+ * end up allowing some of them to log more than their quota. 
+ * That would be a remarkably rare occurrence.
+ */
+static uint32_t
+quota_check(pid_t pid, time_t now, asl_msg_t *msg)
+{
+       int i, x, maxx, max;
+       char *str;
+
+       if (msg == NULL) return VERIFY_STATUS_INVALID_MESSAGE;
+       if (global.mps_limit == 0) return VERIFY_STATUS_OK;
+
+       OSSpinLockLock(&global.lock);
+
+       if (quota_table_time != now)
+       {
+               memset(quota_table_pid, 0, sizeof(quota_table_pid));
+               quota_table_time = now;
+       }
+
+       /* hash is last 10 bits of the pid, shifted up 3 bits to allow 8 slots per bucket */
+       x = (pid & 0x000003ff) << 3;
+       maxx = x;
+       max = quota_table_quota[x];
+
+       for (i = 0; i < QUOTA_TABLE_SLOTS; i++)
+       {
+               if (quota_table_pid[x] == 0)
+               {
+                       quota_table_pid[x] = pid;
+                       quota_table_quota[x] = global.mps_limit;
+
+                       OSSpinLockUnlock(&global.lock);
+                       return VERIFY_STATUS_OK;
+               }
+
+               if (quota_table_pid[x] == pid)
+               {
+                       quota_table_quota[x] = quota_table_quota[x] - 1;
+
+                       if (quota_table_quota[x] == 0)
+                       {
+                               quota_table_quota[x] = -1;
+
+                               str = NULL;
+                               asprintf(&str, QUOTA_EXCEEDED_MESSAGE, (int)pid, global.mps_limit);
+                               if (str != NULL)
+                               {
+                                       asl_set(msg, ASL_KEY_MSG, str);
+                                       free(str);
+                                       asl_set(msg, ASL_KEY_LEVEL, QUOTA_EXCEEDED_LEVEL);
+                               }
+
+                               OSSpinLockUnlock(&global.lock);
+                               return VERIFY_STATUS_OK;
+                       }
+
+                       if (quota_table_quota[x] < 0)
+                       {
+                               OSSpinLockUnlock(&global.lock);
+                               return VERIFY_STATUS_EXCEEDED_QUOTA;
+                       }
+
+                       OSSpinLockUnlock(&global.lock);
+                       return VERIFY_STATUS_OK;
+               }
+
+               if (quota_table_quota[x] > max)
+               {
+                       maxx = x;
+                       max = quota_table_quota[x];
+               }
+
+               x += 1;
+       }
+
+       /* can't find the pid and no slots were available - reuse slot with highest remaining quota */
+       quota_table_pid[maxx] = pid;
+       quota_table_quota[maxx] = global.mps_limit;
+
+       OSSpinLockUnlock(&global.lock);
+       return VERIFY_STATUS_OK;
+}
+
+int
+asl_check_option(asl_msg_t *msg, const char *opt)
+{
+       const char *p;
+       uint32_t len;
+
+       if (msg == NULL) return 0;
+       if (opt == NULL) return 0;
+
+       len = strlen(opt);
+       if (len == 0) return 0;
+
+       p = asl_get(msg, ASL_KEY_OPTION);
+       if (p == NULL) return 0;
+
+       while (*p != '\0')
+       {
+               while ((*p == ' ') || (*p == '\t') || (*p == ',')) p++;
+               if (*p == '\0') return 0;
+
+               if (strncasecmp(p, opt, len) == 0)
+               {
+                       p += len;
+                       if ((*p == ' ') || (*p == '\t') || (*p == ',') || (*p == '\0')) return 1;
+               }
+
+               while ((*p != ' ') && (*p != '\t') && (*p != ',') && (*p != '\0')) p++;
+       }
+
+       return 0;
+}
+
 int
 aslevent_init(void)
 {
@@ -112,6 +381,7 @@ aslevent_addmatch(asl_msg_t *query, char *outid)
 
        tmp = calloc(1, sizeof(struct aslmatch));
        if (tmp == NULL) return -1;
+
        tmp->query = query;
        tmp->outid = outid;
        TAILQ_INSERT_TAIL(&Matchq, tmp, entries);
@@ -120,7 +390,7 @@ aslevent_addmatch(asl_msg_t *query, char *outid)
 }
 
 void
-aslevent_match(asl_msg_t *msg)
+asl_message_match_and_log(asl_msg_t *msg)
 {
        struct aslmatch *i;
 
@@ -138,21 +408,20 @@ aslevent_match(asl_msg_t *msg)
 int
 aslevent_removefd(int fd)
 {
-       struct aslevent *i;
-       int found = -1;
+       struct aslevent *e, *next;
 
-       for (i = Eventq.tqh_first; i != NULL; i = i->entries.tqe_next)
+       e = Eventq.tqh_first;
+
+       while (e != NULL)
        {
-               if (fd == i->fd)
+               next = e->entries.tqe_next;
+               if (fd == e->fd)
                {
-                       asldebug("removing %d\n", i->fd);
-                       TAILQ_REMOVE(&Eventq, i, entries);
-                       found = 0;
-                       if (i->sender != NULL) free(i->sender);
-                       free(i);
-                       i = NULL;
+                       e->fd = -1;
                        return 0;
                }
+
+               e = next;
        }
 
        return -1;
@@ -163,31 +432,59 @@ whatsmyhostname()
 {
        char *dot;
 
-       if (gotname != 0) return (const char *)myname;
-
        if (gethostname(myname, MAXHOSTNAMELEN) < 0)
        {
                memset(myname, 0, sizeof(myname));
                return "localhost";
        }
 
-       if (strcmp(myname, "localhost")) gotname = 1;
-
        dot = strchr(myname, '.');
        if (dot != NULL) *dot = '\0';
 
        return (const char *)myname;
 }
 
+void
+asl_client_count_increment()
+{
+       OSSpinLockLock(&count_lock);
+
+#ifndef CONFIG_IPHONE
+       if (global.client_count == 0) vproc_trans = vproc_transaction_begin(NULL);
+#endif
+       global.client_count++;
+#ifdef DEBUG
+       asldebug("global.client_count++ (%d)\n", global.client_count);
+#endif
+
+       OSSpinLockUnlock(&count_lock);
+}
+
+void
+asl_client_count_decrement()
+{
+       OSSpinLockLock(&count_lock);
+
+       if (global.client_count > 0) global.client_count--;
+#ifndef CONFIG_IPHONE
+       if (global.client_count == 0) vproc_transaction_end(NULL, vproc_trans);
+#endif
+#ifdef DEBUG
+       asldebug("global.client_count-- (%d)\n", global.client_count);
+#endif
+
+       OSSpinLockUnlock(&count_lock);
+}
+
 int
-aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptfn)
+aslevent_addfd(int source, int fd, uint32_t flags, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptfn)
 {
        struct aslevent *e;
-       int found = 0;
+       int found = 0, status;
 #ifdef LOCAL_PEERCRED
        struct xucred cr;
 #endif
-       int len;
+       socklen_t len;
        uid_t u;
        gid_t g;
        struct sockaddr_storage ss;
@@ -198,34 +495,45 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf
        sender = NULL;
 
        memset(&ss, 0, sizeof(struct sockaddr_storage));
+       memset(str, 0, sizeof(str));
 
        len = sizeof(struct sockaddr_storage);
 
-       if (getpeername(fd, (struct sockaddr *)&ss, &len) == 0)
+       if (flags & ADDFD_FLAGS_LOCAL)
        {
-               if (len == 0)
-               {
-                       /* UNIX Domain socket */
-                       snprintf(str, sizeof(str), whatsmyhostname());
-                       sender = str;
-               }
-               else
-               {
-                       if (inet_ntop(ss.ss_family, (struct sockaddr *)&ss, str, 256) == 0) sender = str;
-               }
-       }
+               snprintf(str, sizeof(str), "localhost");
+               sender = str;
 
 #ifdef LOCAL_PEERCRED
-       len = sizeof(cr);
+               len = sizeof(cr);
 
-       if (getsockopt(fd, LOCAL_PEERCRED, 1, &cr, &len) == 0)
+               status = getsockopt(fd, LOCAL_PEERCRED, 1, &cr, &len);
+               if (status == 0)
+               {
+                       u = cr.cr_uid;
+                       g = cr.cr_gid;
+               }
+#endif
+       }
+       else
        {
-               u = cr.cr_uid;
-               g = cr.cr_gid;
+               status = getpeername(fd, (struct sockaddr *)&ss, &len);
+               if (status == 0)
+               {
+                       if (len == 0)
+                       {
+                               /* UNIX Domain socket */
+                               snprintf(str, sizeof(str), "localhost");
+                               sender = str;
+                       }
+                       else
+                       {
+                               if (inet_ntop(ss.ss_family, (struct sockaddr *)&ss, str, 256) == NULL) sender = str;
+                       }
+               }
        }
-#endif
 
-       asldebug("fd %d   UID %d   GID %d   Sender %s\n", fd, u, g, (sender == NULL) ? "NULL" : sender );
+       asldebug("source %d fd %d   flags 0x%08x UID %d   GID %d   Sender %s\n", source, fd, flags, u, g, (sender == NULL) ? "NULL" : sender );
 
        for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next)
        {
@@ -236,7 +544,12 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf
                        e->exceptfn = exceptfn;
                        if (e->sender != NULL) free(e->sender);
                        e->sender = NULL;
-                       if (sender != NULL) e->sender = strdup(sender);
+                       if (sender != NULL)
+                       {
+                               e->sender = strdup(sender);
+                               if (e->sender == NULL) return -1;
+                       }
+
                        e->uid = u;
                        e->gid = g;
                        found = 1;
@@ -248,12 +561,18 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf
        e = calloc(1, sizeof(struct aslevent));
        if (e == NULL) return -1;
 
+       e->source = source;
        e->fd = fd;
        e->readfn = readfn;
        e->writefn = writefn;
        e->exceptfn = exceptfn;
        e->sender = NULL;
-       if (sender != NULL) e->sender = strdup(sender);
+       if (sender != NULL)
+       {
+               e->sender = strdup(sender);
+               if (e->sender == NULL) return -1;
+       }
+
        e->uid = u;
        e->gid = g;
 
@@ -262,86 +581,267 @@ aslevent_addfd(int fd, aslreadfn readfn, aslwritefn writefn, aslexceptfn exceptf
        return 0;
 }
 
-int
-aslmsg_verify(struct aslevent *e, asl_msg_t *msg)
+/*
+ * Checks message content and sets attributes as required
+ *
+ * SOURCE_INTERNAL log messages sent by syslogd itself
+ * SOURCE_ASL_SOCKET legacy asl(3) TCP socket
+ * SOURCE_BSD_SOCKET legacy syslog(3) UDP socket
+ * SOURCE_UDP_SOCKET from the network
+ * SOURCE_KERN from the kernel
+ * SOURCE_ASL_MESSAGE mach messages sent from Libc by asl(3) and syslog(3)
+ * SOURCE_LAUNCHD forwarded from launchd
+ */
+
+static uint32_t
+aslmsg_verify(uint32_t source, struct aslevent *e, asl_msg_t *msg, int32_t *kern_post_level)
 {
-       const char *val;
-       char buf[32], *timestr;
-       time_t tick;
-       struct tm gtime;
+       const char *val, *fac;
+       char buf[64];
+       time_t tick, now;
+       uid_t uid;
+       uint32_t status, level, fnum;
+       pid_t pid;
+
+       if (msg == NULL) return VERIFY_STATUS_INVALID_MESSAGE;
 
-       if (msg == NULL) return -1;
+       if (kern_post_level != NULL) *kern_post_level = -1;
 
        /* Time */
+       now = time(NULL);
+
        tick = 0;
        val = asl_get(msg, ASL_KEY_TIME);
        if (val != NULL) tick = asl_parse_time(val);
 
-       if (tick == 0) tick = time(NULL);
-       memset(&gtime, 0, sizeof(struct tm));
-       gmtime_r(&tick, &gtime);
+       /* Set time to now if it is unset or from the future (not allowed!) */
+       if ((tick == 0) || (tick > now)) tick = now;
 
-       /* Canonical form: YYYY.MM.DD hh:mm:ss UTC */
-       asprintf(&timestr, "%d.%02d.%02d %02d:%02d:%02d UTC", gtime.tm_year + 1900, gtime.tm_mon + 1, gtime.tm_mday, gtime.tm_hour, gtime.tm_min, gtime.tm_sec);
-       if (timestr != NULL)
-       {
-               asl_set(msg, ASL_KEY_TIME, timestr);
-               free(timestr);
-       }
+       /* Canonical form: seconds since the epoch */
+       snprintf(buf, sizeof(buf) - 1, "%lu", tick);
+       asl_set(msg, ASL_KEY_TIME, buf);
 
        /* Host */
        if (e == NULL) asl_set(msg, ASL_KEY_HOST, whatsmyhostname());
-       else if (e->sender != NULL) asl_set(msg, ASL_KEY_HOST, e->sender);
-
-       /* Sender */
-       val = asl_get(msg, ASL_KEY_SENDER);
-       if (val == NULL) asl_set(msg, ASL_KEY_SENDER, "Unknown");
+       else if (e->sender != NULL)
+       {
+               if (!strcmp(e->sender, "localhost")) asl_set(msg, ASL_KEY_HOST, whatsmyhostname());
+               else asl_set(msg, ASL_KEY_HOST, e->sender);
+       }
 
        /* PID */
+       pid = 0;
+
        val = asl_get(msg, ASL_KEY_PID);
        if (val == NULL) asl_set(msg, ASL_KEY_PID, "0");
+       else pid = (pid_t)atoi(val);
+
+       /* if PID is 1 (launchd), use the refpid if there is one */
+       if (pid == 1)
+       {
+               val = asl_get(msg, ASL_KEY_REF_PID);
+               if (val != NULL) pid = (pid_t)atoi(val);
+       }
+
+       /* if quotas are enabled and pid > 1 (not kernel or launchd) check quota */
+       if ((global.mps_limit > 0) && (pid > 1))
+       {
+               status = quota_check(pid, now, msg);
+               if (status != VERIFY_STATUS_OK) return status;
+       }
 
        /* UID */
+       uid = -2;
        val = asl_get(msg, ASL_KEY_UID);
-       if (val == NULL)
+
+       switch (source)
        {
-               if (e == NULL) asl_set(msg, ASL_KEY_UID, "-2");
-               else if (e->uid == 99) asl_set(msg, ASL_KEY_UID, "-2");
-               else
+               case SOURCE_KERN:
+               case SOURCE_INTERNAL:
                {
-                       snprintf(buf, sizeof(buf), "%d", e->uid);
-                       asl_set(msg, ASL_KEY_UID, buf);
+                       /* we know the UID is 0 */
+                       uid = 0;
+                       asl_set(msg, ASL_KEY_UID, "0");
+                       break;
+               }
+               case SOURCE_ASL_SOCKET:
+               case SOURCE_ASL_MESSAGE:
+               case SOURCE_LAUNCHD:
+               {
+                       /* we trust the UID in the message */
+                       if (val != NULL) uid = atoi(val);
+                       break;
+               }
+               case SOURCE_BSD_SOCKET:
+               case SOURCE_UDP_SOCKET:
+               {
+                       if (val == NULL)
+                       {
+                               if (e == NULL) asl_set(msg, ASL_KEY_UID, "-2");
+                               else if (e->uid == 99) asl_set(msg, ASL_KEY_UID, "-2");
+                               else
+                               {
+                                       uid = e->uid;
+                                       snprintf(buf, sizeof(buf), "%d", e->uid);
+                                       asl_set(msg, ASL_KEY_UID, buf);
+                               }
+                       }
+                       else if ((e != NULL) && (e->uid != 99))
+                       {
+                               uid = e->uid;
+                               snprintf(buf, sizeof(buf), "%d", e->uid);
+                               asl_set(msg, ASL_KEY_UID, buf);
+                       }
+               }
+               default:
+               {
+                       asl_set(msg, ASL_KEY_UID, "-2");
                }
-       }
-       else if ((e != NULL) && (e->uid != 99))
-       {
-               snprintf(buf, sizeof(buf), "%d", e->uid);
-               asl_set(msg, ASL_KEY_UID, buf);
        }
 
        /* GID */
        val = asl_get(msg, ASL_KEY_GID);
+
+       switch (source)
+       {
+               case SOURCE_KERN:
+               case SOURCE_INTERNAL:
+               {
+                       /* we know the GID is 0 */
+                       asl_set(msg, ASL_KEY_GID, "0");
+                       break;
+               }
+               case SOURCE_ASL_SOCKET:
+               case SOURCE_ASL_MESSAGE:
+               case SOURCE_LAUNCHD:
+               {
+                       /* we trust the GID in the message */
+                       break;
+               }
+               case SOURCE_BSD_SOCKET:
+               case SOURCE_UDP_SOCKET:
+               {
+                       if (val == NULL)
+                       {
+                               if (e == NULL) asl_set(msg, ASL_KEY_GID, "-2");
+                               else if (e->gid == 99) asl_set(msg, ASL_KEY_GID, "-2");
+                               else
+                               {
+                                       snprintf(buf, sizeof(buf), "%d", e->gid);
+                                       asl_set(msg, ASL_KEY_GID, buf);
+                               }
+                       }
+                       else if ((e != NULL) && (e->gid != 99))
+                       {
+                               snprintf(buf, sizeof(buf), "%d", e->gid);
+                               asl_set(msg, ASL_KEY_GID, buf);
+                       }
+               }
+               default:
+               {
+                       asl_set(msg, ASL_KEY_GID, "-2");
+               }
+       }
+
+       /* Sender */
+       val = asl_get(msg, ASL_KEY_SENDER);
        if (val == NULL)
        {
-               if (e == NULL) asl_set(msg, ASL_KEY_GID, "-2");
-               else if (e->gid == 99) asl_set(msg, ASL_KEY_GID, "-2");
-               else
+               switch (source)
                {
-                       snprintf(buf, sizeof(buf), "%d", e->gid);
-                       asl_set(msg, ASL_KEY_GID, buf);
+                       case SOURCE_KERN:
+                       {
+                               asl_set(msg, ASL_KEY_SENDER, "kernel");
+                               break;
+                       }
+                       case SOURCE_INTERNAL:
+                       {
+                               asl_set(msg, ASL_KEY_SENDER, "syslogd");
+                               break;
+                       }
+                       default:
+                       {
+                               asl_set(msg, ASL_KEY_SENDER, "Unknown");
+                       }
                }
        }
-       else if ((e != NULL) && (e->gid != 99))
+       else if ((source != SOURCE_KERN) && (uid != 0) && (!strcmp(val, "kernel")))
        {
-               snprintf(buf, sizeof(buf), "%d", e->gid);
-               asl_set(msg, ASL_KEY_GID, buf);
+               /* allow UID 0 to send messages with "Sender kernel", but nobody else */
+               asl_set(msg, ASL_KEY_SENDER, "Unknown");
        }
 
        /* Level */
        val = asl_get(msg, ASL_KEY_LEVEL);
-       if (val == NULL) asl_set(msg, ASL_KEY_LEVEL, "7");
+       level = ASL_LEVEL_DEBUG;
+       if ((val != NULL) && (val[1] == '\0') && (val[0] >= '0') && (val[0] <= '7')) level = val[0] - '0';
+       snprintf(buf, sizeof(buf), "%d", level);
+       asl_set(msg, ASL_KEY_LEVEL, buf);
+
+       /* Facility */
+       fac = asl_get(msg, ASL_KEY_FACILITY);
+       if (fac == NULL)
+       {
+               if (source == SOURCE_KERN) fac = "kern";
+               else fac = "user";
+               asl_set(msg, ASL_KEY_FACILITY, fac);
+       }
+       else if (fac[0] == '#')
+       {
+               fnum = LOG_USER;
+               if ((fac[1] >= '0') && (fac[1] <= '9'))
+               {
+                       fnum = atoi(fac + 1) << 3;
+                       if ((fnum == 0) && (strcmp(fac + 1, "0"))) fnum = LOG_USER;
+               }
 
-       return 0;
+               fac = asl_syslog_faciliy_num_to_name(fnum);
+               asl_set(msg, ASL_KEY_FACILITY, fac);
+       }
+       else if (!strncmp(fac, SYSTEM_RESERVED, SYSTEM_RESERVED_LEN))
+       {
+               /* only UID 0 may use "com.apple.system" */
+               if (uid != 0) asl_set(msg, ASL_KEY_FACILITY, FACILITY_USER);
+       }
+
+       /*
+        * kernel messages are only readable by root and admin group.
+        */
+       if (source == SOURCE_KERN)
+       {
+               asl_set(msg, ASL_KEY_READ_UID, "0");
+               asl_set(msg, ASL_KEY_READ_GID, "80");
+       }
+
+       /*
+        * Access Control: only UID 0 may use facility com.apple.system (or anything with that prefix).
+        * N.B. kernel can use any facility name.
+        */
+
+       /* Set DB Expire Time for com.apple.system.utmpx and lastlog */
+       if ((!strcmp(fac, "com.apple.system.utmpx")) || (!strcmp(fac, "com.apple.system.lastlog")))
+       {
+               snprintf(buf, sizeof(buf), "%lu", tick + global.utmp_ttl);
+               asl_set(msg, ASL_KEY_EXPIRE_TIME, buf);
+       }
+
+       /* Set DB Expire Time for Filestsrem errors */
+       if (!strcmp(fac, FSLOG_VAL_FACILITY))
+       {
+               snprintf(buf, sizeof(buf), "%lu", tick + global.fs_ttl);
+               asl_set(msg, ASL_KEY_EXPIRE_TIME, buf);
+       }
+
+       /*
+        * special case handling of kernel disaster messages
+        */
+       if ((source == SOURCE_KERN) && (level <= KERN_DISASTER_LEVEL))
+       {
+               if (kern_post_level != NULL) *kern_post_level = level;
+               disaster_message(msg);
+       }
+
+       return VERIFY_STATUS_OK;
 }
 
 int
@@ -366,14 +866,16 @@ aslevent_fdsets(fd_set *rd, fd_set *wr, fd_set *ex)
        struct aslevent *e;
        int status = 0;
 
-       asldebug("--> aslevent_fdsets\n");
+//     asldebug("--> aslevent_fdsets\n");
        FD_ZERO(rd);
        FD_ZERO(wr);
        FD_ZERO(ex);
 
        for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next)
        {
-               asldebug("adding fd %d\n", e->fd);
+               if (e->fd < 0) continue;
+
+//             asldebug("adding fd %d\n", e->fd);
                if (e->readfn)
                {
                        FD_SET(e->fd, rd);
@@ -393,53 +895,167 @@ aslevent_fdsets(fd_set *rd, fd_set *wr, fd_set *ex)
                }
        }
 
-       asldebug("<--aslevent_fdsets\n");
+//     asldebug("<--aslevent_fdsets\n");
        return status;
 }
 
 void
-aslevent_handleevent(fd_set rd, fd_set wr, fd_set ex, char *errstr)
+aslevent_cleanup()
+{
+       struct aslevent *e, *next;
+
+       e = Eventq.tqh_first;
+
+       while (e != NULL)
+       {
+               next = e->entries.tqe_next;
+               if (e->fd < 0)
+               {
+                       TAILQ_REMOVE(&Eventq, e, entries);
+                       if (e->sender != NULL) free(e->sender);
+                       free(e);
+               }
+
+               e = next;
+       }
+}
+
+void
+list_append_msg(asl_search_result_t *list, asl_msg_t *msg)
+{
+       if (list == NULL) return;
+       if (msg == NULL) return;
+
+       /*
+        * NB: curr is the list size
+        * grow list if necessary
+        */
+       if (list->count == list->curr)
+       {
+               if (list->curr == 0)
+               {
+                       list->msg = (asl_msg_t **)calloc(LIST_SIZE_DELTA, sizeof(asl_msg_t *));
+               }
+               else
+               {
+                       list->msg = (asl_msg_t **)reallocf(list->msg, (list->curr + LIST_SIZE_DELTA) * sizeof(asl_msg_t *));
+               }
+
+               if (list->msg == NULL)
+               {
+                       list->curr = 0;
+                       list->count = 0;
+                       return;
+               }
+
+               list->curr += LIST_SIZE_DELTA;
+       }
+
+       list->msg[list->count] = msg;
+       list->count++;
+}
+
+void
+work_enqueue(asl_msg_t *m)
+{
+       pthread_mutex_lock(global.work_queue_lock);
+       list_append_msg(global.work_queue, m);
+       pthread_mutex_unlock(global.work_queue_lock);
+       pthread_cond_signal(&global.work_queue_cond);
+}
+
+void
+asl_enqueue_message(uint32_t source, struct aslevent *e, asl_msg_t *msg)
+{
+       int32_t kplevel;
+       uint32_t status;
+
+       if (msg == NULL) return;
+
+       /* set retain count to 1 */
+       msg->type |= 0x10;
+
+       kplevel = -1;
+       status = aslmsg_verify(source, e, msg, &kplevel);
+       if (status == VERIFY_STATUS_OK)
+       {
+               if ((source == SOURCE_KERN) && (kplevel >= 0)) notify_post(kern_notify_key[kplevel]);
+               work_enqueue(msg);
+       }
+       else
+       {
+               asl_msg_release(msg);
+       }
+}
+
+asl_msg_t **
+asl_work_dequeue(uint32_t *count)
+{
+       asl_msg_t **work;
+
+       pthread_mutex_lock(global.work_queue_lock);
+       pthread_cond_wait(&global.work_queue_cond, global.work_queue_lock);
+
+       work = NULL;
+       *count = 0;
+
+       if (global.work_queue->count == 0)
+       {
+               pthread_mutex_unlock(global.work_queue_lock);
+               return NULL;
+       }
+
+       work = global.work_queue->msg;
+       *count = global.work_queue->count;
+
+       global.work_queue->count = 0;
+       global.work_queue->curr = 0;
+       global.work_queue->msg = NULL;
+
+       pthread_mutex_unlock(global.work_queue_lock);
+       return work;
+}
+
+void
+aslevent_handleevent(fd_set *rd, fd_set *wr, fd_set *ex)
 {
        struct aslevent *e;
        char *out = NULL;
        asl_msg_t *msg;
+       int32_t cleanup;
+
+//     asldebug("--> aslevent_handleevent\n");
 
-       asldebug("--> aslevent_handleevent\n");
-       if (errstr) errstr = NULL;
+       cleanup = 0;
 
        for (e = Eventq.tqh_first; e != NULL; e = e->entries.tqe_next)
        {
-               if (FD_ISSET(e->fd, &rd) && (e->readfn != NULL))
+               if (e->fd < 0)
+               {
+                       cleanup = 1;
+                       continue;
+               }
+
+               if (FD_ISSET(e->fd, rd) && (e->readfn != NULL))
                {
-                       asldebug("handling read event on %d\n", e->fd);
+//                     asldebug("handling read event on %d\n", e->fd);
                        msg = e->readfn(e->fd);
-                       if (msg == NULL)
-                       {
-                               asldebug("error reading message\n");
-                               continue;
-                       }
+                       if (msg == NULL) continue;
 
-                       if (aslmsg_verify(e, msg) < 0)
-                       {
-                               asl_free(msg);
-                               asldebug("recieved invalid message\n");
-                       }
-                       else
-                       {
-                               aslevent_match(msg);
-                               asl_free(msg);
-                       }
+                       asl_enqueue_message(e->source, e, msg);
                }
 
-               if (FD_ISSET(e->fd, &ex) && e->exceptfn)
+               if (FD_ISSET(e->fd, ex) && e->exceptfn)
                {
                        asldebug("handling except event on %d\n", e->fd);
                        out = e->exceptfn(e->fd);
-                       if (out == NULL) asldebug("error writing message\n");
+                       if (out == NULL) asldebug("error writing message\n\n");
                }
        }
 
-       asldebug("<-- aslevent_handleevent\n");
+       if (cleanup != 0) aslevent_cleanup();
+
+//     asldebug("<-- aslevent_handleevent\n");
 }
 
 int
@@ -450,19 +1066,47 @@ asl_log_string(const char *str)
        if (str == NULL) return 1;
 
        msg = asl_msg_from_string(str);
-       if (aslmsg_verify(NULL, msg) < 0)
+       if (msg == NULL) return 1;
+
+       asl_enqueue_message(SOURCE_INTERNAL, NULL, msg);
+
+       return 0;
+}
+
+int
+asldebug(const char *str, ...)
+{
+       va_list v;
+       int status;
+       FILE *dfp;
+
+       OSSpinLockLock(&global.lock);
+       if (global.debug == 0)
        {
-               asl_free(msg);
-               return -1;
+               OSSpinLockUnlock(&global.lock);
+               return 0;
        }
 
-       aslevent_match(msg);
-       asl_free(msg);
-       return 0;
+       dfp = stderr;
+       if (global.debug_file != NULL) dfp = fopen(global.debug_file, "a");
+       if (dfp == NULL)
+       {
+               OSSpinLockUnlock(&global.lock);
+               return 0;
+       }
+
+       va_start(v, str);
+       status = vfprintf(dfp, str, v);
+       va_end(v);
+
+       if (global.debug_file != NULL) fclose(dfp);
+       OSSpinLockUnlock(&global.lock);
+
+       return status;
 }
 
 void
-aslmark(void)
+asl_mark(void)
 {
        char *str;
 
@@ -491,8 +1135,6 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
        if (in == NULL) return NULL;
        if (len <= 0) return NULL;
 
-       asldebug("asl_syslog_input_convert: %s\n", in); 
-
        pri = LOG_DEBUG;
        tval = NULL;
        sval = NULL;
@@ -541,6 +1183,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
        if (((len - index) > 15) && (p[9] == ':') && (p[12] == ':') && (p[15] == ' '))
        {
                tmp = malloc(16);
+               if (tmp == NULL) return NULL;
+
                memcpy(tmp, p, 15);
                tmp[15] = '\0';
 
@@ -563,16 +1207,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
        if (kern != 0)
        {
                msg = (asl_msg_t *)calloc(1, sizeof(asl_msg_t));
-               msg->type = ASL_TYPE_MSG;
-
-               asl_set(msg, ASL_KEY_SENDER, "kernel");
+               if (msg == NULL) return NULL;
 
-               asl_set(msg, "Facility", "kern");
-               if (tval != NULL)
-               {
-                       asl_set(msg, ASL_KEY_TIME, tval);
-                       free(tval);
-               }
 
                asl_set(msg, ASL_KEY_MSG, p);
 
@@ -580,10 +1216,6 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
 
                asl_set(msg, ASL_KEY_PID, "0");
 
-               asl_set(msg, ASL_KEY_UID, "0");
-
-               asl_set(msg, ASL_KEY_GID, "0");
-
                asl_set(msg, ASL_KEY_HOST, whatsmyhostname());
 
                return msg;
@@ -598,11 +1230,15 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
                {
                        n = brace - p;
                        sval = malloc(n + 1);
+                       if (sval == NULL) return NULL;
+
                        memcpy(sval, p, n);
                        sval[n] = '\0';
 
                        n = colon - (brace + 1) - 1;
                        pval = malloc(n + 1);
+                       if (pval == NULL) return NULL;
+
                        memcpy(pval, (brace + 1), n);
                        pval[n] = '\0';
                }
@@ -610,6 +1246,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
                {
                        n = colon - p;
                        sval = malloc(n + 1);
+                       if (sval == NULL) return NULL;
+
                        memcpy(sval, p, n);
                        sval[n] = '\0';
                }
@@ -629,6 +1267,8 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
        if (n > 0)
        {
                mval = malloc(n + 1);
+               if (mval == NULL) return NULL;
+
                memcpy(mval, p, n);
                mval[n] = '\0';
        }
@@ -636,7 +1276,7 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
        if (fval == NULL) fval = asl_syslog_faciliy_num_to_name(LOG_USER);
 
        msg = (asl_msg_t *)calloc(1, sizeof(asl_msg_t));
-       msg->type = ASL_TYPE_MSG;
+       if (msg == NULL) return NULL;
 
        if (tval != NULL)
        {
@@ -675,13 +1315,50 @@ asl_syslog_input_convert(const char *in, int len, char *rhost, int kern)
 
        if (msg->count == 0)
        {
-               asl_free(msg);
+               asl_msg_release(msg);
                return NULL;
        }
 
        return msg;
 }
 
+asl_msg_t *
+asl_input_parse(const char *in, int len, char *rhost, int kern)
+{
+       asl_msg_t *m;
+       int status, x, legacy;
+
+       asldebug("asl_input_parse: %s\n", (in == NULL) ? "NULL" : in);
+
+       if (in == NULL) return NULL;
+
+       legacy = 1;
+       m = NULL;
+
+       /* calculate length if not provided */
+       if (len == 0) len = strlen(in);
+
+       /*
+        * Determine if the input is "old" syslog format or new ASL format.
+        * Old format lines should start with "<", but they can just be straight text.
+        * ASL input starts with a length (10 bytes) followed by a space and a '['.
+        */
+       if ((in[0] != '<') && (len > 11))
+       {
+               status = sscanf(in, "%d ", &x);
+               if ((status == 1) && (in[10] == ' ') && (in[11] == '[')) legacy = 0;
+       }
+
+       if (legacy == 1) return asl_syslog_input_convert(in, len, rhost, kern);
+
+       m = asl_msg_from_string(in + 11);
+       if (m == NULL) return NULL;
+
+       if (rhost != NULL) asl_set(m, ASL_KEY_HOST, rhost);
+
+       return m;
+}
+
 char *
 get_line_from_file(FILE *f)
 {
@@ -693,8 +1370,158 @@ get_line_from_file(FILE *f)
        if (len == 0) return NULL;
 
        s = malloc(len + 1);
+       if (s == NULL) return NULL;
+
        memcpy(s, out, len);
 
        s[len - 1] = '\0';
        return s;
 }
+
+uint32_t
+asl_msg_type(asl_msg_t *m)
+{
+       if (m == NULL) return ASL_TYPE_ERROR;
+       return (m->type & ASL_MSG_TYPE_MASK);
+}
+
+void
+asl_msg_release(asl_msg_t *m)
+{
+       int32_t newval;
+
+       if (m == NULL) return;
+
+       newval = OSAtomicAdd32(-0x10, (int32_t*)&m->type) >> 4;
+       assert(newval >= 0);
+
+       if (newval > 0) return;
+
+       asl_free(m);
+}
+
+asl_msg_t *
+asl_msg_retain(asl_msg_t *m)
+{
+       int32_t newval;
+
+       if (m == NULL) return NULL;
+
+       newval = OSAtomicAdd32(0x10, (int32_t*)&m->type) >> 4;
+       assert(newval > 0);
+
+       return m;
+}
+
+void
+launchd_callback(struct timeval *when, pid_t from_pid, pid_t about_pid, uid_t sender_uid, gid_t sender_gid, int priority, const char *from_name, const char *about_name, const char *session_name, const char *msg)
+{
+       asl_msg_t *m;
+       char str[256];
+       time_t now;
+
+/*
+       asldebug("launchd_callback Time %lu %lu PID %u RefPID %u UID %d GID %d PRI %d Sender %s Ref %s Session %s Message %s\n",
+       when->tv_sec, when->tv_usec, from_pid, about_pid, sender_uid, sender_gid, priority, from_name, about_name, session_name, msg);
+*/
+
+       m = asl_new(ASL_TYPE_MSG);
+       if (m == NULL) return;
+
+       /* Level */
+       if (priority < ASL_LEVEL_EMERG) priority = ASL_LEVEL_EMERG;
+       if (priority > ASL_LEVEL_DEBUG) priority = ASL_LEVEL_DEBUG;
+       snprintf(str, sizeof(str), "%d", priority);
+
+       asl_set(m, ASL_KEY_LEVEL, str);
+
+       /* Time */
+       if (when != NULL)
+       {
+               snprintf(str, sizeof(str), "%lu", when->tv_sec);
+               asl_set(m, ASL_KEY_TIME, str);
+
+               snprintf(str, sizeof(str), "%lu", 1000 * (unsigned long int)when->tv_usec);
+               asl_set(m, ASL_KEY_TIME_NSEC, str);
+       }
+       else
+       {
+               now = time(NULL);
+               snprintf(str, sizeof(str), "%lu", now);
+               asl_set(m, ASL_KEY_TIME, str);
+       }
+
+       /* Host */
+       asl_set(m, ASL_KEY_HOST, whatsmyhostname());
+
+       /* Facility */
+       asl_set(m, ASL_KEY_FACILITY, FACILITY_CONSOLE);
+
+       /* UID */
+       snprintf(str, sizeof(str), "%u", (unsigned int)sender_uid);
+       asl_set(m, ASL_KEY_UID, str);
+
+       /* GID */
+       snprintf(str, sizeof(str), "%u", (unsigned int)sender_gid);
+       asl_set(m, ASL_KEY_GID, str);
+
+       /* PID */
+       if (from_pid != 0)
+       {
+               snprintf(str, sizeof(str), "%u", (unsigned int)from_pid);
+               asl_set(m, ASL_KEY_PID, str);
+       }
+
+       /* Reference PID */
+       if ((about_pid > 0) && (about_pid != from_pid))
+       {
+               snprintf(str, sizeof(str), "%u", (unsigned int)about_pid);
+               asl_set(m, ASL_KEY_REF_PID, str);
+       }
+
+       /* Sender */
+       if (from_name != NULL)
+       {
+               asl_set(m, ASL_KEY_SENDER, from_name);
+       }
+
+       /* ReadUID */
+       if (sender_uid != 0)
+       {
+               snprintf(str, sizeof(str), "%d", (int)sender_uid);
+               asl_set(m, ASL_KEY_READ_UID, str);
+       }
+
+       /* Reference Process */
+       if (about_name != NULL)
+       {
+               if ((from_name != NULL) && (strcmp(from_name, about_name) != 0))
+               {
+                       asl_set(m, ASL_KEY_REF_PROC, about_name);
+               }
+       }
+
+       /* Session */
+       if (session_name != NULL)
+       {
+               asl_set(m, ASL_KEY_SESSION, session_name);
+       }
+
+       /* Message */
+       if (msg != NULL)
+       {
+               asl_set(m, ASL_KEY_MSG, msg);
+       }
+
+       /* verify and push to receivers */
+       asl_enqueue_message(SOURCE_LAUNCHD, NULL, m);
+}
+
+void
+launchd_drain()
+{
+       forever
+       {
+               _vprocmgr_log_drain(NULL, NULL, launchd_callback);
+       }
+}