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