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 */
92typedef struct dirlist_s dirlist_t;
93struct 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
108static FILE		*dbfp;			/* debug file */
109#endif
110
111static pthread_mutex_t	log_mutex;
112static int		binfile_is_open = 0;
113
114static int		minfree = -1;
115static int		minfreeblocks;		/* minfree in blocks */
116
117static dirlist_t	*lastOpenDir = NULL;    /* last activeDir */
118static dirlist_t	*activeDir = NULL;	/* to be current directory */
119static dirlist_t	*startdir;		/* first dir in the ring */
120static int		activeCount = 0;	/* number of dirs in the ring */
121
122static int		openNewFile = 0;	/* need to open a new file */
123static int		hung_count = 0;		/* count of audit_warn hard */
124
125/* flag from audit_plugin_open to audit_plugin_close */
126static int		am_open = 0;
127/* preferred dir state */
128static 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 */
135static uint_t		binfile_cursize = 0;
136static uint_t		binfile_maxsize = 0;
137
138static int open_log(dirlist_t *);
139
140static void
141freedirlist(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
159dirlist_t *
160dupdirnode(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 */
188static int
189growauditlist(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 */
258static int
259loadauditlist(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 */
372static void
373getauditdate(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 */
399static int
400write_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 */
441static void
442close_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 */
504static int
505open_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 */
654static int
655spacecheck(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 */
693static void
694save_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 */
766auditd_rc_t
767auditd_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 */
942auditd_rc_t
943auditd_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
1023auditd_rc_t
1024auditd_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