]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/kern_audit.c
xnu-517.tar.gz
[apple/xnu.git] / bsd / kern / kern_audit.c
diff --git a/bsd/kern/kern_audit.c b/bsd/kern/kern_audit.c
new file mode 100644 (file)
index 0000000..ce838a4
--- /dev/null
@@ -0,0 +1,1592 @@
+/*
+ * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ * 
+ * Copyright (c) 1999-2003 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 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, 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 <sys/param.h>
+#include <sys/fcntl.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/namei.h>
+#include <sys/proc.h>
+#include <sys/queue.h>
+#include <sys/systm.h>
+#include <sys/time.h>
+#include <sys/ucred.h>
+#include <sys/uio.h>
+#include <sys/unistd.h>
+#include <sys/vnode.h>
+#include <sys/audit.h>
+#include <sys/kern_audit.h>
+#include <sys/user.h>
+#include <sys/bsm_kevents.h>
+#include <sys/bsm_klib.h>
+#include <sys/syscall.h>
+#include <sys/malloc.h>
+#include <sys/un.h>
+
+#include <kern/lock.h>
+#include <kern/wait_queue.h>
+
+#ifdef AUDIT
+
+/*
+ * The AUDIT_EXCESSIVELY_VERBOSE define enables a number of
+ * gratuitously noisy printf's to the console.  Due to the
+ * volume, it should be left off unless you want your system
+ * to churn a lot whenever the audit record flow gets high.
+ */
+/* #define     AUDIT_EXCESSIVELY_VERBOSE */
+#ifdef AUDIT_EXCESSIVELY_VERBOSE
+#define        AUDIT_PRINTF(x) printf x
+#else
+#define        AUDIT_PRINTF(X)
+#endif
+
+#if DIAGNOSTIC
+#if defined(assert)
+#undef assert()
+#endif
+#define assert(cond)    \
+    ((void) ((cond) ? 0 : panic("%s:%d (%s)", __FILE__, __LINE__, # cond)))
+#else
+#include <kern/assert.h>
+#endif /* DIAGNOSTIC */
+
+/* 
+ * Define the audit control flags.
+ */
+int    audit_enabled;
+int    audit_suspended;
+
+/*
+ * Mutex to protect global variables shared between various threads and
+ * processes.
+ */
+static mutex_t                         *audit_mtx;
+
+/*
+ * Queue of audit records ready for delivery to disk.  We insert new
+ * records at the tail, and remove records from the head.
+ */
+static TAILQ_HEAD(, kaudit_record)      audit_q;
+
+/*
+ * Condition variable to signal to the worker that it has work to do:
+ * either new records are in the queue, or a log replacement is taking
+ * place.
+ */
+static wait_queue_t                     audit_wait_queue;
+
+/*
+ * When an audit log is rotated, the actual rotation must be performed
+ * by the audit worker thread, as it may have outstanding writes on the
+ * current audit log.  audit_replacement_vp holds the vnode replacing
+ * the current vnode.  We can't let more than one replacement occur
+ * at a time, so if more than one thread requests a replacement, only
+ * one can have the replacement "in progress" at any given moment.  If
+ * a thread tries to replace the audit vnode and discovers a replacement
+ * is already in progress (i.e., audit_replacement_flag != 0), then it
+ * will sleep on audit_replacement_cv waiting its turn to perform a
+ * replacement.  When a replacement is completed, this cv is signalled
+ * by the worker thread so a waiting thread can start another replacement.
+ * We also store a credential to perform audit log write operations with.
+ */
+static wait_queue_t                     audit_replacement_wait_queue;
+
+static int                              audit_replacement_flag;
+static struct vnode                    *audit_replacement_vp;
+static struct ucred                    *audit_replacement_cred;
+
+/*
+ * Flags to use on audit files when opening and closing.
+ */
+const static int                audit_open_flags = FWRITE | O_APPEND;
+const static int                audit_close_flags = FWRITE | O_APPEND;
+
+/*
+ * XXX: Couldn't find the include file for this, so copied kern_exec.c's
+ * behavior.
+ */
+extern task_t kernel_task;
+
+static void
+audit_free(struct kaudit_record *ar)
+{
+       if (ar->k_ar.ar_arg_upath1 != NULL) {
+               kmem_free(kernel_map, ar->k_ar.ar_arg_upath1, MAXPATHLEN);
+       }
+       if (ar->k_ar.ar_arg_upath2 != NULL) {
+               kmem_free(kernel_map, ar->k_ar.ar_arg_upath2, MAXPATHLEN);
+       }
+       if (ar->k_ar.ar_arg_kpath1 != NULL) {
+               kmem_free(kernel_map, ar->k_ar.ar_arg_kpath1, MAXPATHLEN);
+       }
+       if (ar->k_ar.ar_arg_kpath2 != NULL) {
+               kmem_free(kernel_map, ar->k_ar.ar_arg_kpath2, MAXPATHLEN);
+       }
+       if (ar->k_ar.ar_arg_text != NULL) {
+               kmem_free(kernel_map, ar->k_ar.ar_arg_text, MAXPATHLEN);
+       }
+       if (ar->k_udata != NULL) {
+               kmem_free(kernel_map, ar->k_udata, ar->k_ulen);
+       }
+       kmem_free(kernel_map, ar, sizeof(*ar));
+}
+
+static int
+audit_write(struct vnode *vp, struct kaudit_record *ar, struct ucred *cred,
+    struct proc *p)
+{
+       int ret;
+       struct au_record *bsm;
+
+       /* 
+        * If there is a user audit record attached to the kernel record,
+        * then write the user record.
+        */
+       /* XXX Need to decide a few things here: IF the user audit 
+        * record is written, but the write of the kernel record fails,
+        * what to do? Should the kernel record come before or after the
+        * user record? For now, we write the user record first, and
+        * we ignore errors.
+        */
+       if (ar->k_udata != NULL) {
+               vn_rdwr(UIO_WRITE, vp, (void *)ar->k_udata, ar->k_ulen,
+                   (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, p);
+       }
+
+       /* 
+        * Convert the internal kernel record to BSM format and write it
+        * out if everything's OK.
+        */
+       ret = kaudit_to_bsm(ar, &bsm);
+       if (ret == BSM_NOAUDIT)
+               return (0);
+
+       if (ret == BSM_FAILURE) {
+               AUDIT_PRINTF(("BSM conversion failure\n"));
+               return (-1);
+       }
+       
+       /* XXX This function can be called with the kernel funnel held,
+        * which is not optimal. We should break the write functionality
+        * away from the BSM record generation and have the BSM generation
+        * done before this function is called. This function will then
+        * take the BSM record as a parameter.
+        */
+       ret = (vn_rdwr(UIO_WRITE, vp, (void *)bsm->data, bsm->len,
+           (off_t)0, UIO_SYSSPACE, IO_APPEND|IO_UNIT, cred, NULL, p));
+
+       kau_free(bsm);
+
+       return (ret);
+}
+
+static void
+audit_worker()
+{
+       int do_replacement_signal, error, release_funnel;
+       TAILQ_HEAD(, kaudit_record) ar_worklist;
+       struct kaudit_record *ar, *ar_start, *ar_stop;
+       struct vnode *audit_vp, *old_vp;
+       struct ucred *audit_cred, *old_cred;
+       struct proc *audit_p;
+
+       AUDIT_PRINTF(("audit_worker starting\n"));
+
+       TAILQ_INIT(&ar_worklist);
+       audit_cred = NULL;
+       audit_p = current_proc();
+       audit_vp = NULL;
+
+       /*
+        * XXX: Presumably we can assume Mach threads are started without
+        * holding the BSD kernel funnel?
+        */
+       thread_funnel_set(kernel_flock, FALSE);
+
+       mutex_lock(audit_mtx);
+       while (1) {
+               /*
+                * First priority: replace the audit log target if requested.
+                * As we actually close the vnode in the worker thread, we
+                * need to grab the funnel, which means releasing audit_mtx.
+                * In case another replacement was scheduled while the mutex
+                * we released, we loop.
+                *
+                * XXX It could well be we should drain existing records
+                * first to ensure that the timestamps and ordering
+                * are right.
+                */
+               do_replacement_signal = 0;
+               while (audit_replacement_flag != 0) {
+                       old_cred = audit_cred;
+                       old_vp = audit_vp;
+                       audit_cred = audit_replacement_cred;
+                       audit_vp = audit_replacement_vp;
+                       audit_replacement_cred = NULL;
+                       audit_replacement_vp = NULL;
+                       audit_replacement_flag = 0;
+
+                       audit_enabled = (audit_vp != NULL);
+
+                       if (old_vp != NULL || audit_vp != NULL) {
+                               mutex_unlock(audit_mtx);
+                               thread_funnel_set(kernel_flock, TRUE);
+                               release_funnel = 1;
+                       } else
+                               release_funnel = 0;
+                       /*
+                        * XXX: What to do about write failures here?
+                        */
+                       if (old_vp != NULL) {
+                               AUDIT_PRINTF(("Closing old audit file\n"));
+                               vn_close(old_vp, audit_close_flags, old_cred,
+                                   audit_p);
+                               crfree(old_cred);
+                               old_cred = NULL;
+                               old_vp = NULL;
+                               AUDIT_PRINTF(("Audit file closed\n"));
+                       }
+                       if (audit_vp != NULL) {
+                               AUDIT_PRINTF(("Opening new audit file\n"));
+                       }
+                       if (release_funnel) {
+                               thread_funnel_set(kernel_flock, FALSE);
+                               mutex_lock(audit_mtx);
+                       }
+                       do_replacement_signal = 1;
+               }
+               /*
+                * Signal that replacement have occurred to wake up and
+                * start any other replacements started in parallel.  We can
+                * continue about our business in the mean time.  We
+                * broadcast so that both new replacements can be inserted,
+                * but also so that the source(s) of replacement can return
+                * successfully.
+                */
+               if (do_replacement_signal)
+                       wait_queue_wakeup_all(audit_replacement_wait_queue,
+                           0, THREAD_AWAKENED);
+
+               /*
+                * Next, check to see if we have any records to drain into
+                * the vnode.  If not, go back to waiting for an event.
+                */
+               if (TAILQ_EMPTY(&audit_q)) {
+                       int ret;
+
+                       AUDIT_PRINTF(("audit_worker waiting\n"));
+                       ret = wait_queue_assert_wait(audit_wait_queue, 0, 
+                                                    THREAD_UNINT);
+                       mutex_unlock(audit_mtx);
+
+                       assert(ret == THREAD_WAITING);
+                       ret = thread_block(THREAD_CONTINUE_NULL);
+                       assert(ret == THREAD_AWAKENED);
+                       AUDIT_PRINTF(("audit_worker woken up\n"));
+       AUDIT_PRINTF(("audit_worker: new vp = %p; value of flag %d\n",
+           audit_replacement_vp, audit_replacement_flag));
+
+                       mutex_lock(audit_mtx);
+                       continue;
+               }
+
+               /*
+                * If we have records, but there's no active vnode to
+                * write to, drain the record queue.  Generally, we
+                * prevent the unnecessary allocation of records
+                * elsewhere, but we need to allow for races between
+                * conditional allocation and queueing.  Go back to
+                * waiting when we're done.
+                *
+                * XXX: We go out of our way to avoid calling audit_free()
+                * with the audit_mtx held, to avoid a lock order reversal
+                * as free() may grab the funnel.  This will be fixed at
+                * some point.
+                */
+               if (audit_vp == NULL) {
+                       while ((ar = TAILQ_FIRST(&audit_q))) {
+                               TAILQ_REMOVE(&audit_q, ar, k_q);
+                               TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q);
+                       }
+                       mutex_unlock(audit_mtx);
+                       while ((ar = TAILQ_FIRST(&ar_worklist))) {
+                               TAILQ_REMOVE(&ar_worklist, ar, k_q);
+                               audit_free(ar);
+                       }
+                       mutex_lock(audit_mtx);
+                       continue;
+               }
+
+               /*
+                * We have both records to write, and an active vnode
+                * to write to.  Dequeue a record, and start the write.
+                * Eventually, it might make sense to dequeue several
+                * records and perform our own clustering, if the lower
+                * layers aren't doing it automatically enough.
+                *
+                * XXX: We go out of our way to avoid calling audit_free()
+                * with the audit_mtx held, to avoid a lock order reversal
+                * as free() may grab the funnel.  This will be fixed at
+                * some point.
+                */
+               while ((ar = TAILQ_FIRST(&audit_q))) {
+                       TAILQ_REMOVE(&audit_q, ar, k_q);
+                       TAILQ_INSERT_TAIL(&ar_worklist, ar, k_q);
+               }
+               mutex_unlock(audit_mtx);
+               release_funnel = 0;
+               while ((ar = TAILQ_FIRST(&ar_worklist))) {
+                       TAILQ_REMOVE(&ar_worklist, ar, k_q);
+                       if (audit_vp != NULL) {
+                               /*
+                                * XXX: What should happen if there's a write
+                                * error here?
+                                */
+                               if (!release_funnel) {
+                                       thread_funnel_set(kernel_flock, TRUE);
+                                       release_funnel = 1;
+                               }
+                               VOP_LEASE(audit_vp, audit_p, audit_cred,
+                                   LEASE_WRITE);
+                               error = audit_write(audit_vp, ar, audit_cred,
+                                   audit_p);
+                               if (error)
+                                       printf("audit_worker: write error %d\n",
+                                           error);
+                       }
+                       audit_free(ar);
+               }
+               if (release_funnel)
+                       thread_funnel_set(kernel_flock, FALSE);
+               mutex_lock(audit_mtx);
+       }
+}
+
+void
+audit_init(void)
+{
+
+       /* Verify that the syscall to audit event table is the same
+        * size as the system call table.
+        */
+       if (nsys_au_event != nsysent) {
+               printf("Security auditing service initialization failed, ");
+               printf("audit event table doesn't match syscall table.\n");
+               return;
+       }
+
+       printf("Security auditing service present\n");
+       TAILQ_INIT(&audit_q);
+       audit_enabled = 0;
+       audit_suspended = 0;
+       audit_replacement_cred = NULL;
+       audit_replacement_flag = 0;
+       audit_replacement_vp = NULL;
+       audit_mtx = mutex_alloc(ETAP_NO_TRACE);
+       audit_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO);
+       audit_replacement_wait_queue = wait_queue_alloc(SYNC_POLICY_FIFO);
+
+       /* Initialize the BSM audit subsystem. */
+       kau_init();
+
+       kernel_thread(kernel_task, audit_worker);
+}
+
+static void
+audit_rotate_vnode(struct ucred *cred, struct vnode *vp)
+{
+       int ret;
+
+       /*
+        * If other parallel log replacements have been requested, we wait
+        * until they've finished before continuing.
+        */
+       mutex_lock(audit_mtx);
+       while (audit_replacement_flag != 0) {
+
+               AUDIT_PRINTF(("audit_rotate_vnode: sleeping to wait for "
+                   "flag\n"));
+               ret = wait_queue_assert_wait(audit_replacement_wait_queue, 0,
+                                            THREAD_UNINT);
+               mutex_unlock(audit_mtx);
+
+               assert(ret == THREAD_WAITING);
+               ret = thread_block(THREAD_CONTINUE_NULL);
+               assert(ret == THREAD_AWAKENED);
+               AUDIT_PRINTF(("audit_rotate_vnode: woken up (flag %d)\n",
+                   audit_replacement_flag));
+
+               mutex_lock(audit_mtx);
+       }
+       audit_replacement_cred = cred;
+       audit_replacement_flag = 1;
+       audit_replacement_vp = vp;
+
+       /*
+        * Wake up the audit worker to perform the exchange once we
+        * release the mutex.
+        */
+       wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED);
+
+       /*
+        * Wait for the audit_worker to broadcast that a replacement has
+        * taken place; we know that once this has happened, our vnode
+        * has been replaced in, so we can return successfully.
+        */
+       AUDIT_PRINTF(("audit_rotate_vnode: waiting for news of "
+           "replacement\n"));
+       ret = wait_queue_assert_wait(audit_replacement_wait_queue, 0,
+                                    THREAD_UNINT);
+       mutex_unlock(audit_mtx);
+
+       assert(ret == THREAD_WAITING);
+       ret = thread_block(THREAD_CONTINUE_NULL);
+       assert(ret == THREAD_AWAKENED);
+       AUDIT_PRINTF(("audit_rotate_vnode: change acknowledged by "
+           "audit_worker (flag " "now %d)\n", audit_replacement_flag));
+}
+
+/*
+ * Drain the audit queue and close the log at shutdown.
+ */
+void
+audit_shutdown(void)
+{
+
+       audit_rotate_vnode(NULL, NULL);
+}
+
+static __inline__ struct uthread *
+curuthread(void)
+{
+
+       return (get_bsdthread_info(current_act()));
+}
+
+static __inline__ struct kaudit_record *
+currecord(void)
+{
+
+       return (curuthread()->uu_ar);
+}
+
+/**********************************
+ * Begin system calls.            *
+ **********************************/
+/*
+ * System call to allow a user space application to submit a BSM audit
+ * record to the kernel for inclusion in the audit log. This function
+ * does little verification on the audit record that is submitted.
+ *
+ * XXXAUDIT: Audit preselection for user records does not currently
+ * work, since we pre-select only based on the AUE_audit event type,
+ * not the event type submitted as part of the user audit data.
+ */
+struct audit_args {
+       void *  record;
+       int     length;
+};
+/* ARGSUSED */
+int
+audit(struct proc *p, struct audit_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+       void * rec;
+       struct kaudit_record *ar;
+
+       ar = currecord();
+
+       /* XXX: What's the proper error code if a user audit record can't
+        * be written due to auditing off, or otherwise unavailable?
+        */
+       if (ar == NULL)
+               return (ENOTSUP);
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+
+       if (uap->length > MAX_AUDIT_RECORD_SIZE) 
+               return (EINVAL);
+
+       error = kmem_alloc(kernel_map, (vm_offset_t *)&rec, uap->length);
+       if (error != KERN_SUCCESS)
+               return(ENOMEM);
+
+       error = copyin(uap->record, rec, uap->length);
+       if (error)
+               goto free_out;
+
+       /* Verify the record */
+       if (bsm_rec_verify(rec) == 0) {
+               error = EINVAL;
+               goto free_out;
+       }
+
+       /* Attach the user audit record to the kernel audit record. Because
+        * this system call is an auditable event, we will write the user
+        * record along with the record for this audit event.
+        */
+       ar->k_udata = rec;
+       ar->k_ulen  = uap->length;
+       return (0);
+
+free_out:
+       kmem_free(kernel_map, (vm_offset_t)rec, uap->length);
+       return (error);
+}
+
+/*
+ *  System call to manipulate auditing.
+ */
+struct auditon_args {
+       int     cmd;
+       void *  data;
+       int     length;
+};
+/* ARGSUSED */
+int
+auditon(struct proc *p, struct auditon_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       return (ENOSYS);
+}
+
+/*
+ *  System call to pass in file descriptor for audit log.
+ */
+struct auditsvc_args {
+       int     fd;
+       int     limit;
+};
+/* ARGSUSED */
+int
+auditsvc(struct proc *p, struct auditsvc_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       return (ENOSYS);
+}
+
+/* 
+ * System calls to manage the user audit information.
+ * XXXAUDIT May need to lock the proc structure.
+ */
+struct getauid_args {
+       au_id_t *auid;
+};
+/* ARGSUSED */
+int
+getauid(struct proc *p, struct getauid_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+
+       error = copyout((void *)&p->p_au->ai_auid, (void *)uap->auid, 
+                               sizeof(*uap->auid));
+       if (error)
+               return (error);
+
+       return (0);
+}
+
+struct setauid_args {
+       au_id_t *auid;
+};
+/* ARGSUSED */
+int
+setauid(struct proc *p, struct setauid_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+
+       error = copyin((void *)uap->auid, (void *)&p->p_au->ai_auid, 
+                               sizeof(p->p_au->ai_auid));
+       if (error)
+               return (error);
+
+       audit_arg_auid(p->p_au->ai_auid);
+       return (0);
+}
+
+/*
+ *  System calls to get and set process audit information.
+ */
+struct getaudit_args {
+       struct auditinfo        *auditinfo;
+};
+/* ARGSUSED */
+int
+getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       error = copyout((void *)p->p_au, (void *)uap->auditinfo, 
+                               sizeof(*uap->auditinfo));
+       if (error)
+               return (error);
+
+       return (0);
+}
+
+struct setaudit_args {
+       struct auditinfo        *auditinfo;
+};
+/* ARGSUSED */
+int
+setaudit(struct proc *p, struct setaudit_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       error = copyin((void *)uap->auditinfo, (void *)p->p_au, 
+                               sizeof(*p->p_au));
+       if (error)
+               return (error);
+
+       return (0);
+}
+
+struct getaudit_addr_args {
+       struct auditinfo_addr   *auditinfo_addr;
+       int                     length;
+};
+/* ARGSUSED */
+int
+getaudit_addr(struct proc *p, struct getaudit_addr_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       return (ENOSYS);
+}
+
+struct setaudit_addr_args {
+       struct auditinfo_addr   *auditinfo_addr;
+       int                     length;
+};
+/* ARGSUSED */
+int
+setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval)
+{
+       register struct pcred *pc = p->p_cred;
+       int error;
+
+       error = suser(pc->pc_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+       return (ENOSYS);
+}
+
+/*
+ * Syscall to manage audit files.
+ *
+ * XXX: Should generate an audit event.
+ */
+struct auditctl_args {
+       char    *path;
+};
+/* ARGSUSED */
+int
+auditctl(struct proc *p, struct auditctl_args *uap)
+{
+       struct kaudit_record *ar;
+       struct nameidata nd;
+       struct ucred *cred;
+       struct vnode *vp;
+       int error, flags, ret;
+
+       error = suser(p->p_ucred, &p->p_acflag);
+       if (error)
+               return (error);
+
+       vp = NULL;
+       cred = NULL;
+
+       /*
+        * If a path is specified, open the replacement vnode, perform
+        * validity checks, and grab another reference to the current
+        * credential.
+        */
+       if (uap->path != NULL) {
+               NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE,
+                   uap->path, p);
+               flags = audit_open_flags;
+               error = vn_open(&nd, flags, 0);
+               if (error)
+                       goto out;
+               VOP_UNLOCK(nd.ni_vp, 0, p);
+               vp = nd.ni_vp;
+               if (vp->v_type != VREG) {
+                       vn_close(vp, audit_close_flags, p->p_ucred, p);
+                       error = EINVAL;
+                       goto out;
+               }
+               cred = p->p_ucred;
+               crhold(cred);
+       }
+
+       audit_rotate_vnode(cred, vp);
+out:
+       return (error);
+}
+
+/**********************************
+ * End of system calls.           *
+ **********************************/
+
+/*
+ * MPSAFE
+ */
+struct kaudit_record *
+audit_new(int event, struct proc *p, struct uthread *uthread)
+{
+       struct kaudit_record *ar;
+       int no_record;
+
+       /*
+        * Eventually, there may be certain classes of events that
+        * we will audit regardless of the audit state at the time
+        * the record is created.  These events will generally
+        * correspond to changes in the audit state.  The dummy
+        * code below is from our first prototype, but may also
+        * be used in the final version (with modified event numbers).
+        */
+#if 0
+       if (event != AUDIT_EVENT_FILESTOP && event != AUDIT_EVENT_FILESTART) {
+#endif
+               mutex_lock(audit_mtx);
+               no_record = (audit_suspended || !audit_enabled);
+               mutex_unlock(audit_mtx);
+               if (no_record)
+                       return (NULL);
+#if 0
+       }
+#endif
+
+       /*
+        * Eventually, we might want to have global event filtering
+        * by event type here.
+        */
+
+       /*
+        * XXX: Process-based event preselection should occur here.
+        * Currently, we only post-select.
+        */
+
+       /*
+        * Initialize the audit record header.
+        * XXX: Should probably use a zone; whatever we use must be
+        * safe to call from the non-BSD side of the house.
+        * XXX: We may want to fail-stop if allocation fails.
+        */
+       (void)kmem_alloc(kernel_map, &ar, sizeof(*ar));
+       if (ar == NULL)
+               return NULL;
+
+       bzero(ar, sizeof(*ar));
+       ar->k_ar.ar_magic = AUDIT_RECORD_MAGIC;
+       ar->k_ar.ar_event = event;
+       nanotime(&ar->k_ar.ar_starttime);
+
+       /* Export the subject credential. */
+       cru2x(p->p_ucred, &ar->k_ar.ar_subj_cred);
+       ar->k_ar.ar_subj_ruid = p->p_cred->p_ruid;
+       ar->k_ar.ar_subj_rgid = p->p_cred->p_rgid;
+       ar->k_ar.ar_subj_egid = p->p_ucred->cr_groups[0];
+       ar->k_ar.ar_subj_auid = p->p_au->ai_auid;
+       ar->k_ar.ar_subj_pid = p->p_pid;
+       bcopy(p->p_comm, ar->k_ar.ar_subj_comm, MAXCOMLEN);
+       bcopy(&p->p_au->ai_mask, &ar->k_ar.ar_subj_amask, 
+                       sizeof(p->p_au->ai_mask));
+
+       return (ar);
+}
+
+/*
+ * MPSAFE
+ * XXXAUDIT: So far, this is unused, and should probably be GC'd.
+ */
+void
+audit_abort(struct kaudit_record *ar)
+{
+
+       audit_free(ar);
+}
+
+/*
+ * MPSAFE
+ */
+void
+audit_commit(struct kaudit_record *ar, int error, int retval)
+{
+
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_errno = error;
+       ar->k_ar.ar_retval = retval;
+
+       /*
+        * We might want to do some system-wide post-filtering
+        * here at some point.
+        */
+
+       /*
+        * Timestamp system call end.
+        */
+       nanotime(&ar->k_ar.ar_endtime);
+
+       /*
+        * XXXAUDIT: The number of outstanding uncommitted audit records is
+        * limited by the number of concurrent threads servicing system
+        * calls in the kernel.  However, there is currently no bound on
+        * the size of the committed records in the audit event queue
+        * before they are sent to disk.  Probably, there should be a fixed
+        * size bound (perhaps configurable), and if that bound is reached,
+        * threads should sleep in audit_commit() until there's room.
+        */
+       mutex_lock(audit_mtx);
+       /*
+        * Note: it could be that some records initiated while audit was
+        * enabled should still be committed?
+        */
+       if (audit_suspended || !audit_enabled) {
+               mutex_unlock(audit_mtx);
+               audit_free(ar);
+               return;
+       }
+       TAILQ_INSERT_TAIL(&audit_q, ar, k_q);
+       wait_queue_wakeup_one(audit_wait_queue, 0, THREAD_AWAKENED);
+       mutex_unlock(audit_mtx);
+}
+
+/*
+ * Calls to set up and tear down audit structures associated with
+ * each system call.
+ */
+void
+audit_syscall_enter(unsigned short code, struct proc *proc, 
+                       struct uthread *uthread)
+{
+       int audit_event;
+
+       assert(uthread->uu_ar == NULL);
+
+       audit_event = sys_au_event[code];
+
+       /*
+        * Allocate an audit record, if desired, and store in the BSD
+        * thread for later use.
+        */
+       if (audit_event != AUE_NULL) {
+#if 0
+               AUDIT_PRINTF(("Allocated record type %d for syscall %d\n",
+                   audit_event, code));
+#endif
+               if (au_preselect(audit_event, &proc->p_au->ai_mask,
+                               AU_PRS_FAILURE | AU_PRS_SUCCESS)) {
+                       uthread->uu_ar = audit_new(audit_event, proc, uthread);
+               } else {
+                       uthread->uu_ar = NULL;
+               }
+       }
+}
+
+void
+audit_syscall_exit(int error, struct proc *proc, struct uthread *uthread)
+{
+       int retval;
+
+       /*
+        * Commit the audit record as desired; once we pass the record
+        * into audit_commit(), the memory is owned by the audit
+        * subsystem.
+        * The return value from the system call is stored on the user
+        * thread. If there was an error, the return value is set to -1,
+        * imitating the behavior of the cerror routine.
+        */
+       if (error)
+               retval = -1;
+       else
+               retval = uthread->uu_rval[0];
+
+       audit_commit(uthread->uu_ar, error, retval);
+       if (uthread->uu_ar != NULL)
+               AUDIT_PRINTF(("audit record committed by pid %d\n", proc->p_pid));
+       uthread->uu_ar = NULL;
+
+}
+
+/*
+ * Calls to manipulate elements of the audit record structure from system
+ * call code.  Macro wrappers will prevent this functions from being
+ * entered if auditing is disabled, avoiding the function call cost.  We
+ * check the thread audit record pointer anyway, as the audit condition
+ * could change, and pre-selection may not have allocated an audit
+ * record for this event.
+ */
+void
+audit_arg_accmode(int accmode)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_accmode = accmode;
+       ar->k_ar.ar_valid_arg |= ARG_ACCMODE;
+}
+
+void
+audit_arg_cmode(int cmode)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_cmode = cmode;
+       ar->k_ar.ar_valid_arg |= ARG_CMODE;
+}
+
+void
+audit_arg_fd(int fd)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_fd = fd;
+       ar->k_ar.ar_valid_arg |= ARG_FD;
+}
+
+void
+audit_arg_fflags(int fflags)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_fflags = fflags;
+       ar->k_ar.ar_valid_arg |= ARG_FFLAGS;
+}
+
+void
+audit_arg_gid(gid_t gid, gid_t egid, gid_t rgid, gid_t sgid)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_gid = gid;
+       ar->k_ar.ar_arg_egid = egid;
+       ar->k_ar.ar_arg_rgid = rgid;
+       ar->k_ar.ar_arg_sgid = sgid;
+       ar->k_ar.ar_valid_arg |= (ARG_GID | ARG_EGID | ARG_RGID | ARG_SGID);
+}
+
+void
+audit_arg_uid(uid_t uid, uid_t euid, uid_t ruid, uid_t suid)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_uid = uid;
+       ar->k_ar.ar_arg_euid = euid;
+       ar->k_ar.ar_arg_ruid = ruid;
+       ar->k_ar.ar_arg_suid = suid;
+       ar->k_ar.ar_valid_arg |= (ARG_UID | ARG_EUID | ARG_RUID | ARG_SUID);
+}
+
+void
+audit_arg_groupset(gid_t *gidset, u_int gidset_size)
+{
+       int i;
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       for (i = 0; i < gidset_size; i++)
+               ar->k_ar.ar_arg_groups.gidset[i] = gidset[i];
+       ar->k_ar.ar_arg_groups.gidset_size = gidset_size;
+       ar->k_ar.ar_valid_arg |= ARG_GROUPSET;
+}
+
+void
+audit_arg_login(char *login)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+#if 0
+       /*
+        * XXX: Add strlcpy() to Darwin for improved safety.
+        */
+       strlcpy(ar->k_ar.ar_arg_login, login, MAXLOGNAME);
+#else
+       strcpy(ar->k_ar.ar_arg_login, login);
+#endif
+
+       ar->k_ar.ar_valid_arg |= ARG_LOGIN;
+}
+
+void
+audit_arg_mask(int mask)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_mask = mask;
+       ar->k_ar.ar_valid_arg |= ARG_MASK;
+}
+
+void
+audit_arg_mode(mode_t mode)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_mode = mode;
+       ar->k_ar.ar_valid_arg |= ARG_MODE;
+}
+
+void
+audit_arg_dev(int dev)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_dev = dev;
+       ar->k_ar.ar_valid_arg |= ARG_DEV;
+}
+
+void
+audit_arg_owner(uid_t uid, gid_t gid)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_uid = uid;
+       ar->k_ar.ar_arg_gid = gid;
+       ar->k_ar.ar_valid_arg |= (ARG_UID | ARG_GID);
+}
+
+void
+audit_arg_pid(pid_t pid)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_pid = pid;
+       ar->k_ar.ar_valid_arg |= ARG_PID;
+}
+
+void
+audit_arg_signum(u_int signum)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_signum = signum;
+       ar->k_ar.ar_valid_arg |= ARG_SIGNUM;
+}
+
+void
+audit_arg_socket(int sodomain, int sotype, int soprotocol)
+{
+
+       struct kaudit_record *ar;
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_sockinfo.sodomain = sodomain;
+       ar->k_ar.ar_arg_sockinfo.sotype = sotype;
+       ar->k_ar.ar_arg_sockinfo.soprotocol = soprotocol;
+       ar->k_ar.ar_valid_arg |= ARG_SOCKINFO;
+}
+
+void
+audit_arg_sockaddr(struct proc *p, struct sockaddr *so)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL || p == NULL || so == NULL)
+               return;
+
+       bcopy(so, &ar->k_ar.ar_arg_sockaddr, sizeof(ar->k_ar.ar_arg_sockaddr));
+       switch (so->sa_family) {
+       case AF_INET:
+               ar->k_ar.ar_valid_arg |= ARG_SADDRINET;
+               break;
+       case AF_INET6:
+               ar->k_ar.ar_valid_arg |= ARG_SADDRINET6;
+               break;
+       case AF_UNIX:
+               audit_arg_upath(p, ((struct sockaddr_un *)so)->sun_path, 
+                               ARG_UPATH1);
+               ar->k_ar.ar_valid_arg |= ARG_SADDRUNIX;
+               break;
+       }
+}
+
+void
+audit_arg_auid(uid_t auid)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_auid = auid;
+       ar->k_ar.ar_valid_arg |= ARG_AUID;
+}
+
+void
+audit_arg_text(char *text)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       /* Invalidate the text string */
+       ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_TEXT);
+       if (text == NULL)
+               return; 
+
+       if (ar->k_ar.ar_arg_text == NULL) {
+               kmem_alloc(kernel_map, &ar->k_ar.ar_arg_text, MAXPATHLEN);
+               if (ar->k_ar.ar_arg_text == NULL)
+                       return; 
+       }
+
+       strcpy(ar->k_ar.ar_arg_text, text);
+       ar->k_ar.ar_valid_arg |= ARG_TEXT;
+}
+
+void
+audit_arg_cmd(int cmd)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_cmd = cmd;
+       ar->k_ar.ar_valid_arg |= ARG_CMD;
+}
+
+void
+audit_arg_svipc_cmd(int cmd)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_svipc_cmd = cmd;
+       ar->k_ar.ar_valid_arg |= ARG_SVIPC_CMD;
+}
+
+void
+audit_arg_svipc_perm(struct ipc_perm *perm)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       bcopy(perm, &ar->k_ar.ar_arg_svipc_perm, 
+               sizeof(ar->k_ar.ar_arg_svipc_perm));
+       ar->k_ar.ar_valid_arg |= ARG_SVIPC_PERM;
+}
+
+void
+audit_arg_svipc_id(int id)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_svipc_id = id;
+       ar->k_ar.ar_valid_arg |= ARG_SVIPC_ID;
+}
+
+void
+audit_arg_svipc_addr(void * addr)
+{
+       struct kaudit_record *ar;
+
+       ar = currecord();
+       if (ar == NULL)
+               return;
+
+       ar->k_ar.ar_arg_svipc_addr = addr;
+       ar->k_ar.ar_valid_arg |= ARG_SVIPC_ADDR;
+}
+
+/* 
+ * Initialize the audit information for the a process, presumably the first 
+ * process in the system.
+ * XXX It is not clear what the initial values should be for audit ID, 
+ * session ID, etc. 
+ */
+void
+audit_proc_init(struct proc *p)
+{
+       MALLOC_ZONE(p->p_au, struct auditinfo *, sizeof(*p->p_au), 
+                       M_SUBPROC, M_WAITOK);
+
+       bzero((void *)p->p_au, sizeof(*p->p_au));
+}
+
+/* 
+ * Copy the audit info from the parent process to the child process when
+ * a fork takes place.
+ * XXX Need to check for failure from the memory allocation, in here
+ * as well as in any functions that use the process auditing info.
+ */
+void
+audit_proc_fork(struct proc *parent, struct proc *child)
+{
+       /* Always set up the audit information pointer as this function
+        * should only be called when the proc is new. If proc structures
+        * are ever cached and reused, then this behavior will leak memory.
+        */
+       MALLOC_ZONE(child->p_au, struct auditinfo *, sizeof(*child->p_au), 
+                       M_SUBPROC, M_WAITOK);
+
+       bcopy(parent->p_au, child->p_au, sizeof(*child->p_au));
+}
+
+/*
+ * Free the auditing structure for the process. 
+ */
+void
+audit_proc_free(struct proc *p)
+{
+       FREE_ZONE((void *)p->p_au, sizeof(*p->p_au), M_SUBPROC);
+       p->p_au = NULL;
+}
+
+/* 
+ * Store a path as given by the user process for auditing into the audit 
+ * record stored on the user thread. This function will allocate the memory to 
+ * store the path info if not already available. This memory will be 
+ * freed when the audit record is freed.
+ */
+void
+audit_arg_upath(struct proc *p, char *upath, u_int64_t flags)
+{
+       struct kaudit_record *ar;
+       char **pathp;
+
+       if (p == NULL || upath == NULL) 
+               return;         /* nothing to do! */
+
+       if (flags & (ARG_UPATH1 | ARG_UPATH2) == 0)
+               return;
+
+       ar = currecord();
+       if (ar == NULL) /* This will be the case for unaudited system calls */
+               return;
+
+       if (flags & ARG_UPATH1) {
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_UPATH1);
+               pathp = &ar->k_ar.ar_arg_upath1;
+       }
+       else {
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_UPATH2);
+               pathp = &ar->k_ar.ar_arg_upath2;
+       }
+
+       if (*pathp == NULL) {
+               kmem_alloc(kernel_map, pathp, MAXPATHLEN);
+               if (*pathp == NULL)
+                       return;
+       }
+
+       canon_path(p, upath, *pathp);
+
+       if (flags & ARG_UPATH1)
+               ar->k_ar.ar_valid_arg |= ARG_UPATH1;
+       else
+               ar->k_ar.ar_valid_arg |= ARG_UPATH2;
+}
+
+/*
+ * Function to save the path and vnode attr information into the audit 
+ * record. 
+ *
+ * It is assumed that the caller will hold any vnode locks necessary to
+ * perform a VOP_GETATTR() on the passed vnode.
+ *
+ * XXX: The attr code is very similar to vfs_vnops.c:vn_stat(), but
+ * always provides access to the generation number as we need that
+ * to construct the BSM file ID.
+ * XXX: We should accept the process argument from the caller, since
+ * it's very likely they already have a reference.
+ * XXX: Error handling in this function is poor.
+ */
+void
+audit_arg_vnpath(struct vnode *vp, u_int64_t flags)
+{
+       struct kaudit_record *ar;
+       struct vattr vattr;
+       int error;
+       int len;
+       char **pathp;
+       struct vnode_au_info *vnp;
+       struct proc *p;
+
+       if (vp == NULL)
+               return;
+
+       ar = currecord();
+       if (ar == NULL) /* This will be the case for unaudited system calls */
+               return;
+
+       if (flags & (ARG_VNODE1 | ARG_VNODE2) == 0)
+               return;
+
+       p = current_proc();
+
+       if (flags & ARG_VNODE1) {
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_KPATH1);
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE1);
+               pathp = &ar->k_ar.ar_arg_kpath1;
+               vnp = &ar->k_ar.ar_arg_vnode1;
+       }
+       else {
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_KPATH2);
+               ar->k_ar.ar_valid_arg &= (ARG_ALL ^ ARG_VNODE2);
+               pathp = &ar->k_ar.ar_arg_kpath2;
+               vnp = &ar->k_ar.ar_arg_vnode2;
+       }
+
+       if (*pathp == NULL) {
+               kmem_alloc(kernel_map, pathp, MAXPATHLEN);
+               if (*pathp == NULL)
+                       return;
+       }
+
+       /* Copy the path looked up by the vn_getpath() function */
+       len = MAXPATHLEN;
+       vn_getpath(vp, *pathp, &len);
+       if (flags & ARG_VNODE1)
+               ar->k_ar.ar_valid_arg |= ARG_KPATH1;
+       else
+               ar->k_ar.ar_valid_arg |= ARG_KPATH2;
+
+       /*
+        * XXX: We'd assert the vnode lock here, only Darwin doesn't
+        * appear to have vnode locking assertions.
+        */
+       error = VOP_GETATTR(vp, &vattr, p->p_ucred, p);
+       if (error) {
+               /* XXX: How to handle this case? */
+               return;
+       }
+
+       vnp->vn_mode = vattr.va_mode;
+       vnp->vn_uid = vattr.va_uid;
+       vnp->vn_gid = vattr.va_gid;
+       vnp->vn_dev = vattr.va_rdev;
+       vnp->vn_fsid = vattr.va_fsid;
+       vnp->vn_fileid = vattr.va_fileid;
+       vnp->vn_gen = vattr.va_gen;
+       if (flags & ARG_VNODE1)
+               ar->k_ar.ar_valid_arg |= ARG_VNODE1;
+       else
+               ar->k_ar.ar_valid_arg |= ARG_VNODE2;
+
+}
+
+#else /* !AUDIT */
+
+void
+audit_init(void)
+{
+
+}
+
+void
+audit_shutdown(void)
+{
+
+}
+
+int
+audit(struct proc *p, struct audit_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+auditon(struct proc *p, struct auditon_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+auditsvc(struct proc *p, struct auditsvc_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+getauid(struct proc *p, struct getauid_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+setauid(struct proc *p, struct setauid_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+getaudit(struct proc *p, struct getaudit_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+setaudit(struct proc *p, struct setaudit_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+getaudit_addr(struct proc *p, struct getaudit_addr_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+setaudit_addr(struct proc *p, struct setaudit_addr_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+int
+auditctl(struct proc *p, struct auditctl_args *uap, register_t *retval)
+{
+       return (ENOSYS);
+}
+
+void
+audit_proc_init(struct proc *p)
+{
+
+}
+
+void
+audit_proc_fork(struct proc *parent, struct proc *child)
+{
+
+}
+
+void
+audit_proc_free(struct proc *p)
+{
+
+}
+
+#endif /* AUDIT */