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