xref: /titanic_50/usr/src/lib/auditd_plugins/binfile/binfile.c (revision 2df1fe9ca32bb227b9158c67f5c00b54c20b10fd)
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) close(currentdir->dl_fd);
522 	currentdir->dl_fd = -1;
523 	(void) rename(currentdir->dl_filename, oldname);
524 
525 	DPRINT((dbfp, "binfile: Log closed %s\n", oldname));
526 
527 	free(currentdir->dl_filename);
528 	currentdir->dl_filename = NULL;
529 }
530 
531 
532 /*
533  * open_log - open a new file in the current directory.  If a
534  *	file is already open, close it.
535  *
536  *	return 1 if ok, 0 if all directories are full.
537  *
538  *	lastOpenDir - used to get the oldfile name (and change it),
539  *		to close the oldfile.
540  *
541  * The caller must hold log_mutex while calling open_log.
542  *
543  */
544 static int
545 open_log(dirlist_t *current_dir)
546 {
547 	char	auditdate[AUDIT_DATE_SZ + 1];
548 	char	oldname[AUDIT_FNAME_SZ + 1] = "";
549 	char	newname[AUDIT_FNAME_SZ + 1];
550 	char	*name;			/* pointer into oldname */
551 	int	opened;
552 	int	error = 0;
553 	int	newfd = 0;
554 
555 	static char		host[MAXHOSTNAMELEN + 1] = "";
556 	/* previous directory with open log file */
557 	static dirlist_t	*lastOpenDir = NULL;
558 
559 	if (host[0] == '\0')
560 		(void) gethostname(host, MAXHOSTNAMELEN);
561 
562 	/* Get a filename which does not already exist */
563 	opened = 0;
564 	while (!opened) {
565 		getauditdate(auditdate);
566 		(void) snprintf(newname, AUDIT_FNAME_SZ,
567 		    "%s/%s.not_terminated.%s",
568 		    current_dir->dl_dirname, auditdate, host);
569 		newfd = open(newname,
570 		    O_RDWR | O_APPEND | O_CREAT | O_EXCL, 0640);
571 		if (newfd < 0) {
572 			switch (errno) {
573 			case EEXIST:
574 				DPRINT((dbfp,
575 				    "open_log says duplicate for %s "
576 				    "(will try another)\n", newname));
577 				(void) sleep(1);
578 				break;
579 			default:
580 				/* open failed */
581 				DPRINT((dbfp,
582 				    "open_log says full for %s: %s\n",
583 				    newname, strerror(errno)));
584 				current_dir->dl_space = SPACE_FULL;
585 				current_dir = current_dir->dl_next;
586 				return (0);
587 			} /* switch */
588 		} else
589 			opened = 1;
590 	} /* while */
591 
592 	/*
593 	 * When we get here, we have opened our new log file.
594 	 * Now we need to update the name of the old file to
595 	 * store in this file's header.  lastOpenDir may point
596 	 * to current_dir if the list is only one entry long and
597 	 * there is only one list.
598 	 */
599 	if ((lastOpenDir != NULL) && (lastOpenDir->dl_filename != NULL)) {
600 		(void) strlcpy(oldname, lastOpenDir->dl_filename,
601 		    AUDIT_FNAME_SZ);
602 		name = (char *)strrchr(oldname, '/') + 1;
603 
604 		(void) memcpy(name + AUDIT_DATE_SZ + 1, auditdate,
605 		    AUDIT_DATE_SZ);
606 
607 		close_log(lastOpenDir, oldname, newname);
608 	}
609 	error = write_file_token(newfd, oldname);
610 	if (error) {
611 		/* write token failed */
612 		(void) close(newfd);
613 
614 		current_dir->dl_space = SPACE_FULL;
615 		current_dir->dl_fd = -1;
616 		free(current_dir->dl_filename);
617 		current_dir->dl_filename = NULL;
618 		current_dir = current_dir->dl_next;
619 		return (0);
620 	} else {
621 		lastOpenDir = current_dir;
622 		current_dir->dl_fd = newfd;
623 		current_dir->dl_filename = strdup(newname);
624 
625 		__logpost(newname);
626 
627 		DPRINT((dbfp, "binfile: Log opened: %s\n", newname));
628 		return (1);
629 	}
630 }
631 
632 #define	IGNORE_SIZE	8192
633 /*
634  * spacecheck - determine whether the given directory's filesystem
635  *	has the at least the space requested.  Also set the space
636  *	value in the directory list structure.  If the caller
637  *	passes other than PLENTY_SPACE or SOFT_SPACE, the caller should
638  *	ignore the return value.  Otherwise, 0 = less than the
639  *	requested space is available, 1 = at least the requested space
640  *	is available.
641  *
642  *	log_mutex must be held by the caller
643  *
644  *	-1 is returned if stat fails
645  *
646  * IGNORE_SIZE is one page (Sol 9 / 10 timeframe) and is the default
647  * buffer size written for Sol 9 and earlier.  To keep the same accuracy
648  * for the soft limit check as before, spacecheck checks for space
649  * remaining IGNORE_SIZE bytes.  This reduces the number of statvfs()
650  * calls and related math.
651  *
652  * globals -
653  *	minfree - the soft limit, i.e., the % of filesystem to reserve
654  */
655 static int
656 spacecheck(dirlist_t *thisdir, int test_limit, size_t next_buf_size)
657 {
658 	struct statvfs	sb;
659 	static int	ignore_size = 0;
660 
661 	ignore_size += next_buf_size;
662 
663 	if ((test_limit == PLENTY_SPACE) && (ignore_size < IGNORE_SIZE))
664 		return (1);
665 
666 	assert(thisdir != NULL);
667 
668 	if (statvfs(thisdir->dl_dirname, &sb) < 0) {
669 		thisdir->dl_space = SPACE_FULL;
670 		minfreeblocks = AVAIL_MIN;
671 		return (-1);
672 	} else {
673 		minfreeblocks = ((minfree * sb.f_blocks) / 100) + AVAIL_MIN;
674 
675 		if (sb.f_bavail < AVAIL_MIN)
676 			thisdir->dl_space = SPACE_FULL;
677 		else if (sb.f_bavail > minfreeblocks) {
678 			thisdir->dl_space = fullness_state = PLENTY_SPACE;
679 			ignore_size = 0;
680 		} else
681 			thisdir->dl_space = SOFT_SPACE;
682 	}
683 	if (thisdir->dl_space == PLENTY_SPACE)
684 		return (1);
685 
686 	return (thisdir->dl_space == test_limit);
687 }
688 
689 /*
690  * auditd_plugin() writes a buffer to the currently open file. The
691  * global "openNewFile" is used to force a new log file for cases
692  * such as the initial open, when minfree is reached or the current
693  * file system fills up, and "audit -s" with changed audit_control
694  * data.  For "audit -n" a new log file is opened immediately in
695  * auditd_plugin_open().
696  *
697  * This function manages one or more audit directories as follows:
698  *
699  * 	If the current open file is in a directory that has not
700  *	reached the soft limit, write the input data and return.
701  *
702  *	Scan the list of directories for one which has not reached
703  *	the soft limit; if one is found, write and return.  Such
704  *	a writable directory is in "PLENTY_SPACE" state.
705  *
706  *	Scan the list of directories for one which has not reached
707  *	the hard limit; if one is found, write and return.  This
708  *	directory in in "SOFT_SPACE" state.
709  *
710  * Oh, and if a write fails, handle it like a hard space limit.
711  *
712  * audit_warn (via __audit_dowarn()) is used to alert an operator
713  * at various levels of fullness.
714  */
715 /* ARGSUSED */
716 auditd_rc_t
717 auditd_plugin(const char *input, size_t in_len, uint32_t sequence, char **error)
718 {
719 	auditd_rc_t	rc = AUDITD_FAIL;
720 	int		open_status;
721 	size_t		out_len;
722 	/* LINTED */
723 	int		statrc;
724 	/* avoid excess audit_warnage */
725 	static int	allsoftfull_warning = 0;
726 	static int	allhard_pause = 0;
727 	static struct timeval	next_allhard;
728 	struct timeval	now;
729 #if DEBUG
730 	static char	*last_file_written_to = NULL;
731 	static uint32_t	last_sequence = 0;
732 	static uint32_t	write_count = 0;
733 
734 	if ((last_sequence > 0) && (sequence != last_sequence + 1))
735 		fprintf(dbfp, "binfile: buffer sequence=%d but prev=%d=n",
736 		    sequence, last_sequence);
737 	last_sequence = sequence;
738 
739 	fprintf(dbfp, "binfile: input seq=%d, len=%d\n",
740 	    sequence, in_len);
741 #endif
742 	*error = NULL;
743 	/*
744 	 * lock is for activeDir, referenced by open_log() and close_log()
745 	 */
746 	(void) pthread_mutex_lock(&log_mutex);
747 	while (rc == AUDITD_FAIL) {
748 		open_status = 1;
749 		if (openNewFile) {
750 			open_status = open_log(activeDir);
751 			if (open_status == 1)	/* ok */
752 				openNewFile = 0;
753 		}
754 		/*
755 		 * consider "space ok" return and error return the same;
756 		 * a -1 means spacecheck couldn't check for space.
757 		 */
758 		if ((open_status == 1) &&
759 		    (statrc = spacecheck(activeDir, fullness_state,
760 		    in_len)) != 0) {
761 #if DEBUG
762 			DPRINT((dbfp, "binfile: returned from spacecheck\n"));
763 			/*
764 			 * The last copy of last_file_written_to is
765 			 * never free'd, so there will be one open
766 			 * memory reference on exit.  It's debug only.
767 			 */
768 			if ((last_file_written_to != NULL) &&
769 			    (strcmp(last_file_written_to,
770 			    activeDir->dl_filename) != 0)) {
771 				DPRINT((dbfp, "binfile:  now writing to %s\n",
772 				    activeDir->dl_filename));
773 				free(last_file_written_to);
774 			}
775 			DPRINT((dbfp, "binfile:  finished some debug stuff\n"));
776 			last_file_written_to =
777 			    strdup(activeDir->dl_filename);
778 #endif
779 			out_len = write(activeDir->dl_fd, input, in_len);
780 			DPRINT((dbfp, "binfile:  finished the write\n"));
781 
782 			if (out_len == in_len) {
783 				DPRINT((dbfp,
784 				    "binfile: write_count=%u, sequence=%u,"
785 				    " l=%u\n",
786 				    ++write_count, sequence, out_len));
787 				allsoftfull_warning = 0;
788 				activeDir->dl_flags = 0;
789 
790 				rc = AUDITD_SUCCESS;
791 				break;
792 			} else if (!(activeDir->dl_flags & HARD_WARNED)) {
793 				DPRINT((dbfp,
794 				    "binfile: write failed, sequence=%u, "
795 				    "l=%u\n", sequence, out_len));
796 				DPRINT((dbfp, "hard warning sent.\n"));
797 				__audit_dowarn("hard", activeDir->dl_dirname,
798 				    0);
799 
800 				activeDir->dl_flags |= HARD_WARNED;
801 			}
802 		} else {
803 			DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n",
804 			    statrc, fullness_state));
805 			if (!(activeDir->dl_flags & SOFT_WARNED) &&
806 			    (activeDir->dl_space == SOFT_SPACE)) {
807 				DPRINT((dbfp, "soft warning sent\n"));
808 				__audit_dowarn("soft",
809 				    activeDir->dl_dirname, 0);
810 				activeDir->dl_flags |= SOFT_WARNED;
811 			}
812 			if (!(activeDir->dl_flags & HARD_WARNED) &&
813 			    (activeDir->dl_space == SPACE_FULL)) {
814 				DPRINT((dbfp, "hard warning sent.\n"));
815 				__audit_dowarn("hard",
816 				    activeDir->dl_dirname, 0);
817 				activeDir->dl_flags |= HARD_WARNED;
818 			}
819 		}
820 		DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n",
821 		    activeDir->dl_dirname, activeDir->dl_next->dl_dirname));
822 
823 		activeDir = activeDir->dl_next;
824 		openNewFile = 1;
825 
826 		if (activeDir == startdir) {		/* full circle */
827 			if (fullness_state == PLENTY_SPACE) {	/* once */
828 				fullness_state = SOFT_SPACE;
829 				if (allsoftfull_warning == 0) {
830 					allsoftfull_warning++;
831 					__audit_dowarn("allsoft", "", 0);
832 				}
833 			} else {			/* full circle twice */
834 				if ((hung_count > 0) && !allhard_pause) {
835 					allhard_pause = 1;
836 					(void) gettimeofday(&next_allhard,
837 					    NULL);
838 					next_allhard.tv_sec += ALLHARD_DELAY;
839 				}
840 
841 				if (allhard_pause) {
842 					(void) gettimeofday(&now, NULL);
843 					if (now.tv_sec >= next_allhard.tv_sec) {
844 						allhard_pause = 0;
845 						__audit_dowarn("allhard", "",
846 						    ++hung_count);
847 					}
848 				} else {
849 					__audit_dowarn("allhard", "",
850 					    ++hung_count);
851 				}
852 				minfreeblocks = AVAIL_MIN;
853 				rc = AUDITD_RETRY;
854 				*error = strdup(gettext(
855 				    "all partitions full\n"));
856 				__logpost("");
857 			}
858 		}
859 	}
860 	(void) pthread_mutex_unlock(&log_mutex);
861 
862 	return (rc);
863 }
864 
865 
866 /*
867  * the open function uses getacdir() and getacmin to determine which
868  * directories to use and when to switch.  It takes no inputs.
869  *
870  * It may be called multiple times as auditd handles SIGHUP and SIGUSR1
871  * corresponding to the audit(1M) flags -s and -n
872  *
873  * kvlist is NULL only if auditd caught a SIGUSR1, so after the first
874  * time open is called, the reason is -s if kvlist != NULL and -n
875  * otherwise.
876  *
877  */
878 
879 auditd_rc_t
880 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
881 {
882 	int		rc = 0;
883 	int		status;
884 	int		reason;
885 	char		*dirlist;
886 	char		*minfree;
887 	kva_t		*kv;
888 
889 	*error = NULL;
890 	*ret_list = NULL;
891 	kv = (kva_t *)kvlist;
892 
893 	if (am_open) {
894 		if (kvlist == NULL)
895 			reason = 1;	/* audit -n */
896 		else
897 			reason = 2;	/* audit -s */
898 	} else {
899 		reason = 0;		/* initial open */
900 #if DEBUG
901 		dbfp = __auditd_debug_file_open();
902 #endif
903 	}
904 	DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason));
905 
906 	am_open = 1;
907 
908 	if (kvlist == NULL) {
909 		dirlist = NULL;
910 		minfree = NULL;
911 	} else {
912 		dirlist = kva_match(kv, "p_dir");
913 		minfree = kva_match(kv, "p_minfree");
914 	}
915 	switch (reason) {
916 	case 0:			/* initial open */
917 		if (!binfile_is_open)
918 			(void) pthread_mutex_init(&log_mutex, NULL);
919 		binfile_is_open = 1;
920 		openNewFile = 1;
921 		/* FALLTHRU */
922 	case 2:			/* audit -s */
923 		fullness_state = PLENTY_SPACE;
924 		status = loadauditlist(dirlist, minfree);
925 
926 		if (status == -1) {
927 			__logpost("");
928 			*error = strdup(gettext("no directories configured"));
929 			return (AUDITD_RETRY);
930 		} else if (status == AUDITD_NO_MEMORY) {
931 			__logpost("");
932 			*error = strdup(gettext("no memory"));
933 			return (status);
934 		} else {	/* status is 0 or -2 (no change or changed) */
935 			hung_count = 0;
936 			DPRINT((dbfp, "binfile: loadauditlist returned %d\n",
937 			    status));
938 		}
939 		break;
940 	case 1:			/* audit -n */
941 		(void) pthread_mutex_lock(&log_mutex);
942 		if (open_log(activeDir) == 1)	/* ok */
943 			openNewFile = 0;
944 		(void) pthread_mutex_unlock(&log_mutex);
945 		break;
946 	}
947 
948 	rc = AUDITD_SUCCESS;
949 	*ret_list = NULL;
950 
951 	return (rc);
952 }
953 
954 auditd_rc_t
955 auditd_plugin_close(char **error)
956 {
957 	*error = NULL;
958 
959 	(void) pthread_mutex_lock(&log_mutex);
960 	close_log(activeDir, "", "");
961 	freedirlist(activeDir);
962 	activeDir = NULL;
963 	(void) pthread_mutex_unlock(&log_mutex);
964 
965 	DPRINT((dbfp, "binfile:  closed\n"));
966 
967 	if (binfile_is_open) {
968 		(void) pthread_mutex_destroy(&log_mutex);
969 		binfile_is_open = 0;
970 		/* LINTED */
971 	} else {
972 		DPRINT((dbfp,
973 		    "auditd_plugin_close() called when already closed."));
974 	}
975 	am_open = 0;
976 	return (AUDITD_SUCCESS);
977 }
978