Home | History | Annotate | Download | only in binfile
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  *
     26  * write binary audit records directly to a file.
     27  */
     28 
     29 #define	DEBUG   0
     30 
     31 #if DEBUG
     32 #define	DPRINT(x) {fprintf x; }
     33 #else
     34 #define	DPRINT(x)
     35 #endif
     36 
     37 /*
     38  * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
     39  * implement a replacable library for use by auditd; they are a
     40  * project private interface and may change without notice.
     41  *
     42  */
     43 
     44 #include <assert.h>
     45 #include <bsm/audit.h>
     46 #include <bsm/audit_record.h>
     47 #include <bsm/libbsm.h>
     48 #include <errno.h>
     49 #include <fcntl.h>
     50 #include <libintl.h>
     51 #include <netdb.h>
     52 #include <pthread.h>
     53 #include <secdb.h>
     54 #include <signal.h>
     55 #include <stdio.h>
     56 #include <stdlib.h>
     57 #include <string.h>
     58 #include <sys/param.h>
     59 #include <sys/types.h>
     60 #include <time.h>
     61 #include <tzfile.h>
     62 #include <unistd.h>
     63 #include <sys/vfs.h>
     64 #include <limits.h>
     65 #include <syslog.h>
     66 #include <security/auditd.h>
     67 #include <audit_plugin.h>
     68 
     69 #define	AUDIT_DATE_SZ	14
     70 #define	AUDIT_FNAME_SZ	2 * AUDIT_DATE_SZ + 2 + MAXHOSTNAMELEN
     71 #define	AUDIT_BAK_SZ	50	/* size of name of audit_data back-up file */
     72 
     73 			/* per-directory status */
     74 #define	SOFT_SPACE	0	/* minfree or less space available	*/
     75 #define	PLENTY_SPACE	1	/* more than minfree available		*/
     76 #define	SPACE_FULL	2	/* out of space				*/
     77 
     78 #define	AVAIL_MIN	50	/* If there are less that this number	*/
     79 				/* of blocks avail, the filesystem is	*/
     80 				/* presumed full.			*/
     81 
     82 #define	ALLHARD_DELAY	20	/* Call audit_warn(allhard) every 20 seconds */
     83 
     84 /* minimum reasonable size in bytes to roll over an audit file */
     85 #define	FSIZE_MIN	512000
     86 
     87 /*
     88  * The directory list is a circular linked list.  It is pointed into by
     89  * activeDir.  Each element contains the pointer to the next
     90  * element, the directory pathname, a flag for how much space there is
     91  * in the directory's filesystem, and a file handle.  Since a new
     92  * directory list can be created from auditd_plugin_open() while the
     93  * current list is in use, activeDir is protected by log_mutex.
     94  */
     95 typedef struct dirlist_s dirlist_t;
     96 struct dirlist_s {
     97 	dirlist_t	*dl_next;
     98 	int		dl_space;
     99 	int		dl_flags;
    100 	char		*dl_dirname;
    101 	char		*dl_filename;	/* file name (not path) if open */
    102 	int		dl_fd;		/* file handle, -1 unless open */
    103 };
    104 /*
    105  * Defines for dl_flags
    106  */
    107 #define	SOFT_WARNED	0x0001	/* already did soft warning for this dir */
    108 #define	HARD_WARNED	0x0002	/* already did hard warning for this dir */
    109 
    110 #if DEBUG
    111 static FILE		*dbfp;			/* debug file */
    112 #endif
    113 
    114 static pthread_mutex_t	log_mutex;
    115 static int		binfile_is_open = 0;
    116 
    117 static int		minfree = -1;
    118 static int		minfreeblocks;		/* minfree in blocks */
    119 
    120 static dirlist_t	*activeDir = NULL;	/* current directory */
    121 static dirlist_t	*startdir;		/* first dir in the ring */
    122 static int		activeCount = 0;	/* number of dirs in the ring */
    123 
    124 static int		openNewFile = 0;	/* need to open a new file */
    125 static int		hung_count = 0;		/* count of audit_warn hard */
    126 
    127 /* flag from audit_plugin_open to audit_plugin_close */
    128 static int		am_open = 0;
    129 /* preferred dir state */
    130 static int		fullness_state = PLENTY_SPACE;
    131 
    132 /*
    133  * These are used to implement a maximum size for the auditing
    134  * file. binfile_maxsize is set via the 'p_fsize' parameter to the
    135  * audit_binfile plugin.
    136  */
    137 static uint_t		binfile_cursize = 0;
    138 static uint_t		binfile_maxsize = 0;
    139 
    140 static int open_log(dirlist_t *);
    141 
    142 static void
    143 freedirlist(dirlist_t *head)
    144 {
    145 	dirlist_t	 *n1, *n2;
    146 	/*
    147 	 * Free up the old directory list if any
    148 	 */
    149 	if (head != NULL) {
    150 		n1 = head;
    151 		do {
    152 			n2 = n1->dl_next;
    153 			free(n1->dl_dirname);
    154 			free(n1->dl_filename);
    155 			free(n1);
    156 			n1 = n2;
    157 		} while (n1 != head);
    158 	}
    159 }
    160 
    161 
    162 /*
    163  * add to a linked list of directories available for writing
    164  *
    165  */
    166 
    167 static int
    168 growauditlist(dirlist_t **listhead, char *dirlist,
    169     dirlist_t *endnode, int *count)
    170 {
    171 	dirlist_t	*node;
    172 	char		*bs, *be;
    173 	dirlist_t	**node_p;
    174 	char		*dirname;
    175 	char		*remainder;
    176 
    177 	DPRINT((dbfp, "binfile: dirlist=%s\n", dirlist));
    178 
    179 	if (*listhead == NULL)
    180 		node_p = listhead;
    181 	else
    182 		node_p = &(endnode->dl_next);
    183 
    184 	node = NULL;
    185 	while ((dirname = strtok_r(dirlist, ",", &remainder)) != NULL) {
    186 		dirlist = NULL;
    187 
    188 		DPRINT((dbfp, "binfile: p_dir = %s\n", dirname));
    189 
    190 		(*count)++;
    191 		node = malloc(sizeof (dirlist_t));
    192 		if (node == NULL)
    193 			return (AUDITD_NO_MEMORY);
    194 
    195 		node->dl_flags = 0;
    196 		node->dl_filename = NULL;
    197 		node->dl_fd = -1;
    198 		node->dl_space = PLENTY_SPACE;
    199 
    200 		node->dl_dirname = malloc((unsigned)strlen(dirname) + 1);
    201 		if (node->dl_dirname == NULL)
    202 			return (AUDITD_NO_MEMORY);
    203 
    204 		bs = dirname;
    205 		while ((*bs == ' ') || (*bs == '\t'))	/* trim blanks */
    206 			bs++;
    207 		be = bs + strlen(bs) - 1;
    208 		while (be > bs) {	/* trim trailing blanks */
    209 			if ((*bs != ' ') && (*bs != '\t'))
    210 				break;
    211 			be--;
    212 		}
    213 		*(be + 1) = '\0';
    214 		(void) strlcpy(node->dl_dirname, bs, AUDIT_FNAME_SZ);
    215 
    216 		if (*listhead != NULL)
    217 			node->dl_next = *listhead;
    218 		else
    219 			node->dl_next = node;
    220 		*node_p = node;
    221 		node_p = &(node->dl_next);
    222 
    223 	}
    224 	return (0);
    225 }
    226 
    227 /*
    228  * create a linked list of directories available for writing
    229  *
    230  * if a list already exists, the two are compared and the new one is
    231  * used only if it is different than the old.
    232  *
    233  * returns -2 for new or changed list, 0 for unchanged list and -1 for
    234  * error.  (Positive returns are for AUDITD_<error code> values)
    235  *
    236  */
    237 
    238 static int
    239 loadauditlist(char *dirstr, char *minfreestr)
    240 {
    241 	char		buf[MAXPATHLEN];
    242 	char		*bs, *be;
    243 	dirlist_t	 *node, *n1, *n2;
    244 	dirlist_t	 **node_p;
    245 	dirlist_t	*listhead = NULL;
    246 	dirlist_t	*thisdir;
    247 	int		acresult;
    248 	int		node_count = 0;
    249 	int		rc;
    250 	int		temp_minfree;
    251 	au_acinfo_t	*ach;
    252 
    253 	static dirlist_t	*activeList = NULL;	/* directory list */
    254 
    255 	DPRINT((dbfp, "binfile: Loading audit list from auditcontrol\n"));
    256 
    257 	/*
    258 	 * Build new directory list
    259 	 */
    260 	/* part 1 -- using pre Sol 10 audit_control directives */
    261 	node_p = &listhead;
    262 
    263 	ach = _openac(NULL);
    264 	if (ach == NULL)
    265 		return (-1);
    266 
    267 	/* at least one directory is needed */
    268 	while ((acresult = _getacdir(ach, buf, sizeof (buf))) == 0 ||
    269 	    acresult == 2 || acresult == -3) {
    270 		/*
    271 		 * loop if the result is 0 (success), 2 (a warning
    272 		 * that the audit_data file has been rewound),
    273 		 * or -3 (a directory entry was found, but it
    274 		 * was badly formatted.
    275 		 */
    276 		if (acresult == 0) {
    277 			/*
    278 			 * A directory entry was found.
    279 			 */
    280 			node_count++;
    281 			node = malloc(sizeof (dirlist_t));
    282 			if (node == NULL)
    283 				return (AUDITD_NO_MEMORY);
    284 
    285 			node->dl_flags = 0;
    286 			node->dl_fd = -1;
    287 			node->dl_space = PLENTY_SPACE;
    288 			node->dl_filename = NULL;
    289 
    290 			node->dl_dirname = malloc((unsigned)strlen(buf) + 1);
    291 			if (node->dl_dirname == NULL)
    292 				return (AUDITD_NO_MEMORY);
    293 
    294 			bs = buf;
    295 			while ((*bs == ' ') || (*bs == '\t'))
    296 				bs++;
    297 			be = bs + strlen(bs) - 1;
    298 			while (be > bs) {	/* trim trailing blanks */
    299 				if ((*bs != ' ') && (*bs != '\t'))
    300 					break;
    301 				be--;
    302 			}
    303 			*(be + 1) = '\0';
    304 			(void) strlcpy(node->dl_dirname, bs, AUDIT_FNAME_SZ);
    305 
    306 			if (listhead != NULL)
    307 				node->dl_next = listhead;
    308 			else
    309 				node->dl_next = node;
    310 			*node_p = node;
    311 			node_p = &(node->dl_next);
    312 		}
    313 	}   /* end of getacdir while */
    314 	/*
    315 	 * part 2 -- use directories and minfree from the (new as of Sol 10)
    316 	 * plugin directive
    317 	 */
    318 	if (dirstr != NULL) {
    319 		if (node_count == 0) {
    320 			listhead = NULL;
    321 			node = NULL;
    322 		}
    323 		rc = growauditlist(&listhead, dirstr, node, &node_count);
    324 		if (rc)
    325 			return (rc);
    326 	}
    327 	if (node_count == 0) {
    328 		/*
    329 		 * there was a problem getting the directory
    330 		 * list or remote host info from the audit_control file
    331 		 * even though auditd thought there was at least 1 good
    332 		 * entry
    333 		 */
    334 		DPRINT((dbfp, "binfile: "
    335 		    "problem getting directory / libpath list "
    336 		    "from audit_control.\n"));
    337 
    338 		_endac(ach);
    339 		return (-1);
    340 	}
    341 #if DEBUG
    342 	/* print out directory list */
    343 
    344 	if (listhead != NULL) {
    345 		fprintf(dbfp, "Directory list:\n\t%s\n", listhead->dl_dirname);
    346 		thisdir = listhead->dl_next;
    347 
    348 		while (thisdir != listhead) {
    349 			fprintf(dbfp, "\t%s\n", thisdir->dl_dirname);
    350 			thisdir = thisdir->dl_next;
    351 		}
    352 	}
    353 #endif	/* DEBUG */
    354 	thisdir = listhead;
    355 	/*
    356 	 * See if the list has changed.
    357 	 * If there was a change  rc = 0 if no change, else 1
    358 	 */
    359 	rc = 0;	/* no change */
    360 
    361 	if (node_count == activeCount) {
    362 		n1 = listhead;
    363 		n2 = activeList;
    364 		do {
    365 			if (strcmp(n1->dl_dirname, n2->dl_dirname) != 0) {
    366 				DPRINT((dbfp,
    367 				    "binfile: new dirname = %s\n"
    368 				    "binfile: old dirname = %s\n",
    369 				    n1->dl_dirname,
    370 				    n2->dl_dirname));
    371 				rc = -2;
    372 				break;
    373 			}
    374 			n1 = n1->dl_next;
    375 			n2 = n2->dl_next;
    376 		} while ((n1 != listhead) && (n2 != activeList));
    377 	} else {
    378 		DPRINT((dbfp, "binfile:  old dir count = %d\n"
    379 		    "binfile:  new dir count = %d\n",
    380 		    activeCount, node_count));
    381 		rc = -2;
    382 	}
    383 	if (rc == -2) {
    384 		(void) pthread_mutex_lock(&log_mutex);
    385 		DPRINT((dbfp, "loadauditlist:  close / open log\n"));
    386 		if (open_log(listhead) == 0) {
    387 			openNewFile = 1;	/* try again later */
    388 		} else {
    389 			openNewFile = 0;
    390 		}
    391 		freedirlist(activeList);	/* old list */
    392 		activeList = listhead;		/* new list */
    393 		activeDir = startdir = thisdir;
    394 		activeCount = node_count;
    395 		(void) pthread_mutex_unlock(&log_mutex);
    396 	} else
    397 		freedirlist(listhead);
    398 	/*
    399 	 * Get the minfree value.  If minfree comes in via the attribute
    400 	 * list, ignore the possibility it may also be listed on a separate
    401 	 * audit_control line.
    402 	 */
    403 	if (minfreestr != NULL)
    404 		temp_minfree = atoi(minfreestr);
    405 	else if (!(_getacmin(ach, &temp_minfree) == 0))
    406 		temp_minfree = 0;
    407 
    408 	if ((temp_minfree < 0) || (temp_minfree > 100))
    409 		temp_minfree = 0;
    410 
    411 	if (minfree != temp_minfree) {
    412 		DPRINT((dbfp, "minfree:  old = %d, new = %d\n",
    413 		    minfree, temp_minfree));
    414 		rc = -2;		/* data change */
    415 		minfree = temp_minfree;
    416 	}
    417 	_endac(ach);
    418 
    419 	return (rc);
    420 }
    421 
    422 
    423 /*
    424  * getauditdate - get the current time (GMT) and put it in the form
    425  *		  yyyymmddHHMMSS .
    426  */
    427 static void
    428 getauditdate(char *date)
    429 {
    430 	struct timeval tp;
    431 	struct timezone tzp;
    432 	struct tm tm;
    433 
    434 	(void) gettimeofday(&tp, &tzp);
    435 	tm = *gmtime(&tp.tv_sec);
    436 	/*
    437 	 * NOTE:  if we want to use gmtime, we have to be aware that the
    438 	 *	structure only keeps the year as an offset from TM_YEAR_BASE.
    439 	 *	I have used TM_YEAR_BASE in this code so that if they change
    440 	 *	this base from 1900 to 2000, it will hopefully mean that this
    441 	 *	code does not have to change.  TM_YEAR_BASE is defined in
    442 	 *	tzfile.h .
    443 	 */
    444 	(void) sprintf(date, "%.4d%.2d%.2d%.2d%.2d%.2d",
    445 	    tm.tm_year + TM_YEAR_BASE, tm.tm_mon + 1, tm.tm_mday,
    446 	    tm.tm_hour, tm.tm_min, tm.tm_sec);
    447 }
    448 
    449 
    450 
    451 /*
    452  * write_file_token - put the file token into the audit log
    453  */
    454 static int
    455 write_file_token(int fd, char *name)
    456 {
    457 	adr_t adr;					/* xdr ptr */
    458 	struct timeval tv;				/* time now */
    459 	char for_adr[AUDIT_FNAME_SZ + AUDIT_FNAME_SZ];	/* plenty of room */
    460 	char	token_id;
    461 	short	i;
    462 
    463 	(void) gettimeofday(&tv, (struct timezone *)0);
    464 	i = strlen(name) + 1;
    465 	adr_start(&adr, for_adr);
    466 #ifdef _LP64
    467 		token_id = AUT_OTHER_FILE64;
    468 		adr_char(&adr, &token_id, 1);
    469 		adr_int64(&adr, (int64_t *)& tv, 2);
    470 #else
    471 		token_id = AUT_OTHER_FILE32;
    472 		adr_char(&adr, &token_id, 1);
    473 		adr_int32(&adr, (int32_t *)& tv, 2);
    474 #endif
    475 
    476 	adr_short(&adr, &i, 1);
    477 	adr_char(&adr, name, i);
    478 
    479 	if (write(fd, for_adr, adr_count(&adr)) < 0) {
    480 		DPRINT((dbfp, "binfile: Bad write\n"));
    481 		return (errno);
    482 	}
    483 	return (0);
    484 }
    485 
    486 /*
    487  * close_log - close the file if open.  Also put the name of the
    488  *	new log file in the trailer, and rename the old file
    489  *	to oldname.  The caller must hold log_mutext while calling
    490  *      close_log since any change to activeDir is a complete redo
    491  *	of all it points to.
    492  * arguments -
    493  *	oldname - the new name for the file to be closed
    494  *	newname - the name of the new log file (for the trailer)
    495  */
    496 static void
    497 close_log(dirlist_t *currentdir, char *oname, char *newname)
    498 {
    499 	char	auditdate[AUDIT_DATE_SZ+1];
    500 	char	*name;
    501 	char	oldname[AUDIT_FNAME_SZ+1];
    502 
    503 	if ((currentdir == NULL) || (currentdir->dl_fd == -1))
    504 		return;
    505 	/*
    506 	 * If oldname is blank, we were called by auditd_plugin_close()
    507 	 * instead of by open_log, so we need to update our name.
    508 	 */
    509 	(void) strlcpy(oldname, oname, AUDIT_FNAME_SZ);
    510 
    511 	if (strcmp(oldname, "") == 0) {
    512 		getauditdate(auditdate);
    513 
    514 		assert(currentdir->dl_filename != NULL);
    515 
    516 		(void) strlcpy(oldname, currentdir->dl_filename,
    517 		    AUDIT_FNAME_SZ);
    518 
    519 		name = strrchr(oldname, '/') + 1;
    520 		(void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate,
    521 		    AUDIT_DATE_SZ);
    522 	}
    523 	/*
    524 	 * Write the trailer record and rename and close the file.
    525 	 * If any of the write, rename, or close fail, ignore it
    526 	 * since there is not much else we can do and the next open()
    527 	 * will trigger the necessary full directory logic.
    528 	 *
    529 	 * newname is "" if binfile is being closed down.
    530 	 */
    531 	(void) write_file_token(currentdir->dl_fd, newname);
    532 	if (currentdir->dl_fd >= 0) {
    533 		(void) fsync(currentdir->dl_fd);
    534 		(void) close(currentdir->dl_fd);
    535 	}
    536 	currentdir->dl_fd = -1;
    537 	(void) rename(currentdir->dl_filename, oldname);
    538 
    539 	DPRINT((dbfp, "binfile: Log closed %s\n", oldname));
    540 
    541 	free(currentdir->dl_filename);
    542 	currentdir->dl_filename = NULL;
    543 }
    544 
    545 
    546 /*
    547  * open_log - open a new file in the current directory.  If a
    548  *	file is already open, close it.
    549  *
    550  *	return 1 if ok, 0 if all directories are full.
    551  *
    552  *	lastOpenDir - used to get the oldfile name (and change it),
    553  *		to close the oldfile.
    554  *
    555  * The caller must hold log_mutex while calling open_log.
    556  *
    557  */
    558 static int
    559 open_log(dirlist_t *current_dir)
    560 {
    561 	char	auditdate[AUDIT_DATE_SZ + 1];
    562 	char	oldname[AUDIT_FNAME_SZ + 1] = "";
    563 	char	newname[AUDIT_FNAME_SZ + 1];
    564 	char	*name;			/* pointer into oldname */
    565 	int	opened;
    566 	int	error = 0;
    567 	int	newfd = 0;
    568 
    569 	static char		host[MAXHOSTNAMELEN + 1] = "";
    570 	/* previous directory with open log file */
    571 	static dirlist_t	*lastOpenDir = NULL;
    572 
    573 	if (host[0] == '\0')
    574 		(void) gethostname(host, MAXHOSTNAMELEN);
    575 
    576 	/* Get a filename which does not already exist */
    577 	opened = 0;
    578 	while (!opened) {
    579 		getauditdate(auditdate);
    580 		(void) snprintf(newname, AUDIT_FNAME_SZ,
    581 		    "%s/%s.not_terminated.%s",
    582 		    current_dir->dl_dirname, auditdate, host);
    583 		newfd = open(newname,
    584 		    O_RDWR | O_APPEND | O_CREAT | O_EXCL, 0640);
    585 		if (newfd < 0) {
    586 			switch (errno) {
    587 			case EEXIST:
    588 				DPRINT((dbfp,
    589 				    "open_log says duplicate for %s "
    590 				    "(will try another)\n", newname));
    591 				(void) sleep(1);
    592 				break;
    593 			default:
    594 				/* open failed */
    595 				DPRINT((dbfp,
    596 				    "open_log says full for %s: %s\n",
    597 				    newname, strerror(errno)));
    598 				current_dir->dl_space = SPACE_FULL;
    599 				current_dir = current_dir->dl_next;
    600 				return (0);
    601 			} /* switch */
    602 		} else
    603 			opened = 1;
    604 	} /* while */
    605 
    606 	/*
    607 	 * When we get here, we have opened our new log file.
    608 	 * Now we need to update the name of the old file to
    609 	 * store in this file's header.  lastOpenDir may point
    610 	 * to current_dir if the list is only one entry long and
    611 	 * there is only one list.
    612 	 */
    613 	if ((lastOpenDir != NULL) && (lastOpenDir->dl_filename != NULL)) {
    614 		(void) strlcpy(oldname, lastOpenDir->dl_filename,
    615 		    AUDIT_FNAME_SZ);
    616 		name = (char *)strrchr(oldname, '/') + 1;
    617 
    618 		(void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate,
    619 		    AUDIT_DATE_SZ);
    620 
    621 		close_log(lastOpenDir, oldname, newname);
    622 	}
    623 	error = write_file_token(newfd, oldname);
    624 	if (error) {
    625 		/* write token failed */
    626 		(void) close(newfd);
    627 
    628 		current_dir->dl_space = SPACE_FULL;
    629 		current_dir->dl_fd = -1;
    630 		free(current_dir->dl_filename);
    631 		current_dir->dl_filename = NULL;
    632 		current_dir = current_dir->dl_next;
    633 		return (0);
    634 	} else {
    635 		lastOpenDir = current_dir;
    636 		current_dir->dl_fd = newfd;
    637 		current_dir->dl_filename = strdup(newname);
    638 
    639 		/*
    640 		 * New file opened, so reset file size statistic (used
    641 		 * to ensure audit log does not grow above size limit
    642 		 * set by p_fsize).
    643 		 */
    644 		binfile_cursize = 0;
    645 
    646 		(void) __logpost(newname);
    647 
    648 		DPRINT((dbfp, "binfile: Log opened: %s\n", newname));
    649 		return (1);
    650 	}
    651 }
    652 
    653 #define	IGNORE_SIZE	8192
    654 /*
    655  * spacecheck - determine whether the given directory's filesystem
    656  *	has the at least the space requested.  Also set the space
    657  *	value in the directory list structure.  If the caller
    658  *	passes other than PLENTY_SPACE or SOFT_SPACE, the caller should
    659  *	ignore the return value.  Otherwise, 0 = less than the
    660  *	requested space is available, 1 = at least the requested space
    661  *	is available.
    662  *
    663  *	log_mutex must be held by the caller
    664  *
    665  *	-1 is returned if stat fails
    666  *
    667  * IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default
    668  * buffer size written for Sol 9 and earlier.  To keep the same accuracy
    669  * for the soft limit check as before, spacecheck checks for space
    670  * remaining IGNORE_SIZE bytes.  This reduces the number of statvfs()
    671  * calls and related math.
    672  *
    673  * globals -
    674  *	minfree - the soft limit, i.e., the % of filesystem to reserve
    675  */
    676 static int
    677 spacecheck(dirlist_t *thisdir, int test_limit, size_t next_buf_size)
    678 {
    679 	struct statvfs	sb;
    680 	static int	ignore_size = 0;
    681 
    682 	ignore_size += next_buf_size;
    683 
    684 	if ((test_limit == PLENTY_SPACE) && (ignore_size < IGNORE_SIZE))
    685 		return (1);
    686 
    687 	assert(thisdir != NULL);
    688 
    689 	if (statvfs(thisdir->dl_dirname, &sb) < 0) {
    690 		thisdir->dl_space = SPACE_FULL;
    691 		minfreeblocks = AVAIL_MIN;
    692 		return (-1);
    693 	} else {
    694 		minfreeblocks = ((minfree * sb.f_blocks) / 100) + AVAIL_MIN;
    695 
    696 		if (sb.f_bavail < AVAIL_MIN)
    697 			thisdir->dl_space = SPACE_FULL;
    698 		else if (sb.f_bavail > minfreeblocks) {
    699 			thisdir->dl_space = fullness_state = PLENTY_SPACE;
    700 			ignore_size = 0;
    701 		} else
    702 			thisdir->dl_space = SOFT_SPACE;
    703 	}
    704 	if (thisdir->dl_space == PLENTY_SPACE)
    705 		return (1);
    706 
    707 	return (thisdir->dl_space == test_limit);
    708 }
    709 
    710 /*
    711  * Parses p_fsize value and contains it within the range FSIZE_MIN and
    712  * INT_MAX so using uints won't cause an undetected overflow of
    713  * INT_MAX.  Defaults to 0 if the value is invalid or is missing.
    714  */
    715 static void
    716 save_maxsize(char *maxsize) {
    717 	/*
    718 	 * strtol() returns a long which could be larger than int so
    719 	 * store here for sanity checking first
    720 	 */
    721 	long proposed_maxsize;
    722 
    723 	if (maxsize != NULL) {
    724 		/*
    725 		 * There is no explicit error return from strtol() so
    726 		 * we may need to depend on the value of errno.
    727 		 */
    728 		errno = 0;
    729 		proposed_maxsize = strtol(maxsize, (char **)NULL, 10);
    730 
    731 		/*
    732 		 * If sizeof(long) is greater than sizeof(int) on this
    733 		 * platform, proposed_maxsize might be greater than
    734 		 * INT_MAX without it being reported as ERANGE.
    735 		 */
    736 		if ((errno == ERANGE) ||
    737 		    ((proposed_maxsize != 0) &&
    738 			(proposed_maxsize < FSIZE_MIN)) ||
    739 		    (proposed_maxsize > INT_MAX)) {
    740 			binfile_maxsize = 0;
    741 			DPRINT((dbfp, "binfile: p_fsize parameter out of "
    742 					"range: %s\n", maxsize));
    743 			/*
    744 			 * Inform administrator of the error via
    745 			 * syslog
    746 			 */
    747 			__audit_syslog("audit_binfile.so",
    748 			    LOG_CONS | LOG_NDELAY,
    749 			    LOG_DAEMON, LOG_ERR,
    750 			    gettext("p_fsize parameter out of range\n"));
    751 		} else {
    752 			binfile_maxsize = proposed_maxsize;
    753 		}
    754 	} else { /* p_fsize string was not present */
    755 		binfile_maxsize = 0;
    756 	}
    757 
    758 	DPRINT((dbfp, "binfile: set maxsize to %d\n", binfile_maxsize));
    759 }
    760 
    761 /*
    762  * auditd_plugin() writes a buffer to the currently open file. The
    763  * global "openNewFile" is used to force a new log file for cases such
    764  * as the initial open, when minfree is reached, the p_fsize value is
    765  * exceeded or the current file system fills up, and "audit -s" with
    766  * changed parameters.  For "audit -n" a new log file is opened
    767  * immediately in auditd_plugin_open().
    768  *
    769  * This function manages one or more audit directories as follows:
    770  *
    771  * 	If the current open file is in a directory that has not
    772  *	reached the soft limit, write the input data and return.
    773  *
    774  *	Scan the list of directories for one which has not reached
    775  *	the soft limit; if one is found, write and return.  Such
    776  *	a writable directory is in "PLENTY_SPACE" state.
    777  *
    778  *	Scan the list of directories for one which has not reached
    779  *	the hard limit; if one is found, write and return.  This
    780  *	directory in in "SOFT_SPACE" state.
    781  *
    782  * Oh, and if a write fails, handle it like a hard space limit.
    783  *
    784  * audit_warn (via __audit_dowarn()) is used to alert an operator
    785  * at various levels of fullness.
    786  */
    787 /* ARGSUSED */
    788 auditd_rc_t
    789 auditd_plugin(const char *input, size_t in_len, uint32_t sequence, char **error)
    790 {
    791 	auditd_rc_t	rc = AUDITD_FAIL;
    792 	int		open_status;
    793 	size_t		out_len;
    794 	/* LINTED */
    795 	int		statrc;
    796 	/* avoid excess audit_warnage */
    797 	static int	allsoftfull_warning = 0;
    798 	static int	allhard_pause = 0;
    799 	static struct timeval	next_allhard;
    800 	struct timeval	now;
    801 #if DEBUG
    802 	static char	*last_file_written_to = NULL;
    803 	static uint32_t	last_sequence = 0;
    804 	static uint32_t	write_count = 0;
    805 
    806 	if ((last_sequence > 0) && (sequence != last_sequence + 1))
    807 		fprintf(dbfp, "binfile: buffer sequence=%d but prev=%d=n",
    808 		    sequence, last_sequence);
    809 	last_sequence = sequence;
    810 
    811 	fprintf(dbfp, "binfile: input seq=%d, len=%d\n",
    812 	    sequence, in_len);
    813 #endif
    814 	*error = NULL;
    815 	/*
    816 	 * lock is for activeDir, referenced by open_log() and close_log()
    817 	 */
    818 	(void) pthread_mutex_lock(&log_mutex);
    819 
    820 	/*
    821 	 * If this would take us over the maximum size, open a new
    822 	 * file, unless maxsize is 0, in which case growth of the
    823 	 * audit log is unrestricted.
    824 	 */
    825 	if ((binfile_maxsize != 0) &&
    826 	    ((binfile_cursize + in_len) > binfile_maxsize)) {
    827 		DPRINT((dbfp, "binfile: maxsize exceeded, opening new audit "
    828 		    "file.\n"));
    829 		openNewFile = 1;
    830 	}
    831 
    832 	while (rc == AUDITD_FAIL) {
    833 		open_status = 1;
    834 		if (openNewFile) {
    835 			open_status = open_log(activeDir);
    836 			if (open_status == 1)	/* ok */
    837 				openNewFile = 0;
    838 		}
    839 		/*
    840 		 * consider "space ok" return and error return the same;
    841 		 * a -1 means spacecheck couldn't check for space.
    842 		 */
    843 		if ((open_status == 1) &&
    844 		    (statrc = spacecheck(activeDir, fullness_state,
    845 		    in_len)) != 0) {
    846 #if DEBUG
    847 			DPRINT((dbfp, "binfile: returned from spacecheck\n"));
    848 			/*
    849 			 * The last copy of last_file_written_to is
    850 			 * never free'd, so there will be one open
    851 			 * memory reference on exit.  It's debug only.
    852 			 */
    853 			if ((last_file_written_to != NULL) &&
    854 			    (strcmp(last_file_written_to,
    855 			    activeDir->dl_filename) != 0)) {
    856 				DPRINT((dbfp, "binfile:  now writing to %s\n",
    857 				    activeDir->dl_filename));
    858 				free(last_file_written_to);
    859 			}
    860 			DPRINT((dbfp, "binfile:  finished some debug stuff\n"));
    861 			last_file_written_to =
    862 			    strdup(activeDir->dl_filename);
    863 #endif
    864 			out_len = write(activeDir->dl_fd, input, in_len);
    865 			DPRINT((dbfp, "binfile:  finished the write\n"));
    866 
    867 			binfile_cursize += out_len;
    868 
    869 			if (out_len == in_len) {
    870 				DPRINT((dbfp,
    871 				    "binfile: write_count=%u, sequence=%u,"
    872 				    " l=%u\n",
    873 				    ++write_count, sequence, out_len));
    874 				allsoftfull_warning = 0;
    875 				activeDir->dl_flags = 0;
    876 
    877 				rc = AUDITD_SUCCESS;
    878 				break;
    879 			} else if (!(activeDir->dl_flags & HARD_WARNED)) {
    880 				DPRINT((dbfp,
    881 				    "binfile: write failed, sequence=%u, "
    882 				    "l=%u\n", sequence, out_len));
    883 				DPRINT((dbfp, "hard warning sent.\n"));
    884 				__audit_dowarn("hard", activeDir->dl_dirname,
    885 				    0);
    886 
    887 				activeDir->dl_flags |= HARD_WARNED;
    888 			}
    889 		} else {
    890 			DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n",
    891 			    statrc, fullness_state));
    892 			if (!(activeDir->dl_flags & SOFT_WARNED) &&
    893 			    (activeDir->dl_space == SOFT_SPACE)) {
    894 				DPRINT((dbfp, "soft warning sent\n"));
    895 				__audit_dowarn("soft",
    896 				    activeDir->dl_dirname, 0);
    897 				activeDir->dl_flags |= SOFT_WARNED;
    898 			}
    899 			if (!(activeDir->dl_flags & HARD_WARNED) &&
    900 			    (activeDir->dl_space == SPACE_FULL)) {
    901 				DPRINT((dbfp, "hard warning sent.\n"));
    902 				__audit_dowarn("hard",
    903 				    activeDir->dl_dirname, 0);
    904 				activeDir->dl_flags |= HARD_WARNED;
    905 			}
    906 		}
    907 		DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n",
    908 		    activeDir->dl_dirname, activeDir->dl_next->dl_dirname));
    909 
    910 		activeDir = activeDir->dl_next;
    911 		openNewFile = 1;
    912 
    913 		if (activeDir == startdir) {		/* full circle */
    914 			if (fullness_state == PLENTY_SPACE) {	/* once */
    915 				fullness_state = SOFT_SPACE;
    916 				if (allsoftfull_warning == 0) {
    917 					allsoftfull_warning++;
    918 					__audit_dowarn("allsoft", "", 0);
    919 				}
    920 			} else {			/* full circle twice */
    921 				if ((hung_count > 0) && !allhard_pause) {
    922 					allhard_pause = 1;
    923 					(void) gettimeofday(&next_allhard,
    924 					    NULL);
    925 					next_allhard.tv_sec += ALLHARD_DELAY;
    926 				}
    927 
    928 				if (allhard_pause) {
    929 					(void) gettimeofday(&now, NULL);
    930 					if (now.tv_sec >= next_allhard.tv_sec) {
    931 						allhard_pause = 0;
    932 						__audit_dowarn("allhard", "",
    933 						    ++hung_count);
    934 					}
    935 				} else {
    936 					__audit_dowarn("allhard", "",
    937 					    ++hung_count);
    938 				}
    939 				minfreeblocks = AVAIL_MIN;
    940 				rc = AUDITD_RETRY;
    941 				*error = strdup(gettext(
    942 				    "all partitions full\n"));
    943 				(void) __logpost("");
    944 			}
    945 		}
    946 	}
    947 	(void) pthread_mutex_unlock(&log_mutex);
    948 
    949 	return (rc);
    950 }
    951 
    952 
    953 /*
    954  * the open function uses getacdir() and getacmin to determine which
    955  * directories to use and when to switch.  It takes no inputs.
    956  *
    957  * It may be called multiple times as auditd handles SIGHUP and SIGUSR1
    958  * corresponding to the audit(1M) flags -s and -n
    959  *
    960  * kvlist is NULL only if auditd caught a SIGUSR1, so after the first
    961  * time open is called, the reason is -s if kvlist != NULL and -n
    962  * otherwise.
    963  *
    964  */
    965 
    966 auditd_rc_t
    967 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
    968 {
    969 	int		rc = 0;
    970 	int		status;
    971 	int		reason;
    972 	char		*dirlist;
    973 	char		*minfree;
    974 	char		*maxsize;
    975 	kva_t		*kv;
    976 
    977 	*error = NULL;
    978 	*ret_list = NULL;
    979 	kv = (kva_t *)kvlist;
    980 
    981 	if (am_open) {
    982 		if (kvlist == NULL)
    983 			reason = 1;	/* audit -n */
    984 		else
    985 			reason = 2;	/* audit -s */
    986 	} else {
    987 		reason = 0;		/* initial open */
    988 #if DEBUG
    989 		dbfp = __auditd_debug_file_open();
    990 #endif
    991 	}
    992 	DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason));
    993 
    994 	am_open = 1;
    995 
    996 	if (kvlist == NULL) {
    997 		dirlist = NULL;
    998 		minfree = NULL;
    999 		maxsize = NULL;
   1000 	} else {
   1001 		dirlist = kva_match(kv, "p_dir");
   1002 		minfree = kva_match(kv, "p_minfree");
   1003 		maxsize = kva_match(kv, "p_fsize");
   1004 	}
   1005 	switch (reason) {
   1006 	case 0:			/* initial open */
   1007 		if (!binfile_is_open)
   1008 			(void) pthread_mutex_init(&log_mutex, NULL);
   1009 		binfile_is_open = 1;
   1010 		openNewFile = 1;
   1011 		/* FALLTHRU */
   1012 	case 2:			/* audit -s */
   1013 		/* handle p_fsize parameter */
   1014 		save_maxsize(maxsize);
   1015 
   1016 		fullness_state = PLENTY_SPACE;
   1017 		status = loadauditlist(dirlist, minfree);
   1018 
   1019 		if (status == -1) {
   1020 			(void) __logpost("");
   1021 			*error = strdup(gettext("no directories configured"));
   1022 			return (AUDITD_RETRY);
   1023 		} else if (status == AUDITD_NO_MEMORY) {
   1024 			(void) __logpost("");
   1025 			*error = strdup(gettext("no memory"));
   1026 			return (status);
   1027 		} else {	/* status is 0 or -2 (no change or changed) */
   1028 			hung_count = 0;
   1029 			DPRINT((dbfp, "binfile: loadauditlist returned %d\n",
   1030 			    status));
   1031 		}
   1032 		break;
   1033 	case 1:			/* audit -n */
   1034 		(void) pthread_mutex_lock(&log_mutex);
   1035 		if (open_log(activeDir) == 1)	/* ok */
   1036 			openNewFile = 0;
   1037 		(void) pthread_mutex_unlock(&log_mutex);
   1038 		break;
   1039 	}
   1040 
   1041 	rc = AUDITD_SUCCESS;
   1042 	*ret_list = NULL;
   1043 
   1044 	return (rc);
   1045 }
   1046 
   1047 auditd_rc_t
   1048 auditd_plugin_close(char **error)
   1049 {
   1050 	*error = NULL;
   1051 
   1052 	(void) pthread_mutex_lock(&log_mutex);
   1053 	close_log(activeDir, "", "");
   1054 	freedirlist(activeDir);
   1055 	activeDir = NULL;
   1056 	(void) pthread_mutex_unlock(&log_mutex);
   1057 
   1058 	DPRINT((dbfp, "binfile:  closed\n"));
   1059 
   1060 	if (binfile_is_open) {
   1061 		(void) pthread_mutex_destroy(&log_mutex);
   1062 		binfile_is_open = 0;
   1063 		/* LINTED */
   1064 	} else {
   1065 		DPRINT((dbfp,
   1066 		    "auditd_plugin_close() called when already closed."));
   1067 	}
   1068 	am_open = 0;
   1069 	return (AUDITD_SUCCESS);
   1070 }
   1071