xref: /illumos-gate/usr/src/lib/auditd_plugins/binfile/binfile.c (revision 598f4ceed9327d2d6c2325dd67cae3aa06f7fea6)
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;
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(4)\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 	 * strtol() returns a long which could be larger than int so
697 	 * store here for sanity checking first
698 	 */
699 	long proposed_maxsize;
700 
701 	if (maxsize != NULL) {
702 		/*
703 		 * There is no explicit error return from strtol() so
704 		 * we may need to depend on the value of errno.
705 		 */
706 		errno = 0;
707 		proposed_maxsize = strtol(maxsize, (char **)NULL, 10);
708 
709 		/*
710 		 * If sizeof(long) is greater than sizeof(int) on this
711 		 * platform, proposed_maxsize might be greater than
712 		 * INT_MAX without it being reported as ERANGE.
713 		 */
714 		if ((errno == ERANGE) ||
715 		    ((proposed_maxsize != 0) &&
716 			(proposed_maxsize < FSIZE_MIN)) ||
717 		    (proposed_maxsize > INT_MAX)) {
718 			binfile_maxsize = 0;
719 			DPRINT((dbfp, "binfile: p_fsize parameter out of "
720 					"range: %s\n", maxsize));
721 			/*
722 			 * Inform administrator of the error via
723 			 * syslog
724 			 */
725 			__audit_syslog("audit_binfile.so",
726 			    LOG_CONS | LOG_NDELAY,
727 			    LOG_DAEMON, LOG_ERR,
728 			    gettext("p_fsize parameter out of range\n"));
729 		} else {
730 			binfile_maxsize = proposed_maxsize;
731 		}
732 	} else { /* p_fsize string was not present */
733 		binfile_maxsize = 0;
734 	}
735 
736 	DPRINT((dbfp, "binfile: set maxsize to %d\n", binfile_maxsize));
737 }
738 
739 /*
740  * auditd_plugin() writes a buffer to the currently open file. The
741  * global "openNewFile" is used to force a new log file for cases such
742  * as the initial open, when minfree is reached, the p_fsize value is
743  * exceeded or the current file system fills up, and "audit -s" with
744  * changed parameters.  For "audit -n" a new log file is opened
745  * immediately in auditd_plugin_open().
746  *
747  * This function manages one or more audit directories as follows:
748  *
749  * 	If the current open file is in a directory that has not
750  *	reached the soft limit, write the input data and return.
751  *
752  *	Scan the list of directories for one which has not reached
753  *	the soft limit; if one is found, write and return.  Such
754  *	a writable directory is in "PLENTY_SPACE" state.
755  *
756  *	Scan the list of directories for one which has not reached
757  *	the hard limit; if one is found, write and return.  This
758  *	directory in in "SOFT_SPACE" state.
759  *
760  * Oh, and if a write fails, handle it like a hard space limit.
761  *
762  * audit_warn (via __audit_dowarn()) is used to alert an operator
763  * at various levels of fullness.
764  */
765 /* ARGSUSED */
766 auditd_rc_t
767 auditd_plugin(const char *input, size_t in_len, uint64_t sequence, char **error)
768 {
769 	auditd_rc_t	rc = AUDITD_FAIL;
770 	int		open_status;
771 	size_t		out_len;
772 	/* avoid excess audit_warnage */
773 	static int	allsoftfull_warning = 0;
774 	static int	allhard_pause = 0;
775 	static struct timeval	next_allhard;
776 	struct timeval	now;
777 #if DEBUG
778 	int		statrc;
779 	static char	*last_file_written_to = NULL;
780 	static uint64_t	last_sequence = 0;
781 	static uint64_t	write_count = 0;
782 
783 	if ((last_sequence > 0) && (sequence != last_sequence + 1))
784 		(void) fprintf(dbfp,
785 		    "binfile: buffer sequence=%llu but prev=%llu=n",
786 		    sequence, last_sequence);
787 	last_sequence = sequence;
788 
789 	(void) fprintf(dbfp, "binfile: input seq=%llu, len=%d\n",
790 	    sequence, in_len);
791 #endif
792 	*error = NULL;
793 	/*
794 	 * lock is for activeDir, referenced by open_log() and close_log()
795 	 */
796 	(void) pthread_mutex_lock(&log_mutex);
797 
798 	/*
799 	 * If this would take us over the maximum size, open a new
800 	 * file, unless maxsize is 0, in which case growth of the
801 	 * audit log is unrestricted.
802 	 */
803 	if ((binfile_maxsize != 0) &&
804 	    ((binfile_cursize + in_len) > binfile_maxsize)) {
805 		DPRINT((dbfp, "binfile: maxsize exceeded, opening new audit "
806 		    "file.\n"));
807 		openNewFile = 1;
808 	}
809 
810 	while (rc == AUDITD_FAIL) {
811 		open_status = 1;
812 		if (openNewFile) {
813 			open_status = open_log(activeDir);
814 			if (open_status == 1)	/* ok */
815 				openNewFile = 0;
816 		}
817 		/*
818 		 * consider "space ok" return and error return the same;
819 		 * a -1 means spacecheck couldn't check for space.
820 		 */
821 #if !DEBUG
822 		if ((open_status == 1) &&
823 		    (spacecheck(activeDir, fullness_state, in_len) != 0)) {
824 #else
825 		if ((open_status == 1) &&
826 		    (statrc = spacecheck(activeDir, fullness_state,
827 		    in_len) != 0)) {
828 			DPRINT((dbfp, "binfile: returned from spacecheck\n"));
829 			/*
830 			 * The last copy of last_file_written_to is
831 			 * never free'd, so there will be one open
832 			 * memory reference on exit.  It's debug only.
833 			 */
834 			if ((last_file_written_to != NULL) &&
835 			    (strcmp(last_file_written_to,
836 			    activeDir->dl_filename) != 0)) {
837 				DPRINT((dbfp, "binfile:  now writing to %s\n",
838 				    activeDir->dl_filename));
839 				free(last_file_written_to);
840 			}
841 			DPRINT((dbfp, "binfile:  finished some debug stuff\n"));
842 			last_file_written_to =
843 			    strdup(activeDir->dl_filename);
844 #endif
845 			out_len = write(activeDir->dl_fd, input, in_len);
846 			DPRINT((dbfp, "binfile:  finished the write\n"));
847 
848 			binfile_cursize += out_len;
849 
850 			if (out_len == in_len) {
851 				DPRINT((dbfp,
852 				    "binfile: write_count=%llu, sequence=%llu,"
853 				    " l=%u\n",
854 				    ++write_count, sequence, out_len));
855 				allsoftfull_warning = 0;
856 				activeDir->dl_flags = 0;
857 
858 				rc = AUDITD_SUCCESS;
859 				break;
860 			} else if (!(activeDir->dl_flags & HARD_WARNED)) {
861 				DPRINT((dbfp,
862 				    "binfile: write failed, sequence=%llu, "
863 				    "l=%u\n", sequence, out_len));
864 				DPRINT((dbfp, "hard warning sent.\n"));
865 				__audit_dowarn("hard", activeDir->dl_dirname,
866 				    0);
867 
868 				activeDir->dl_flags |= HARD_WARNED;
869 			}
870 		} else {
871 			DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n",
872 			    statrc, fullness_state));
873 			if (!(activeDir->dl_flags & SOFT_WARNED) &&
874 			    (activeDir->dl_space == SOFT_SPACE)) {
875 				DPRINT((dbfp, "soft warning sent\n"));
876 				__audit_dowarn("soft",
877 				    activeDir->dl_dirname, 0);
878 				activeDir->dl_flags |= SOFT_WARNED;
879 			}
880 			if (!(activeDir->dl_flags & HARD_WARNED) &&
881 			    (activeDir->dl_space == SPACE_FULL)) {
882 				DPRINT((dbfp, "hard warning sent.\n"));
883 				__audit_dowarn("hard",
884 				    activeDir->dl_dirname, 0);
885 				activeDir->dl_flags |= HARD_WARNED;
886 			}
887 		}
888 		DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n",
889 		    activeDir->dl_dirname, activeDir->dl_next->dl_dirname));
890 
891 		activeDir = activeDir->dl_next;
892 		openNewFile = 1;
893 
894 		if (activeDir == startdir) {		/* full circle */
895 			if (fullness_state == PLENTY_SPACE) {	/* once */
896 				fullness_state = SOFT_SPACE;
897 				if (allsoftfull_warning == 0) {
898 					allsoftfull_warning++;
899 					__audit_dowarn("allsoft", "", 0);
900 				}
901 			} else {			/* full circle twice */
902 				if ((hung_count > 0) && !allhard_pause) {
903 					allhard_pause = 1;
904 					(void) gettimeofday(&next_allhard,
905 					    NULL);
906 					next_allhard.tv_sec += ALLHARD_DELAY;
907 				}
908 
909 				if (allhard_pause) {
910 					(void) gettimeofday(&now, NULL);
911 					if (now.tv_sec >= next_allhard.tv_sec) {
912 						allhard_pause = 0;
913 						__audit_dowarn("allhard", "",
914 						    ++hung_count);
915 					}
916 				} else {
917 					__audit_dowarn("allhard", "",
918 					    ++hung_count);
919 				}
920 				minfreeblocks = AVAIL_MIN;
921 				rc = AUDITD_RETRY;
922 				*error = strdup(gettext(
923 				    "all partitions full\n"));
924 				(void) __logpost("");
925 			}
926 		}
927 	}
928 	(void) pthread_mutex_unlock(&log_mutex);
929 
930 	return (rc);
931 }
932 
933 
934 /*
935  * It may be called multiple times as auditd handles SIGHUP and SIGUSR1
936  * corresponding to the audit(1M) flags -s and -n
937  *
938  * kvlist is NULL only if auditd caught a SIGUSR1 (audit -n), so after the first
939  * time open is called; the reason is -s if kvlist != NULL and -n otherwise.
940  *
941  */
942 auditd_rc_t
943 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
944 {
945 	int		rc = 0;
946 	int		status;
947 	int		reason;
948 	char		*dirlist;
949 	char		*minfree;
950 	char		*maxsize;
951 	kva_t		*kv;
952 
953 	*error = NULL;
954 	*ret_list = NULL;
955 	kv = (kva_t *)kvlist;
956 
957 	if (am_open) {
958 		if (kvlist == NULL)
959 			reason = 1;	/* audit -n */
960 		else
961 			reason = 2;	/* audit -s */
962 	} else {
963 		reason = 0;		/* initial open */
964 #if DEBUG
965 		dbfp = __auditd_debug_file_open();
966 #endif
967 	}
968 	DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason));
969 
970 	am_open = 1;
971 
972 	if (kvlist == NULL) {
973 		dirlist = NULL;
974 		minfree = NULL;
975 		maxsize = NULL;
976 	} else {
977 		dirlist = kva_match(kv, "p_dir");
978 		minfree = kva_match(kv, "p_minfree");
979 		maxsize = kva_match(kv, "p_fsize");
980 	}
981 	switch (reason) {
982 	case 0:			/* initial open */
983 		if (!binfile_is_open)
984 			(void) pthread_mutex_init(&log_mutex, NULL);
985 		binfile_is_open = 1;
986 		openNewFile = 1;
987 		/* FALLTHRU */
988 	case 2:			/* audit -s */
989 		/* handle p_fsize parameter */
990 		save_maxsize(maxsize);
991 
992 		fullness_state = PLENTY_SPACE;
993 		status = loadauditlist(dirlist, minfree);
994 
995 		if (status == -1) {
996 			(void) __logpost("");
997 			*error = strdup(gettext("no directories configured"));
998 			return (AUDITD_RETRY);
999 		} else if (status == AUDITD_NO_MEMORY) {
1000 			(void) __logpost("");
1001 			*error = strdup(gettext("no memory"));
1002 			return (status);
1003 		} else {	/* status is 0 or -2 (no change or changed) */
1004 			hung_count = 0;
1005 			DPRINT((dbfp, "binfile: loadauditlist returned %d\n",
1006 			    status));
1007 		}
1008 		break;
1009 	case 1:			/* audit -n */
1010 		(void) pthread_mutex_lock(&log_mutex);
1011 		if (open_log(activeDir) == 1)	/* ok */
1012 			openNewFile = 0;
1013 		(void) pthread_mutex_unlock(&log_mutex);
1014 		break;
1015 	}
1016 
1017 	rc = AUDITD_SUCCESS;
1018 	*ret_list = NULL;
1019 
1020 	return (rc);
1021 }
1022 
1023 auditd_rc_t
1024 auditd_plugin_close(char **error)
1025 {
1026 	*error = NULL;
1027 
1028 	(void) pthread_mutex_lock(&log_mutex);
1029 	close_log(&lastOpenDir, "", "");
1030 	freedirlist(activeDir);
1031 	activeDir = NULL;
1032 	(void) pthread_mutex_unlock(&log_mutex);
1033 
1034 	DPRINT((dbfp, "binfile:  closed\n"));
1035 
1036 	(void) __logpost("");
1037 
1038 	if (binfile_is_open) {
1039 		(void) pthread_mutex_destroy(&log_mutex);
1040 		binfile_is_open = 0;
1041 #if DEBUG
1042 	} else {
1043 		(void) fprintf(dbfp,
1044 		    "auditd_plugin_close() called when already closed.");
1045 #endif
1046 	}
1047 	am_open = 0;
1048 	return (AUDITD_SUCCESS);
1049 }
1050