/* $CoreSDI: auditd.c,v 1.41 2001/11/01 22:22:31 claudio Exp $ */ /* * Copyright (c) 2000, 2001, Core SDI S.A., Argentina * All rights reserved * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither name of the Core SDI S.A. nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Auditd - Audit Server Daemon * Author: Claudio Castiglia */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__FreeBSD__) && (__FreeBSD_version >= 500035) #include #endif #include "sysdep.h" #include "packet.h" #include "version.h" #include "audit.h" #include "audconf.h" #include "commands.h" #include "log.h" #include "resource.h" #include "modtypes.h" #include "modules.h" #include "ia_list.h" #include "resadmin.h" #include "authargs.h" #include "ia.h" #define OPTARGS "f:M:p:t:T:ldvqh?" AUDITD_OPTIONS options; /* Configuration */ int sfd; /* Listening socket */ int sighup = 0; /* 1 if SIGHUP received */ /* * create_pidfile(): * Create pidfile and write pid. */ void create_pidfile() { int fd; char cpid[21]; fd = open(options.pidfile, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd < 0) { fatal(EX_OSERR, "%s: %s.", options.pidfile, strerror(errno)); /* NOTREACHED */ } snprintf(cpid, sizeof(cpid), "%d", getpid()); if (write(fd, cpid, strlen(cpid)) <= 0) { fatal(EX_OSERR, "%s: %s.", options.pidfile, strerror(errno)); /* NOTREACHED */ } close(fd); } /* * signal_handler() */ void signal_handler(int signo) { int old_errno, status; old_errno = errno; switch(signo) { case SIGCHLD: waitpid(-1, &status, WNOHANG); break; case SIGHUP: sighup = 1; break; case SIGPIPE: errno = EPIPE; log_msg(LOG_INFO, "Connection closed: %s", strerror(errno)); exit(EX_IOERR); case SIGALRM: log_msg(LOG_INFO, "Connection closed: timeout."); bye: auditd_release_options(&options); /* XXX cleanup(), shutdown(2) */ exit(-1); case SIGABRT: case SIGINT: case SIGQUIT: case SIGTERM: log_msg(LOG_INFO, "Exiting on signal %d.", signo); /* * XXX: It should always cleanup for every exit(3) and * not only on fatal(). */ goto bye; } errno = old_errno; } /* * set_parent_signals(): * Set parent signal handler and its stack. */ int set_parent_signals() { struct sigaction sa; struct sigaltstack alt_stack; alt_stack.ss_sp = malloc(SIGSTKSZ); alt_stack.ss_size = SIGSTKSZ; alt_stack.ss_flags = 0; if (alt_stack.ss_sp == NULL) return (-1); sigemptyset(&sa.sa_mask); sigaddset(&sa.sa_mask, SIGCHLD); sigaddset(&sa.sa_mask, SIGHUP); sigaddset(&sa.sa_mask, SIGINT); sigaddset(&sa.sa_mask, SIGQUIT); sigaddset(&sa.sa_mask, SIGTERM); sigaddset(&sa.sa_mask, SIGABRT); sa.sa_handler = signal_handler; sa.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_ONSTACK; return (sigaltstack(&alt_stack, NULL) | sigaction(SIGCHLD, &sa, NULL) | sigaction(SIGHUP, &sa, NULL) | sigaction(SIGINT, &sa, NULL) | sigaction(SIGQUIT, &sa, NULL) | sigaction(SIGTERM, &sa, NULL) | sigaction(SIGABRT, &sa, NULL)); } /* * set_child_signals(): * Set childs signal handler. */ int set_child_signals() { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = signal_handler; sa.sa_flags = 0; sigaddset(&sa.sa_mask, SIGPIPE); sigaddset(&sa.sa_mask, SIGALRM); sigaction(SIGPIPE, &sa, NULL); sigaction(SIGALRM, &sa, NULL); sa.sa_handler = SIG_IGN; return (sigaction(SIGCHLD, &sa, NULL) | sigaction(SIGHUP, &sa, NULL) | sigaction(SIGINT, &sa, NULL)); } /* * _restart(): * Restart function, called after SIGHUP; executes a new * daemon and terminates current one; on error the current * daemon is terminated and the error message logged. */ static void _restart(char **argv) { log_msg(LOG_INFO, "SIGHUP received, restarting."); if (sfd >= 0) close(sfd); execv(argv[0], argv); log_err("Restart failed: %s: %s.", argv[0], strerror(errno)); exit(-1); } /* * send_error(): * Send error message to the client. */ void send_error(PACKET *packet, const char *fmt, ...) { char msg[LINE_MAX]; va_list ap; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); packet_put_int32(packet, CMD_ERROR); packet_put_string(packet, msg); } /* * internal_error(): * Send "Command aborted." message to the client. */ void internal_error(PACKET *packet) { send_error(packet, "Command aborted."); } /* * auth_error(): * Log authentication error message and send "Authentication failed" * to the client. */ void auth_error(PACKET *packet, const char *fmt, ...) { va_list ap; char msg[LINE_MAX]; va_start(ap, fmt); vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); log_err(msg); packet_put_int32(packet, CMD_ERROR); packet_put_string(packet, "Authentication failed."); } /* * send_string_list(): * Send given string list to the client. */ void send_string_list(PACKET *packet, int items, const char **list) { int i; for (i = 0; i < items; i++) packet_put_string(packet, list[i]); packet_put_string(packet, ""); } /* * auth_get_module_list(): * Send authentication module list to the client. */ void auth_get_module_list(PACKET *packet) { log_debug("Sending authentication module list."); packet_put_int32(packet, CMD_OK); send_string_list(packet, options.auth_modules, (const char **) options.auth_list); log_cmd("unknown user", "Ok", "AUTH MODULE LIST"); } /* * get_auth(): * Set specified authentication module, authenticate user, * and return authentication context; on error NULL is * returned (user authentication failure is considered an error). */ AUTHCON * get_auth(PACKET *packet) { AUTHCON *authcon; char buf[MAXPATHLEN]; int32_t command; int i; /* Receive command */ packet_get_int32(packet, &command); /* List auth module names */ if (command == CMD_IA_GET_MODULE_LIST) { auth_get_module_list(packet); return (NULL); } else { if (command != CMD_IA_SET_MODULE) return (NULL); } /* Receive authentication module name and check if it exists */ packet_get_string(packet, buf, sizeof(buf)); if (strlen(buf) == 0) return (NULL); for (i = 0; i < options.auth_modules; i++) if (!strcmp(buf, options.auth_list[i])) break; if (i == options.auth_modules) { auth_error(packet, "Incorrect '%s' authentication module.", buf); return (NULL); } /* Load authentication module */ if ( (authcon = load_auth(options.auth_list[i], packet)) == NULL) { auth_error(packet, "Unable to load '%s' authentication module.", buf); return (NULL); } /* Authentication module successfully loaded */ packet_put_int32(packet, CMD_OK); /* Authentication process */ if (authcon->mod.proc_entry(AUTH_SERVER, authcon, NULL) < 0) { auth_error(packet, "Login for %s failed.", (authcon->peername[0] != '\0') ? authcon->peername : "unknown user"); release_auth(authcon); return (NULL); } /* Load user resources (if not loaded by authentication module) */ if (authcon->rlist == NULL) { authcon->rlist = res_open_list(authcon->peername, RES_READ); if (authcon->rlist == NULL) { auth_error(packet, "Can't load resources for user '%s':" " %s.", authcon->peername, strerror(errno)); release_auth(authcon); return (NULL); } } log_msg(LOG_INFO, "Starting session for %s.", authcon->peername); return (authcon); } /* * Talk w/client */ void talk(PACKET *packet, struct sockaddr_in *caddr) { SESSION session; struct hostent *h; char cip[INET_ADDRSTRLEN], msg[LINE_MAX]; int32_t c; /* Get peer address */ inet_ntop(AF_INET, &caddr->sin_addr, cip, sizeof(cip)); h = gethostbyaddr((char *) &caddr->sin_addr, sizeof(caddr->sin_addr), AF_INET); log_msg(LOG_INFO, "Connection from %s [ %s ].", (h == NULL) ? "" : h->h_name, cip); bzero((void *) &session, sizeof(session)); alarm(options.timeout); if ( (session.aucon = get_auth(packet)) != NULL) { alarm(options.command_timeout); while (1) { packet_get_int32(packet, &c); if (c == CMD_QUIT) break; alarm(0); switch (c) { case CMD_IA_GET_MODULE_LIST: ia_get_module_list(&session); break; case CMD_IA_SET_MODULE: ia_set_module(&session); break; case CMD_IA_GET_LIST: ia_get_list(&session); break; case CMD_IA_GET_INFO: ia_get_info(&session); break; case CMD_IA_GET: ia_get(&session); break; case CMD_IA_ZAP: ia_zap(&session); break; case CMD_IA_ROTATE: ia_rotate(&session); break; case CMD_IA_SIGN: ia_sign(&session); break; case CMD_RES_ADDUSER: resadmin_adduser(&session); break; case CMD_RES_RMUSER: resadmin_rmuser(&session); break; case CMD_RES_SET: resadmin_set(&session); break; case CMD_RES_FIND: resadmin_find(&session); break; case CMD_RES_REMOVE: resadmin_remove(&session); break; default: snprintf(msg, sizeof(msg), "Command [ %04X ] " "not supported.", c); log_err(msg); packet_put_int32(packet, CMD_EOPNOTSUPP); packet_put_string(packet, msg); } alarm(options.command_timeout); } release_ia(session.iacon); /* first IA, then AUTH */ release_auth(session.aucon); } alarm(0); log_msg(LOG_INFO, "Connection closed."); } /* * usage() */ void usage() { extern char *__progname; fprintf(stderr, "Auditd version %s\n" "Usage: %s [options]\n" "Options:\n" " -f file Config file (default is %s).\n" " -M path Modules path (default is %s).\n" " -p [add:]port Address and port to listen (default is\n" " to listen on all addresses and port %d).\n" " -t seconds Connection timeout (default is %d).\n" " -T seconds Command timeout (default is %d).\n" " -l Log each audit(1) session.\n" " -d Log debug information.\n" " -v Verbose mode: don't become a daemon and log\n" " messages on stderr instead of syslog(3).\n" " -q Quiet mode (disables -l, -d, and -v options).\n" " -h This help.\n", AUDIT_VERSION, __progname, DEFAULT_CONFIG_FILE, DEFAULT_MODULES_PATH, DEFAULT_AUDIT_PORT, DEFAULT_TIMEOUT, DEFAULT_COMMAND_TIMEOUT); exit(EX_USAGE); } /* * main() */ int main(int argc, char **argv) { int c, old_optind, cfd; struct pollfd pfd; pid_t pid; struct sockaddr_in caddr; socklen_t caddr_len; char *p, laddress[INET_ADDRSTRLEN]; extern int optind; extern char *optarg; /* Load default options */ auditd_set_default_options(&options); old_optind = optind; while ( (c = getopt(argc, argv, OPTARGS)) != -1) switch(c) { case 'f': /* Config file */ if (auditd_set_option(&options, AOC_CONFIG_FILE, optarg) < 0) errx(-1, "Invalid -f argument."); break; case 'M': /* Modules path */ case 'p': /* Listen port */ case 't': /* Login timeout */ case 'T': /* Command timeout */ case 'l': /* Log session messages */ break; case 'd': /* Log debug messages */ auditd_set_option(&options, AOC_LOG_DEBUG, "yes"); break; case 'v': /* Verbose mode: no daemon */ auditd_set_option(&options, AOC_LOG_VERBOSE, "yes"); break; case 'q': /* Quiet mode */ auditd_set_option(&options, AOC_LOG_QUIET, "yes"); break; case 'h': /* Help */ case '?': default: usage(); } if (argc - optind < 0) usage(); /* Init log system for messages from auditd_load_configuration() */ log_init(options.flags, options.sysfac); if (auditd_load_configuration(&options) < 0) exit(EX_CONFIG); optind = old_optind; while ( (c = getopt(argc, argv, OPTARGS)) != -1) switch (c) { case 'f': /* Config file */ break; case 'M': /* Modules path */ if (auditd_set_option(&options, AOC_MODULES_PATH, optarg) < 0) errx(-1, "Invalid -M argument."); break; case 'p': /* Listen address and port "-p [address:]port" */ p = optarg; strsep(&p, ":"); if (p == NULL) p = optarg; else { if (auditd_set_option(&options, AOC_ADDRESS, optarg) < 0) errx(-1, "Invalid -p argument."); } if (auditd_set_option(&options, AOC_PORT, p) < 0) errx(-1, "Invalid -p argument."); break; case 't': /* Login timeout */ if (auditd_set_option(&options, AOC_TIMEOUT, optarg) < 0) errx(-1, "Ivalid -t argument."); break; case 'T': /* Command timeout */ if (auditd_set_option(&options, AOC_COMMAND_TIMEOUT, optarg) < 0) errx(-1, "Invalid -T argument."); break; case 'l': /* Log session messages */ auditd_set_option(&options, AOC_LOG_SESSION, "yes"); break; case 'd': /* Log debug messages */ auditd_set_option(&options, AOC_LOG_DEBUG, "yes"); break; case 'v': /* Verbose mode: do not daemonize */ auditd_set_option(&options, AOC_LOG_VERBOSE, "yes"); break; case 'q': /* Quiet mode */ auditd_set_option(&options, AOC_LOG_QUIET, "yes"); break; case 'h': /* Help */ case '?': default: usage(); } if (set_parent_signals() != 0) fatal(EX_OSERR, "Can't set signal handlers: %s.", strerror(errno)); /* Become a daemon */ if (!(options.flags & LOGF_STDERR)) daemon(0, 0); /* Init log system */ log_init(options.flags, options.sysfac); /* Load resources module */ if (res_module_init(options.resmodule) < 0) fatal(EX_SOFTWARE, ""); /* Load libcrypto lookup table and error strings */ init_cryptostrings(); /* Start auditd */ if ( (sfd = init_socket()) < 0) fatal(EX_OSERR, "Can't initialize socket: %s.", strerror(errno)); c = 1; setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &c, sizeof(c)); if (bind(sfd, (struct sockaddr *) &options.addr, sizeof(options.addr)) < 0) fatal(EX_OSERR, "Can't bind: %s.", strerror(errno)); if (listen(sfd, 5) < 0) fatal(EX_OSERR, "Can't listen: %s.", strerror(errno)); pfd.fd = sfd; pfd.events = POLLIN; create_pidfile(); /* Log start message */ log_msg(LOG_INFO, "Starting auditd, listening on %s, port %d.", inet_ntop(options.addr.sin_family, &options.addr.sin_addr.s_addr, laddress, sizeof(laddress)), ntohs(options.addr.sin_port)); /* Daemon */ while (1) { if (sighup) _restart(argv); if (poll(&pfd, 1, -1) < 0) switch(errno) { case EINTR: case EAGAIN: continue; default: fatal(-1, "Can't poll: %s.", strerror(errno)); /* NOTREACHED */ } caddr_len = sizeof(caddr); cfd = accept(sfd, (struct sockaddr *) &caddr, &caddr_len); if (cfd < 0) { /* XXX: ECONNABORTED (solaris) */ if (errno != EINTR && errno != ECONNABORTED && errno != EWOULDBLOCK && errno != EAGAIN) { fatal(EX_OSERR, "Can't accept: %s.", strerror(errno)); /* NOTREACHED */ } else continue; } /* Do not fork on verbose mode */ if (!(options.flags & LOGF_STDERR)) { if ( (pid = fork()) < 0) fatal(EX_OSERR, "Can't fork: %s.", strerror(errno)); else cleanup_rm_all(); /* remove parent cleanups */ } else pid = 0; if (pid == 0) { PACKET *packet; close(sfd); if (set_child_signals() != 0) { fatal(EX_OSERR, "Can't set child signal " "handlers: %s.", strerror(errno)); /* NOTREACHED */ } packet = packet_init(cfd, cfd); if (packet == NULL) { fatal(EX_OSERR, "Packet initialization: %s.", strerror(errno)); /* NOTREACHED */ } if (!compatible_peer(packet)) { fatal(EX_PROTOCOL, "Incompatible client version."); /* NOTREACHED */ } talk(packet, &caddr); packet_release(packet); close(cfd); release_cryptostrings(); exit(EX_OK); } close(cfd); { int i; wait(&i); /* * XXX: avoid race condition * when accessing same logset: * Simultaneous auditors not supported yet. */ } } return (0); }