xref: /illumos-gate/usr/src/lib/auditd_plugins/binfile/binfile.c (revision 89b2a9fbeabf42fa54594df0e5927bcc50a07cc9)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * write binary audit records directly to a file.
26  */
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
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 		__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 				__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 			__logpost("");
1021 			*error = strdup(gettext("no directories configured"));
1022 			return (AUDITD_RETRY);
1023 		} else if (status == AUDITD_NO_MEMORY) {
1024 			__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