#ifndef lint static char *sccsid = "@(#)auditd.c 1.1 94/10/31 SMI"; /* Copywr SunMicro */ #endif /* * Copyright (c) 1987 Sun Microsystems, Inc. */ /* Audit daemon server */ /**************************************************************************** * These routines make up the audit daemon server. This daemon, called * auditd, handles the user level parts of auditing. For the most part * auditd will be in the audit_svc system call; otherwise, it is handling * the interrupts and errors from the system call and handling the audit * log files. * * The major interrupts are SIGHUP (start over), SIGTERM (start shutting * down), SIGALRM (quit), and SIGUSR1 (start a new audit log file). * * The major errors are EBUSY (auditing is already in use), EINVAL (someone * has turned auditing off), EINTR (one of the above signals was received), * and EDQUOT (the directory is out of space). All other errors are treated * the same as EDQUOT. * * Much of the work of the audit daemon is taking care of when the * directories fill up. In the audit_control file, there is a value * min_free which determines a "soft limit" for what percentage of space * to reserve on the file systems. This code deals with the cases where * one file systems is at this limit (soft), all the file systems are at * this limit (allsoft), one of the file systems is completely full (hard), * and all of the file systems are completely full (allhard). The * audit daemon warns the administrator about these and other problems * using the auditwarn shell script. ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * DEFINES: */ #define MACH_NAME_SZ 64 #define AUDIT_DATE_SZ 14 #define AUDIT_FNAME_SZ 2 * AUDIT_DATE_SZ + 2 + MACH_NAME_SZ #define MYMASK sigmask(~SIGTERM) /* catch only SIGTERM */ #define SOFT_LIMIT 0 /* used by the limit flag */ #define HARD_LIMIT 1 #define SOFT_SPACE 0 /* used in the dirlist_s structure for */ #define HARD_SPACE 1 /* how much space there is in the */ #define SPACE_FULL 2 /* filesystem */ #define STAY_FULL 3 #define AVAIL_MIN 50 /* If there are less that this number */ /* of blocks avail, the filesystem is */ /* presumed full. May need tweeking. */ /* * After we get a SIGTERM, we want to set a timer for 2 seconds and * call do_auditing() to let the auditsvc syscall write as many records * as it can until the timer goes off (at which point it returns to * auditd with SIGALRM. */ #define ALRM_TIME 2 #define SLEEP_TIME 20 /* # of seconds to sleep in all hard loop */ /* DATA STRUCTURES: */ /* * The directory list is a circular linked list. It is pointed into by * startdir and thisdir. Each element contains the pointer to the next * element, the directory pathname and a flag for how much space there is * in the directory's filesystem. */ struct dirlist_s { struct dirlist_s *dl_next; int dl_space; char *dl_name; }; typedef struct dirlist_s dirlist_t; /* * The logfile contains a file descriptor, a directory pathname, * and a filename. */ struct logfile_s { int l_fd; char *l_name; }; typedef struct logfile_s logfile_t; /* * GLOBALS: * startdir pointer to the "start" of the dir list * thisdir pointer into dir list for the current directory * logfile points to struct with info of current log file * force_close true if the user explicitly asked to close the log file * minfree the lower bound percentage read from audit_control * minfreeblocks tells audit_svc what the soft limit is in blocks * limit whether we are using soft or hard limits * errno error returned by audit_svc (and other syscalls) * sig_num number for which signal was received * ljmp used to close a "window" for signals. comment below. * dbflag debug flag (option to auditd) * nflag no fork flag (option to auditd) used for dbx * hung_count count for how many times sent message for all hard * opened whether the open was successful * the files these are the four files auditing uses in addition * to the audit log files */ dirlist_t *startdir; dirlist_t *thisdir; logfile_t *logfile; int force_close; int minfree; int minfreeblocks; int limit = SOFT_LIMIT; extern int errno; int sig_num = 0; jmp_buf ljmp; int dbflag; int nflag; int hung_count; int opened = 1; char auditcontrol[] = "/etc/security/audit/audit_control"; char auditdata[] = "/etc/security/audit/audit_data"; char tempfile[] = "/etc/security/audit/audit_tmp"; char auditwarn[] = "/usr/etc/audit_warn"; char *malloc(); char *memcpy(); char *strcpy(); char *sprintf(); /* * SIGNAL HANDLERS: */ /************************************************************************ * catch() is the signal handler used for SIGHUP, SIGTERM, and SIGALRM * which returns the code to the _setjmp() inside do_auditing(). ***********************************************************************/ void catch(sig) { sig_num = sig; (void)sigsetmask(MYMASK); if (dbflag) (void)fprintf(stderr, "auditd: caught signal %d\n", sig_num); _longjmp(ljmp, 1); } /************************************************************************ * finish() is the signal handler for SIGTERM that is used to * terminate the audit daemon gracefully. It will return us * to the auditsvc syscall for ALRM_TIME seconds (defined above). ***********************************************************************/ void finish() { int audit_break = 0; int reset_list = 0; (void)sigsetmask(MYMASK); if (dbflag) (void)fprintf(stderr, "auditd: processing SIGTERM\n"); if (hung_count > 0) { /* * There is no space to write audit records * so just shutdown here. */ if (dbflag) (void)fprintf(stderr, "auditd: terminating the daemon.\n"); auditon(AUC_NOAUDIT); exit(0); } if (logfile == (logfile_t *)0) { /* This should never happen. */ if (dbflag) (void)fprintf(stderr, "auditd: terminating daemon - no logfile open.\n"); auditon(AUC_NOAUDIT); exit(0); } (void)alarm(ALRM_TIME); /* sets timer to signal auditsvc */ if (dbflag) (void)fprintf(stderr, "auditd: alarm set for %d secs\n", ALRM_TIME); minfreeblocks = 0; do_auditing(minfreeblocks, &audit_break, &reset_list); /* * if do_auditing() returns to here, we came out of * auditsvc() for some other reason than SIGALRM. For * now, just exit. */ (void)dowarn("postsigterm", "", 0); auditon(AUC_NOAUDIT); exit(0); } main(argc, argv) int argc; char *argv[]; { char*s; /* used for processing arguments */ struct sigvec sv; /* used to set up signal handling */ int fd; /* used to remove the control tty */ int reset_list; /* 1 if user wants to re-read audit_control */ /* * Make sure I'm not being audited, and won't be in the future. */ if (setauid(AU_NOAUDITID) < 0) (void)fprintf(stderr, "auditd: setauid error %d", errno); /* * Check and process arguments. */ while (--argc && *(s = *++argv) == '-') { switch(*++s) { case 'd': dbflag++; break; case 'n': nflag++; break; default: (void)fprintf(stderr, "auditd: Unknown flag '%c'\n", *s); exit(1); } } if (argc > 0) { (void)fprintf(stderr, "auditd: usage: auditd [-dn]\n", argv[0]); exit(1); } /* * Only allow super-user to invoke this program */ if (getuid() != 0) { (void)fprintf(stderr, "auditd: must be super-user to run this program\n"); exit(1); } /* * Set the process group to something safe */ if (!nflag && setpgrp(0, getpid()) < 0) { /* this should never happen */ (void)fprintf(stderr, "auditd: unable to set process group\n"); exit(1); } /* * Make sure that we are running on a secure system. */ if (!issecure()) { /* * set audit state to NOAUDIT so people can do fchroot() or * fchdir() if they wish. The return is ignored since the only * reason auditon() would fail is if the state is FCHDONE which * is unexpected but fine. */ auditon(AUC_NOAUDIT); exit(0); } /* * Set the audit state flag to AUDITING. If we can't, fchroot() or * fchdir() has been done. Auditing can't start until the system is * rebooted. Use auditwarn to inform the administrator. */ if (auditon(AUC_AUDITING) < 0) { if (dbflag) perror("auditd"); (void)dowarn("nostart", "", 0); exit(0); } /* * Remove the control tty */ if ((fd = open("/dev/tty", 0, O_RDWR)) > 0) { (void) ioctl(fd, TIOCNOTTY, 0); (void) close(fd); } /* * Fork child process and abandon it (it gets inherited by init) */ if (!nflag && fork()) exit(0); if (dbflag) { (void)fprintf(stderr, "auditd: Effective uid is: %d\n", geteuid()); (void)fprintf(stderr, "auditd: Real uid is: %d\n", getuid()); } setlinebuf(stderr); setlinebuf(stdout); /* * Set the umask so that only audit or other users in the audit group * can get to the files created by auditd. */ (void)umask(027); /* * Set up signal handling. */ (void)sigsetmask(MYMASK); /* Block all signals except SIGTERM */ sv.sv_handler = catch; sv.sv_mask = -1; sv.sv_flags = SV_INTERRUPT; /* Catch signals */ (void)sigvec(SIGHUP, &sv, (struct sigvec *)0); (void)sigvec(SIGUSR1, &sv, (struct sigvec *)0); (void)sigvec(SIGALRM, &sv, (struct sigvec *)0); /* Special handler for SIGTERM */ sv.sv_handler = finish; (void)sigvec(SIGTERM, &sv, (struct sigvec *)0); /* * Here is the main body of the audit daemon. This while loop is * where the code will return if a SIGHUP is received. */ while (1) { loadauditlist(); /* read in the directory list */ /* sets thisdir to the first dir */ force_close = 0; hung_count = 0; limit = HARD_LIMIT; /* * Call process_audit to do the auditing and the handling * of space overflows until reset_list is true which * means the user wants to reread the audit_control file. */ reset_list = 0; while (!reset_list) (void)process_audit(&reset_list); } /* end of while (1) loop*/ } /* end of main */ /******************************************************************* * process_audit - handle auditing and space overflows ******************************************************************/ process_audit(reset_list) int *reset_list; { int audit_break = 0; /* whether we were broken out of do_auditing() */ /* * allhard determines whether all of the filesystems are full. * It is set to 1 (true) until a filesystem is found which has * space, at which point it is set to 0. */ int allhard = 1; int firsttime; /* used when searching for hard space */ if (dbflag) (void)fprintf(stderr, "auditd: inside process_audit\n"); startdir = thisdir; do { /* * test whether thisdir is under the soft limit */ if (testspace(thisdir, SOFT_SPACE)) { limit = SOFT_LIMIT; /* * open_log calls close_log. If the open fails, it * sets thisdir to the next directory in the list */ if (open_log()) { /* * We finally call audit_svc here. */ allhard = 0; do_auditing(minfreeblocks, &audit_break, reset_list); } } else { if (thisdir->dl_space == HARD_SPACE) allhard = 0; /* * Go to the next directory */ thisdir = thisdir->dl_next; } } while (thisdir != startdir && !(audit_break || *reset_list)); if (*reset_list || audit_break) return; if (thisdir == startdir) { if (allhard) { handleallhard(reset_list); } else { if (limit == SOFT_LIMIT) (void)dowarn("allsoft", "", 0); /* * Find the first directory that has * hard space and open a new logfile. * * Complications: * Open may fail which may mean that * there is no more space anywhere. * Also, if they explicitly want to * change files, we need to do so. */ opened = 0; firsttime = 1; while (firsttime || thisdir != startdir) { firsttime = 0; if (thisdir->dl_space == HARD_SPACE) { if (thisdir == startdir && logfile != (logfile_t *)0 && !force_close) { /* use the file already open */ if (dbflag) (void)fprintf(stderr, "auditd: using the same logfile %s\n", thisdir->dl_name); opened = 1; break; } else if (opened = open_log()) { break; } else { if (dbflag) (void)fprintf(stderr, "can't open %s", thisdir->dl_name); thisdir = thisdir->dl_next; } } else { thisdir = thisdir->dl_next; } } limit = HARD_LIMIT; if (opened) { minfreeblocks = 0; do_auditing(minfreeblocks, &audit_break, reset_list); } else { /* allhard is true */ if (dbflag) (void)fprintf(stderr, "auditd: allhard because open failed\n"); handleallhard(reset_list); } } /* end of else (if (allhard)) */ } /* end of if (thisdir == startdir) */ } /* end of process_audit() */ /********************************************************************** * do_auditing - set up for calling audit_svc and handle it's various * returns. * globals: * ljmp - used for _setjmp and _longjmp (see comment below) **********************************************************************/ do_auditing(minfreeblocks, audit_break, reset_list) int minfreeblocks; int *audit_break; int *reset_list; { int error = 0; /* used to save the errno */ *audit_break = 1; *reset_list = 0; force_close = 0; /* this is a global variable */ if (limit == HARD_LIMIT) { if (dbflag) (void)fprintf(stderr, "auditd: hard limit, set minfreeblocks to 0\n"); minfreeblocks = 0; } /* * audit_svc is put inside an if (!_setjmp(ljmp)) because there * is a window in between turning on signalling and actually getting * inside the system call (where we really want the signal catching * to happen) that we need to close. If a signal comes in at this * time, the last line of the signal handler, catch(), will do a * _longjmp back to this test of _setjmp with ljmp set to 1. This * will get us to the else case, and we are all set. */ if (!_setjmp(ljmp)) { if (dbflag) (void)fprintf(stderr, "auditd: Begin audit service with %s; limit = %d blocks.\n", logfile->l_name, minfreeblocks); (void)sigsetmask(0); auditsvc(logfile->l_fd, minfreeblocks); error = errno; (void)sigsetmask(MYMASK); } else { if (dbflag) (void)fprintf(stderr, "auditd: Caught signal outside of auditsvc\n"); error = EINTR; } switch (error) { case EBUSY: if (dbflag) (void)fprintf(stderr, "auditd: already in use.\n"); (void)dowarn("ebusy", "", 0); exit(1); case EINVAL: /* * who knows what we should do here - someone has turned * auditing off unexpectedly - for now we will exit */ if (dbflag) (void)fprintf(stderr, "auditd: audit state is not AUDITING.\n"); (void)dowarn("auditoff", "", 0); exit(1); case EINTR: switch (sig_num) { case SIGTERM: /* * Should not get here. The signal handler does not * return. */ if (dbflag) (void)fprintf(stderr, "auditd: SIGTERM badly handled.\n"); exit(1); case SIGALRM: /* * We have returned from our timed entrance into * auditsvc(). We need to really shut down here. */ if (dbflag) (void)fprintf(stderr, "auditd: processing SIGALRM\n"); if (logfile) { (void)close_log(logfile, "", ""); } /* * Set audit state to NOAUDIT so that people can do * fchroot() or fchdir() if they wish. I don't check * the return since the only reason auditon() would * fail is if the state is FCHDONE which * is unexpected but fine. */ auditon(AUC_NOAUDIT); if (dbflag) (void)fprintf(stderr, "auditd: daemon terminating\n"); exit(0); case SIGHUP: /* * They want to reread the audit_control file. Set * reset_list which will return us to the main while * loop in the main routine. */ if (dbflag) (void)fprintf(stderr, "auditd: processing SIGHUP.\n"); *reset_list = 1; break; case SIGUSR1: /* * In every normal case, we could ignore this case * because the normal behavior of the rest of the * code is to switch to a new log file before getting * back into audit_svc. There is one exception - * if all the filesystems are at the soft limit, * the normal behavior is to keep using the same * log file in audit_svc until this file system * fills up. Since our user is explicitly asking * for a new file, we must make sure in that case * that a new file is used. */ if (dbflag) (void)fprintf(stderr, "auditd: processing SIGUSR1.\n"); force_close = 1; break; default: /* * Should not get here since we only catch SIGTERM, * SIGALRM, SIGHUP, and SIGUSR1. */ if (dbflag) (void)fprintf(stderr, "auditd: Signal %d ignored.\n", sig_num); break; } break; case EDQUOT: /* * Update the structure for this directory to have the * correct space value (HARD_SPACE or SPACE_FULL). Nothing * further needs to be done here, the main routine will * worry about finding space (here or elsewhere). */ if (dbflag) (void)fprintf(stderr, "auditd: processing EDQUOT.\n"); thisdir->dl_space = (limit == SOFT_LIMIT) ? HARD_SPACE : SPACE_FULL; break; case EIO: case ENETDOWN: case ENETUNREACH: case ECONNRESET: case ETIMEDOUT: case EHOSTDOWN: case EHOSTUNREACH: case ESTALE: /* * If any of the errors are returned, the filesystem is * unusable. Make sure that it does not try to use it * the next time that testspace is called. After that, * it will try to use it again since the problem may have * been solved. */ if (dbflag) (void)fprintf(stderr, "auditd: error %d: %s unusable\n", error, thisdir->dl_name); thisdir->dl_space = STAY_FULL; break; default: /* * Correct the space portain of the structure for this * directory. Throw away the return value from testspace. */ (void)testspace(thisdir, SPACE_FULL); if (dbflag) { errno = error; perror("auditd: Error ignored"); } break; } /* * We treat any of the signals or errors that get us here except * for SIGHUP and SIGUSR1 as if we have run out of space. */ if (!*reset_list && !force_close) { if (limit == SOFT_LIMIT) (void)dowarn("soft", logfile->l_name, 0); else (void)dowarn("hard", logfile->l_name, 0); } hung_count = 0; } /*********************************************************************** * handleallhard - handle the calling of dowarn, the sleeping, etc * for the allhard case * globals: * hung_count - how many times dowarn has been called for the * allhard case. * logfile - the current logfile to close and reinitialize. * arguments: * reset_list - set if SIGHUP was received in my_sleep **********************************************************************/ handleallhard(reset_list) int *reset_list; { ++hung_count; if (hung_count == 1) { (void)close_log(logfile, "", ""); logfile = (logfile_t *)0; (void)logpost(getpid(), ""); } (void)dowarn("allhard", "", hung_count); my_sleep(reset_list); } /********************************************************************** * my_sleep - sleep for SLEEP_TIME seconds but only accept the signals * that we want to accept * * arguments: * reset_list - set if SIGHUP was received **********************************************************************/ my_sleep(reset_list) int *reset_list; { if (!_setjmp(ljmp)) { if (dbflag) (void)fprintf(stderr, "auditd: sleeping for 20 seconds\n"); (void)alarm(SLEEP_TIME); /* set timer to "sleep" */ sigpause(0); /* the 0 says to allow all signals */ /* We should never get here!! */ (void)fprintf(stderr, "auditd: We should never get here (after sigpause)!\n"); exit(1); } else { /* Handle the signal from sigpause */ switch (sig_num) { case SIGTERM: /* We should never get here!! */ if (dbflag) (void)fprintf(stderr, "auditd: SIGTERM mishandled\n"); (void)alarm(0); exit(1); case SIGHUP: /* They want to reread the audit_control file */ if (dbflag) (void)fprintf(stderr, "auditd: processing SIGHUP.\n"); (void)alarm(0); *reset_list = 1; break; case SIGALRM: /* We slept for SLEEP_TIME seconds */ break; case SIGUSR1: /* * We can ignore this since the current * logfile has already been closed. */ (void)alarm(0); break; default: /* We shouldn't get here, ignore it */ (void)alarm(0); break; } } } /********************************************************************** * open_log - open a new logfile in the current directory. If a * logfile is already open, close it. * * globals: * thisdir - used to determine where to put the new file * logfile - used to get the oldfile name (and change it), * to close the oldfile and then is set to the newfile **********************************************************************/ int open_log() { char auditdate[AUDIT_DATE_SZ+1]; char host[MACH_NAME_SZ+1]; char oldname[AUDIT_FNAME_SZ+1]; char *name; /* pointer into oldname */ char *rindex(); logfile_t *newlog = (logfile_t *)0; int opened; int error = 0; struct timeval tv; register audit_header_t *ah; register unsigned len; register unsigned ahlen; if (dbflag) (void)fprintf(stderr, "auditd: inside open_log for dir %s\n", thisdir->dl_name); /* make ourselves a new logfile structure */ newlog = (logfile_t *)malloc(sizeof(logfile_t)); if (newlog == NULL) { perror("auditd"); exit(1); } newlog->l_name = (char *)malloc(AUDIT_FNAME_SZ); if (newlog->l_name == NULL) { perror("auditd"); exit(1); } (void)gethostname(host, MACH_NAME_SZ); /* loop to get a filename which does not already exist */ opened = 0; while (!opened) { getauditdate(auditdate); (void)sprintf(newlog->l_name, "%s/%s.not_terminated.%s", thisdir->dl_name, auditdate, host); newlog->l_fd = open(newlog->l_name, O_RDWR|O_APPEND|O_CREAT|O_EXCL, 0600); if (newlog->l_fd < 0) { switch (errno) { case EEXIST: sleep(1); break; default: /* open failed */ if (dbflag) perror("auditd"); thisdir->dl_space = SPACE_FULL; thisdir = thisdir->dl_next; return (0); } /* switch */ } else opened = 1; } /* while */ /* * When we get here, we have opened our new log file. * Now we need to update the name of the old file to * store in this file's header */ if (logfile) { /* set up oldname, but do not change logfile */ (void)strcpy(oldname, logfile->l_name); name = rindex(oldname, '/') + 1; (void)memcpy(name + AUDIT_DATE_SZ + 1, auditdate, AUDIT_DATE_SZ); if (dbflag) (void)fprintf(stderr, "auditd: revised oldname is %s\n", oldname); } else (void)strcpy(oldname, ""); /* * Write Audit Log header record */ (void)gettimeofday(&tv, (struct timezone *)0); len = (strlen(oldname) + 2) & ~1; ahlen = sizeof(audit_header_t) + len; ah = (audit_header_t *)malloc(ahlen); if (ah == NULL) { perror("auditd"); exit(1); } ah->ah_magic = AUDITMAGIC; ah->ah_time = tv.tv_sec; ah->ah_namelen = len; (void)strcpy((char *)&ah[1], oldname); if (write(newlog->l_fd, (caddr_t)ah, (int)ahlen) < 0) error = errno; free((caddr_t)ah); if (error) { if (dbflag) perror("auditd"); (void)close(newlog->l_fd); free((caddr_t)newlog->l_name); free((caddr_t)newlog); thisdir->dl_space = SPACE_FULL; thisdir = thisdir->dl_next; return(0); } (void)close_log(logfile, oldname, newlog->l_name); logfile = newlog; (void)logpost(getpid(), logfile->l_name); startdir = thisdir; if (dbflag) (void)fprintf(stderr, "auditd: Log opened: %s\n", logfile->l_name); return (1); } /********************************************************************* * close_log - close the logfile if open. Also put the name of the * new log file in the trailer, and rename the old file * to oldname * arguments - * logfile - the logfile structure to close (and free) * oldname - the new name for the file to be closed * newname - the name of the new log file (for the trailer) *********************************************************************/ close_log(logfile, oname, newname) register logfile_t *logfile; char *oname; char *newname; { char auditdate[AUDIT_DATE_SZ+1]; register audit_trailer_t *at; unsigned len; unsigned atlen; struct timeval tv; char *name; char *rindex(); char oldname[AUDIT_FNAME_SZ+1]; if (dbflag) (void)fprintf(stderr, "auditd: inside close_log\n"); /* * If there is not a file open, return. */ if (!logfile) { return; } /* * If oldname is blank, we were called by the main routine * instead of by open_log, so we need to update our name. */ (void)strcpy(oldname, oname); if (strcmp(oldname, "") == 0) { getauditdate(auditdate); (void)strcpy(oldname, logfile->l_name); name = rindex(oldname, '/') + 1; (void)memcpy(name + AUDIT_DATE_SZ + 1, auditdate, AUDIT_DATE_SZ); if (dbflag) (void)fprintf(stderr, "auditd: revised oldname is %s\n", oldname); } /* * Write the trailer record and rename and close the file. * If any of the write, rename, or close fail, ignore it * since there is not much else we can do. */ (void)gettimeofday(&tv, (struct timezone *)0); len = (strlen(newname) + 2) & ~1; atlen = sizeof(audit_trailer_t) + len; at = (audit_trailer_t *)malloc(atlen); if (at == NULL) { perror("auditd"); exit(1); } at->at_record_size = atlen; at->at_record_type = AUR_TRAILER; at->at_time = tv.tv_sec; at->at_namelen = len; (void)strcpy((char *)&at[1], newname); (void)write(logfile->l_fd, (caddr_t)at, (int)atlen); free((caddr_t)at); (void)close(logfile->l_fd); (void)rename(logfile->l_name, oldname); if (dbflag) (void)fprintf(stderr, "auditd: Log closed %s\n", logfile->l_name); free((caddr_t)logfile->l_name); free((caddr_t)logfile); } /******************************************************************** * getauditdate - get the current time (GMT) and put it in the form * yyyymmddHHMMSS . ********************************************************************/ getauditdate(date) char *date; { struct timeval tp; struct timezone tzp; struct tm tm, *gmtime(); if (dbflag) (void)fprintf(stderr, "auditd: inside getauditdate\n"); (void)gettimeofday(&tp, &tzp); tm = *gmtime(&tp.tv_sec); /* * NOTE: if we want to use gmtime, we have to be aware that the * structure only keeps the year as an offset from TM_YEAR_BASE. * I have used TM_YEAR_BASE in this code so that if they change * this base from 1900 to 2000, it will hopefully mean that this * code does not have to change. TM_YEAR_BASE is defined in * tzfile.h . */ (void)sprintf(date, "%.4d%.2d%.2d%.2d%.2d%.2d", tm.tm_year + TM_YEAR_BASE, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); if (dbflag) (void)fprintf(stderr, "auditd: auditdate = %s\n", date); } /************************************************************************** * loadauditlist - read the directory list from the audit_control file. * Creates a circular linked list of directories. Also * gets the minfree value from the file. * globals - * thisdir and startdir - both are left pointing to the start of the * directory list * minfree - the soft limit value to use when filling the directories **************************************************************************/ loadauditlist() { char buf[200]; dirlist_t *node; dirlist_t **nodep; int acresult; int temp; int wait_count; if (dbflag) (void)fprintf(stderr, "auditd: Loading audit list from %s\n", auditcontrol); /* * Free up the old directory list */ if (startdir) { thisdir = startdir->dl_next; while (thisdir != startdir) { node = thisdir->dl_next; free((caddr_t)thisdir); thisdir = node; } free((caddr_t)startdir); } /* * Build new directory list */ nodep = &startdir; wait_count = 0; while ((acresult = getacdir(buf, sizeof(buf))) != -1) { if (acresult != 0) { /* * there was a problem getting the directory * list from the audit_control file */ if (dbflag && ++wait_count == 1) (void)fprintf(stderr, "auditd: problem getting directory list from audit_control.\n"); (void)dowarn("getacdir", "", wait_count); /* * sleep for SLEEP_TIME seconds - temp would * be set if the user asked us to re-read the * audit_control file during the sleep. Since * that is exactly what we are trying to do, * this value can be ignored. */ if (wait_count == 1) (void)logpost(getpid(), ""); my_sleep(&temp); continue; } node = (dirlist_t *)malloc(sizeof(dirlist_t)); if (node == NULL) { perror("auditd"); exit(1); } node->dl_name = (char *)malloc((unsigned)strlen(buf) + 1); if (node->dl_name == NULL) { perror("auditd"); exit(1); } (void)strcpy(node->dl_name, buf); node->dl_next = 0; *nodep = node; nodep = &(node->dl_next); } node->dl_next = startdir; if (dbflag) { /* print out directory list */ (void)fprintf(stderr, "Directory list:\n%s\n", startdir->dl_name); thisdir = startdir->dl_next; while (thisdir != startdir) { (void)fprintf(stderr, "%s\n", thisdir->dl_name); thisdir = thisdir->dl_next; } } thisdir = startdir; /* * Get the minfree value */ if (getacmin(&minfree) != 0) minfree = 0; endac(); if (minfree < 0 || minfree > 100) minfree = 0; if (dbflag) (void)fprintf(stderr, "auditd: new minfree: %d\n", minfree); } /*********************************************************************** * logpost - post the new audit log file name to audit_data. ***********************************************************************/ logpost(pid, name) int pid; char *name; { register short fd; register FILE *f; register int error = 0; if (dbflag) (void)fprintf(stderr, "auditd: Posting %d, %s to %s\n", pid, name, auditdata); /* * Ignore the result of unlink since we don't care whether it * already existed or not. */ (void)unlink(tempfile); if ((fd = open(tempfile, O_RDWR|O_CREAT|O_EXCL, 0600)) < 0) { (void)fprintf(stderr, "auditd: can't open %s. Auditd exiting!\n", tempfile); perror("auditd"); (void)dowarn("tmpfile", "", 0); exit(1); } f = fdopen(fd, "w"); if (f == NULL) { /* this should not happen */ (void)fprintf(stderr, "auditd: can't fdopen %s,%d. Auditd exiting!\n", tempfile, fd); (void)dowarn("tmpfile", "", 0); exit(1); } (void)fprintf(f, "%d:%s\n", pid, name); if (fflush(f) == EOF) { (void)fprintf(stderr, "auditd: Can't write audit_data\n"); error = -1; } if (!error && fsync(fd) < 0) { perror("auditd"); error = -1; } (void)fclose(f); if (!error && rename(tempfile, auditdata) < 0) { perror("auditd"); error = -1; } if (error) { if (dbflag) perror("auditd"); (void)unlink(tempfile); } return(error); } /********************************************************************* * testspace - determine whether the given directorie's filesystem * has the given amount of space. Also set the space * value in the directory list structure. * globals - * minfree - what the soft limit is (% of filesystem to reserve) * limit - whether we are using soft or hard limits ********************************************************************/ testspace(thisdir, test_limit) dirlist_t *thisdir; int test_limit; { struct statfs sb; if (dbflag) (void)fprintf(stderr, "auditd: checking %s for space limit %d\n", thisdir->dl_name, test_limit); if (thisdir->dl_space == STAY_FULL) { thisdir->dl_space = SPACE_FULL; minfreeblocks = 0; } else if (statfs(thisdir->dl_name, &sb) < 0) { if (dbflag) perror("auditd"); thisdir->dl_space = SPACE_FULL; minfreeblocks = 0; } else { minfreeblocks = minfree * (sb.f_blocks / 100); if (dbflag) (void)fprintf(stderr, "auditd: bavail = %d, minblocks = %d\n", sb.f_bavail, minfreeblocks); if (sb.f_bavail < AVAIL_MIN) thisdir->dl_space = SPACE_FULL; else if (sb.f_bavail < minfreeblocks) thisdir->dl_space = HARD_SPACE; else thisdir->dl_space = SOFT_SPACE; } return(thisdir->dl_space == test_limit); } /************************************************************************ * dowarn - invoke the shell script auditwarn to notify the adminstrator * about the given problem. * parameters - * option - what the problem is * logfile - used with options soft and hard: which file was being * used when the filesystem filled up * count - used with option allhard: how many times auditwarn has * been called for this problem ***********************************************************************/ dowarn(option, filename, count) char *option; char *filename; int count; { register int pid; int st; char countstr[5]; char warnstring[80]; register short fd; register FILE *f; register int error = 0; if (dbflag) (void)fprintf(stderr, "auditd: calling %s with %s %s %d\n", auditwarn, option, filename, count); if (!nflag) { if ((pid = fork()) == -1) { (void)fprintf(stderr, "auditd: fork failed\n"); return; } else if (pid != 0) { pid = wait((union wait *)&st); return; } } /* * Set our effective uid back to root so that audit_warn can * write to the console if it needs to. */ if (setreuid(0, 0) < 0) perror("auditd"); if (strcmp(option, "soft") == 0 || strcmp(option, "hard") == 0) { (void)sprintf(warnstring, "%s limit exceeded in %s.\n", option, filename); execl(auditwarn, auditwarn, option, filename, 0); } else if (strcmp(option, "allhard") == 0) { (void)sprintf(warnstring, "All audit filesystems are full.\n"); (void)sprintf(countstr, "%d", count); execl(auditwarn, auditwarn, option, countstr, 0); } else if (strcmp(option, "getacdir") == 0) { (void)sprintf(warnstring, "Problem getting directories from audit_control.\n"); (void)sprintf(countstr, "%d", count); execl(auditwarn, auditwarn, option, countstr, 0); } else { (void)sprintf(warnstring, "Need attention for error %s.\n", option); execl(auditwarn, auditwarn, option, 0); } /* * If get here, execls above failed. */ perror("auditd"); if ((fd = open("/dev/console", O_WRONLY)) < 0) { if (dbflag) perror("auditd"); exit(1); } f = fdopen(fd, "w"); if (f == NULL) { /* this should not happen */ if (dbflag) (void)fprintf(stderr, "auditd: couldn't fdopen /dev/console"); exit(1); } (void)fprintf(f, "auditd: Could not execute %s\n", auditwarn); (void)fprintf(f, "auditd: %s\n", warnstring); if (fflush(f) == EOF) { if (dbflag) (void)fprintf(stderr, "auditd: Can't write audit_data\n"); error = -1; } if (!error && fsync(fd) < 0) { if (dbflag) perror("auditd"); error = -1; } (void)fclose(f); if (close(fd) < 0) if (dbflag) perror("auditd"); exit(1); }