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