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