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