2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A/*
2N/A * Copyright (c) 1993, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * private interfaces for auditd plugins and auditd.
2N/A */
2N/A
2N/A#include <bsm/adt.h>
2N/A#include <bsm/audit.h>
2N/A#include <bsm/audit_record.h>
2N/A#include <bsm/audit_uevents.h>
2N/A#include <bsm/libbsm.h>
2N/A#include <errno.h>
2N/A#include <fcntl.h>
2N/A#include <libintl.h>
2N/A#include <pthread.h>
2N/A#include <stdio.h>
2N/A#include <stdarg.h>
2N/A#include <stdlib.h>
2N/A#include <string.h>
2N/A#include <strings.h>
2N/A#include <time.h>
2N/A#include <sys/file.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/types.h>
2N/A#include <syslog.h>
2N/A#include <unistd.h>
2N/A#include <wait.h>
2N/A#include "audit_plugin.h"
2N/A
2N/Astatic char auditwarn[] = "/etc/security/audit_warn";
2N/Astatic pthread_mutex_t syslog_lock;
2N/A
2N/Astatic void
2N/Ainit_syslog_mutex()
2N/A{
2N/A (void) pthread_mutex_init(&syslog_lock, NULL);
2N/A}
2N/A
2N/A/*
2N/A * audit_syslog() -- generate syslog messages from threads that use
2N/A * different severity, facility code, and application names.
2N/A *
2N/A * syslog(3C) is thread safe, but the set openlog() / syslog() /
2N/A * closelog() is not.
2N/A *
2N/A * Assumption: the app_name and facility code are paired, i.e.,
2N/A * if the facility code for this call is the same as for the
2N/A * the previous, the app_name hasn't changed.
2N/A */
2N/Avoid
2N/A__audit_syslog(const char *app_name, int flags, int facility, int severity,
2N/A const char *message, ...)
2N/A{
2N/A static pthread_once_t once_control = PTHREAD_ONCE_INIT;
2N/A static int logopen = 0;
2N/A static int prev_facility = -1;
2N/A va_list args;
2N/A
2N/A (void) pthread_once(&once_control, init_syslog_mutex);
2N/A
2N/A va_start(args, message);
2N/A (void) pthread_mutex_lock(&syslog_lock);
2N/A if (prev_facility != facility) {
2N/A if (logopen)
2N/A closelog();
2N/A openlog(app_name, flags, facility);
2N/A vsyslog(severity, message, args);
2N/A (void) pthread_mutex_unlock(&syslog_lock);
2N/A } else {
2N/A vsyslog(severity, message, args);
2N/A (void) pthread_mutex_unlock(&syslog_lock);
2N/A }
2N/A va_end(args);
2N/A}
2N/A
2N/A/*
2N/A * __audit_dowarn - invoke the shell script auditwarn to notify the
2N/A * adminstrator about a given problem.
2N/A * parameters -
2N/A * option - what the problem is
2N/A * text - when used with options soft and hard: which file was being
2N/A * used when the filesystem filled up
2N/A * when used with the plugin option: error detail
2N/A * count - used with various options: how many times auditwarn has
2N/A * been called for this problem since it was last cleared.
2N/A */
2N/Avoid
2N/A__audit_dowarn(char *option, char *text, int count)
2N/A{
2N/A pid_t pid;
2N/A int st;
2N/A char countstr[5];
2N/A char empty[1] = "";
2N/A
2N/A if ((pid = fork1()) == -1) {
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_DAEMON, LOG_ALERT, "audit_warn fork failed");
2N/A return;
2N/A }
2N/A if (pid != 0) {
2N/A (void) waitpid(pid, &st, 0);
2N/A return;
2N/A }
2N/A
2N/A (void) snprintf(countstr, 5, "%d", count);
2N/A if (text == NULL) {
2N/A text = empty;
2N/A }
2N/A
2N/A /* __audit_syslog() called only in case execl() failed */
2N/A if (strcmp(option, "soft") == 0 || strcmp(option, "hard") == 0) {
2N/A (void) execl(auditwarn, auditwarn, option, text, NULL);
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_AUTH, LOG_ALERT, "%s limit in %s.",
2N/A (strcmp(option, "soft") == 0) ? "soft" : "hard", text);
2N/A
2N/A } else if (strcmp(option, "allhard") == 0) {
2N/A (void) execl(auditwarn, auditwarn, option, countstr, NULL);
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_AUTH, LOG_ALERT, "All audit filesystems are full.");
2N/A
2N/A } else if (strcmp(option, "plugin") == 0) {
2N/A (void) execl(auditwarn, auditwarn, option, text,
2N/A countstr, NULL);
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_AUTH, LOG_ALERT, "error %s.", option);
2N/A
2N/A } else if (strcmp(option, "ars") == 0) {
2N/A (void) execl(auditwarn, auditwarn, option, text, 0);
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_AUTH, LOG_ALERT, "error %s - %s.", option, text);
2N/A
2N/A } else {
2N/A (void) execl(auditwarn, auditwarn, option, NULL);
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_AUTH, LOG_ALERT, "error %s.", option);
2N/A }
2N/A
2N/A exit(1);
2N/A}
2N/A
2N/A/*
2N/A * __audit_dowarn2 - invoke the shell script auditwarn to notify the
2N/A * adminstrator about a given problem.
2N/A * parameters -
2N/A * option - what the problem is
2N/A * name - entity reporting the problem (ie, plugin name)
2N/A * error - error string
2N/A * text - when used with options soft and hard: which file was being
2N/A * used when the filesystem filled up
2N/A * when used with the plugin option: error detail
2N/A * count - used with various options: how many times auditwarn has
2N/A * been called for this problem since it was last cleared.
2N/A */
2N/Avoid
2N/A__audit_dowarn2(char *option, char *name, char *error, char *text, int count)
2N/A{
2N/A pid_t pid;
2N/A int st;
2N/A char countstr[5];
2N/A char empty[4] = "...";
2N/A char none[3] = "--";
2N/A
2N/A if ((pid = fork()) == -1) {
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS,
2N/A LOG_DAEMON, LOG_ALERT, "audit_warn fork failed");
2N/A return;
2N/A }
2N/A if (pid != 0) {
2N/A (void) waitpid(pid, &st, 0);
2N/A return;
2N/A }
2N/A (void) snprintf(countstr, 5, "%d", count);
2N/A if ((text == NULL) || (*text == '\0')) {
2N/A text = empty;
2N/A }
2N/A if ((name == NULL) || (*name == '\0')) {
2N/A name = none;
2N/A }
2N/A
2N/A (void) execl(auditwarn, auditwarn, option, name, error, text,
2N/A countstr, NULL);
2N/A
2N/A /* execl() failed */
2N/A __audit_syslog("auditd", LOG_PID | LOG_ODELAY | LOG_CONS, LOG_AUTH,
2N/A LOG_ALERT, "%s plugin error: %s", name, text);
2N/A
2N/A exit(1);
2N/A}
2N/A
2N/A/*
2N/A * debug use - open a file for auditd and its plugins for debug
2N/A */
2N/AFILE *
2N/A__auditd_debug_file_open() {
2N/A static FILE *fp = NULL;
2N/A
2N/A if (fp != NULL)
2N/A return (fp);
2N/A if ((fp = fopen("/var/audit/debug", "aF")) == NULL)
2N/A (void) fprintf(stderr, "failed to open debug file: %s\n",
2N/A strerror(errno));
2N/A
2N/A return (fp);
2N/A}
2N/A
2N/A/*
2N/A * debug log for auditd, its plugins, and for logging adt issues.
2N/A */
2N/A/*PRINTFLIKE1*/
2N/Avoid
2N/A__auditd_debug(char *fmt, ...)
2N/A{
2N/A va_list va;
2N/A char cbuf[26]; /* standard */
2N/A time_t now = time(NULL);
2N/A FILE *debug = __auditd_debug_file_open();
2N/A
2N/A if (debug == NULL) {
2N/A return;
2N/A }
2N/A
2N/A (void) fprintf(debug, "%.15s ", ctime_r(&now, cbuf, sizeof (cbuf)) + 4);
2N/A va_start(va, fmt);
2N/A (void) vfprintf(debug, fmt, va);
2N/A va_end(va);
2N/A (void) fflush(debug);
2N/A}
2N/A
2N/A/*
2N/A * __do_sethost - set the kernel instance host-id
2N/A *
2N/A * If the hostname look fails, with errno ENETDOWN, the returned
2N/A * address is loopback, we choose to accept this just log it.
2N/A *
2N/A * If the kernel instance host-id is loopback, we try
2N/A * to get it again, note this is likely only to happen on
2N/A * a refresh of the audit service.
2N/A *
2N/A * Entry who = the caller
2N/A *
2N/A * Returns 0 = success and system instance terminal ID set
2N/A * errno = ENETDOWN, loopback address set
2N/A * errno = fatal error
2N/A */
2N/Aint
2N/A__do_sethost(char *who)
2N/A{
2N/A au_tid_addr_t *termid = NULL;
2N/A auditinfo_addr_t audit_info;
2N/A int save_errno = 0;
2N/A
2N/A /*
2N/A * Get the current audit info and look for a no-trivial
2N/A * IP address
2N/A */
2N/A if (auditon(A_GETKAUDIT, (caddr_t)&audit_info,
2N/A sizeof (audit_info)) < 0) {
2N/A save_errno = errno;
2N/A __audit_syslog(who, LOG_PID | LOG_CONS | LOG_NOWAIT,
2N/A LOG_DAEMON, LOG_ALERT, "__do_sethost unable to get kernel "
2N/A "audit context %s", strerror(save_errno));
2N/A errno = save_errno;
2N/A return (errno);
2N/A }
2N/A#ifdef DEBUG
2N/A __auditd_debug("__do_sethosts(%s), type=%d, 0=%x, 1=%x, 2=%x, 3=%x\n",
2N/A who,
2N/A audit_info.ai_termid.at_type,
2N/A audit_info.ai_termid.at_addr[0],
2N/A audit_info.ai_termid.at_addr[1],
2N/A audit_info.ai_termid.at_addr[2],
2N/A audit_info.ai_termid.at_addr[3]);
2N/A#endif /* DEBUG */
2N/A if ((audit_info.ai_termid.at_addr[0] != 0) &&
2N/A (audit_info.ai_termid.at_addr[0] != htonl(INADDR_LOOPBACK))) {
2N/A#ifdef DEBUG
2N/A __auditd_debug("__do_sethost(%s) already set\n", who);
2N/A#endif /* DEBUG */
2N/A return (0);
2N/A }
2N/A
2N/A /* Force a new lookup */
2N/A audit_info.ai_termid.at_type = AU_IPv4;
2N/A audit_info.ai_termid.at_addr[0] = 0;
2N/A audit_info.ai_termid.at_addr[1] = 0;
2N/A audit_info.ai_termid.at_addr[2] = 0;
2N/A audit_info.ai_termid.at_addr[3] = 0;
2N/A if (auditon(A_SETKAUDIT, (caddr_t)&audit_info,
2N/A sizeof (audit_info)) < 0) {
2N/A save_errno = errno;
2N/A __audit_syslog(who, LOG_PID | LOG_CONS | LOG_NOWAIT,
2N/A LOG_DAEMON, LOG_ALERT, "__do_sethost unable to set kernel "
2N/A "audit context %s", strerror(save_errno));
2N/A return (save_errno);
2N/A }
2N/A
2N/A errno = ENOTSUP; /* for termid == NULL */
2N/A /* get my terminal ID */
2N/A if (adt_load_hostname(NULL, (adt_termid_t **)&termid) < 0 ||
2N/A termid == NULL) {
2N/A save_errno = errno;
2N/A __auditd_debug("__do_sethost unable to get local IP "
2N/A "address: %s\n", strerror(errno));
2N/A if ((errno = save_errno) != ENETDOWN) {
2N/A free(termid);
2N/A return (errno);
2N/A }
2N/A }
2N/A
2N/A audit_info.ai_termid = *termid;
2N/A
2N/A /* Update the kernel audit info with new IP address */
2N/A if (auditon(A_SETKAUDIT, (caddr_t)&audit_info,
2N/A sizeof (audit_info)) < 0) {
2N/A save_errno = errno;
2N/A __audit_syslog(who, LOG_PID | LOG_CONS | LOG_NOWAIT,
2N/A LOG_DAEMON, LOG_ALERT, "__do_sethost unable to set kernel "
2N/A "audit context %s", strerror(save_errno));
2N/A }
2N/A free(termid);
2N/A return (save_errno);
2N/A}
2N/A
2N/A/*
2N/A * getshift() - get the multiplier of bytes based on the string describing the
2N/A * passed unit.
2N/A */
2N/Astatic boolean_t
2N/Agetshift(char *str, int *shift)
2N/A{
2N/A const char *ends = "BKMGTPEZ";
2N/A int i;
2N/A
2N/A if (str[0] == '\0') {
2N/A *shift = 0;
2N/A return (B_TRUE);
2N/A }
2N/A for (i = 0; i < strlen(ends); i++) {
2N/A if (toupper(str[0]) == ends[i]) {
2N/A break;
2N/A }
2N/A }
2N/A if (i == strlen(ends)) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A if (str[1] == '\0' || (toupper(str[1]) == 'B' && str[2] == '\0' &&
2N/A toupper(str[0]) != 'B')) {
2N/A *shift = i * 10;
2N/A return (B_TRUE);
2N/A }
2N/A
2N/A return (B_FALSE);
2N/A}
2N/A
2N/A/*
2N/A * __audit_hrstrtonum - translate human readable form of size (eg. "100K")
2N/A * to the actual number in bytes.
2N/A *
2N/A * The code has been borrowed from the libzfs:zfs_nicestrtonum() function.
2N/A */
2N/Aboolean_t
2N/A__audit_hrstrtonum(char *hrstr, uint64_t *num)
2N/A{
2N/A char *end;
2N/A int shift;
2N/A int save_errno = errno;
2N/A
2N/A if (isdigit(*hrstr) == 0 && *hrstr != '.') {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A errno = 0;
2N/A *num = strtoull(hrstr, &end, 10);
2N/A if (errno != 0) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A if (*end == '.') {
2N/A long double fval;
2N/A
2N/A fval = strtold(hrstr, &end);
2N/A
2N/A if (!getshift(end, &shift)) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A fval *= (1 << shift);
2N/A if (fval > UINT64_MAX) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A *num = (uint64_t)fval;
2N/A
2N/A } else {
2N/A if (!getshift(end, &shift)) {
2N/A return (B_FALSE);
2N/A }
2N/A if (shift >= 8 * sizeof (*num) ||
2N/A (*num << shift) >> shift != *num) {
2N/A return (B_FALSE);
2N/A }
2N/A
2N/A *num <<= shift;
2N/A }
2N/A
2N/A errno = save_errno;
2N/A return (B_TRUE);
2N/A}