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 = 0;
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(5)\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 /*
697 * strtol() returns a long which could be larger than int so
698 * store here for sanity checking first
699 */
700 long proposed_maxsize;
701
702 if (maxsize != NULL) {
703 /*
704 * There is no explicit error return from strtol() so
705 * we may need to depend on the value of errno.
706 */
707 errno = 0;
708 proposed_maxsize = strtol(maxsize, (char **)NULL, 10);
709
710 /*
711 * If sizeof(long) is greater than sizeof(int) on this
712 * platform, proposed_maxsize might be greater than
713 * INT_MAX without it being reported as ERANGE.
714 */
715 if ((errno == ERANGE) ||
716 ((proposed_maxsize != 0) &&
717 (proposed_maxsize < FSIZE_MIN)) ||
718 (proposed_maxsize > INT_MAX)) {
719 binfile_maxsize = 0;
720 DPRINT((dbfp, "binfile: p_fsize parameter out of "
721 "range: %s\n", maxsize));
722 /*
723 * Inform administrator of the error via
724 * syslog
725 */
726 __audit_syslog("audit_binfile.so",
727 LOG_CONS | LOG_NDELAY,
728 LOG_DAEMON, LOG_ERR,
729 gettext("p_fsize parameter out of range\n"));
730 } else {
731 binfile_maxsize = proposed_maxsize;
732 }
733 } else { /* p_fsize string was not present */
734 binfile_maxsize = 0;
735 }
736
737 DPRINT((dbfp, "binfile: set maxsize to %d\n", binfile_maxsize));
738 }
739
740 /*
741 * auditd_plugin() writes a buffer to the currently open file. The
742 * global "openNewFile" is used to force a new log file for cases such
743 * as the initial open, when minfree is reached, the p_fsize value is
744 * exceeded or the current file system fills up, and "audit -s" with
745 * changed parameters. For "audit -n" a new log file is opened
746 * immediately in auditd_plugin_open().
747 *
748 * This function manages one or more audit directories as follows:
749 *
750 * If the current open file is in a directory that has not
751 * reached the soft limit, write the input data and return.
752 *
753 * Scan the list of directories for one which has not reached
754 * the soft limit; if one is found, write and return. Such
755 * a writable directory is in "PLENTY_SPACE" state.
756 *
757 * Scan the list of directories for one which has not reached
758 * the hard limit; if one is found, write and return. This
759 * directory in in "SOFT_SPACE" state.
760 *
761 * Oh, and if a write fails, handle it like a hard space limit.
762 *
763 * audit_warn (via __audit_dowarn()) is used to alert an operator
764 * at various levels of fullness.
765 */
766 /* ARGSUSED */
767 auditd_rc_t
auditd_plugin(const char * input,size_t in_len,uint64_t sequence,char ** error)768 auditd_plugin(const char *input, size_t in_len, uint64_t sequence, char **error)
769 {
770 auditd_rc_t rc = AUDITD_FAIL;
771 int open_status;
772 size_t out_len;
773 /* avoid excess audit_warnage */
774 static int allsoftfull_warning = 0;
775 static int allhard_pause = 0;
776 static struct timeval next_allhard;
777 struct timeval now;
778 #if DEBUG
779 int statrc;
780 static char *last_file_written_to = NULL;
781 static uint64_t last_sequence = 0;
782 static uint64_t write_count = 0;
783
784 if ((last_sequence > 0) && (sequence != last_sequence + 1))
785 (void) fprintf(dbfp,
786 "binfile: buffer sequence=%llu but prev=%llu=n",
787 sequence, last_sequence);
788 last_sequence = sequence;
789
790 (void) fprintf(dbfp, "binfile: input seq=%llu, len=%d\n",
791 sequence, in_len);
792 #endif
793 *error = NULL;
794 /*
795 * lock is for activeDir, referenced by open_log() and close_log()
796 */
797 (void) pthread_mutex_lock(&log_mutex);
798
799 /*
800 * If this would take us over the maximum size, open a new
801 * file, unless maxsize is 0, in which case growth of the
802 * audit log is unrestricted.
803 */
804 if ((binfile_maxsize != 0) &&
805 ((binfile_cursize + in_len) > binfile_maxsize)) {
806 DPRINT((dbfp, "binfile: maxsize exceeded, opening new audit "
807 "file.\n"));
808 openNewFile = 1;
809 }
810
811 while (rc == AUDITD_FAIL) {
812 open_status = 1;
813 if (openNewFile) {
814 open_status = open_log(activeDir);
815 if (open_status == 1) /* ok */
816 openNewFile = 0;
817 }
818 /*
819 * consider "space ok" return and error return the same;
820 * a -1 means spacecheck couldn't check for space.
821 */
822 #if !DEBUG
823 if ((open_status == 1) &&
824 (spacecheck(activeDir, fullness_state, in_len) != 0)) {
825 #else
826 if ((open_status == 1) &&
827 (statrc = spacecheck(activeDir, fullness_state,
828 in_len) != 0)) {
829 DPRINT((dbfp, "binfile: returned from spacecheck\n"));
830 /*
831 * The last copy of last_file_written_to is
832 * never free'd, so there will be one open
833 * memory reference on exit. It's debug only.
834 */
835 if ((last_file_written_to != NULL) &&
836 (strcmp(last_file_written_to,
837 activeDir->dl_filename) != 0)) {
838 DPRINT((dbfp, "binfile: now writing to %s\n",
839 activeDir->dl_filename));
840 free(last_file_written_to);
841 }
842 DPRINT((dbfp, "binfile: finished some debug stuff\n"));
843 last_file_written_to =
844 strdup(activeDir->dl_filename);
845 #endif
846 out_len = write(activeDir->dl_fd, input, in_len);
847 DPRINT((dbfp, "binfile: finished the write\n"));
848
849 binfile_cursize += out_len;
850
851 if (out_len == in_len) {
852 DPRINT((dbfp,
853 "binfile: write_count=%llu, sequence=%llu,"
854 " l=%u\n",
855 ++write_count, sequence, out_len));
856 allsoftfull_warning = 0;
857 activeDir->dl_flags = 0;
858
859 rc = AUDITD_SUCCESS;
860 break;
861 } else if (!(activeDir->dl_flags & HARD_WARNED)) {
862 DPRINT((dbfp,
863 "binfile: write failed, sequence=%llu, "
864 "l=%u\n", sequence, out_len));
865 DPRINT((dbfp, "hard warning sent.\n"));
866 __audit_dowarn("hard", activeDir->dl_dirname,
867 0);
868
869 activeDir->dl_flags |= HARD_WARNED;
870 }
871 } else {
872 DPRINT((dbfp, "binfile: statrc=%d, fullness_state=%d\n",
873 statrc, fullness_state));
874 if (!(activeDir->dl_flags & SOFT_WARNED) &&
875 (activeDir->dl_space == SOFT_SPACE)) {
876 DPRINT((dbfp, "soft warning sent\n"));
877 __audit_dowarn("soft",
878 activeDir->dl_dirname, 0);
879 activeDir->dl_flags |= SOFT_WARNED;
880 }
881 if (!(activeDir->dl_flags & HARD_WARNED) &&
882 (activeDir->dl_space == SPACE_FULL)) {
883 DPRINT((dbfp, "hard warning sent.\n"));
884 __audit_dowarn("hard",
885 activeDir->dl_dirname, 0);
886 activeDir->dl_flags |= HARD_WARNED;
887 }
888 }
889 DPRINT((dbfp, "binfile: activeDir=%s, next=%s\n",
890 activeDir->dl_dirname, activeDir->dl_next->dl_dirname));
891
892 activeDir = activeDir->dl_next;
893 openNewFile = 1;
894
895 if (activeDir == startdir) { /* full circle */
896 if (fullness_state == PLENTY_SPACE) { /* once */
897 fullness_state = SOFT_SPACE;
898 if (allsoftfull_warning == 0) {
899 allsoftfull_warning++;
900 __audit_dowarn("allsoft", "", 0);
901 }
902 } else { /* full circle twice */
903 if ((hung_count > 0) && !allhard_pause) {
904 allhard_pause = 1;
905 (void) gettimeofday(&next_allhard,
906 NULL);
907 next_allhard.tv_sec += ALLHARD_DELAY;
908 }
909
910 if (allhard_pause) {
911 (void) gettimeofday(&now, NULL);
912 if (now.tv_sec >= next_allhard.tv_sec) {
913 allhard_pause = 0;
914 __audit_dowarn("allhard", "",
915 ++hung_count);
916 }
917 } else {
918 __audit_dowarn("allhard", "",
919 ++hung_count);
920 }
921 minfreeblocks = AVAIL_MIN;
922 rc = AUDITD_RETRY;
923 *error = strdup(gettext(
924 "all partitions full\n"));
925 (void) __logpost("");
926 }
927 }
928 }
929 (void) pthread_mutex_unlock(&log_mutex);
930
931 return (rc);
932 }
933
934
935 /*
936 * It may be called multiple times as auditd handles SIGHUP and SIGUSR1
937 * corresponding to the audit(8) flags -s and -n
938 *
939 * kvlist is NULL only if auditd caught a SIGUSR1 (audit -n), so after the first
940 * time open is called; the reason is -s if kvlist != NULL and -n otherwise.
941 *
942 */
943 auditd_rc_t
944 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
945 {
946 int rc = 0;
947 int status;
948 int reason;
949 char *dirlist;
950 char *minfree;
951 char *maxsize;
952 kva_t *kv;
953
954 *error = NULL;
955 *ret_list = NULL;
956 kv = (kva_t *)kvlist;
957
958 if (am_open) {
959 if (kvlist == NULL)
960 reason = 1; /* audit -n */
961 else
962 reason = 2; /* audit -s */
963 } else {
964 reason = 0; /* initial open */
965 #if DEBUG
966 dbfp = __auditd_debug_file_open();
967 #endif
968 }
969 DPRINT((dbfp, "binfile: am_open=%d, reason=%d\n", am_open, reason));
970
971 am_open = 1;
972
973 if (kvlist == NULL) {
974 dirlist = NULL;
975 minfree = NULL;
976 maxsize = NULL;
977 } else {
978 dirlist = kva_match(kv, "p_dir");
979 minfree = kva_match(kv, "p_minfree");
980 maxsize = kva_match(kv, "p_fsize");
981 }
982 switch (reason) {
983 case 0: /* initial open */
984 if (!binfile_is_open)
985 (void) pthread_mutex_init(&log_mutex, NULL);
986 binfile_is_open = 1;
987 openNewFile = 1;
988 /* FALLTHRU */
989 case 2: /* audit -s */
990 /* handle p_fsize parameter */
991 save_maxsize(maxsize);
992
993 fullness_state = PLENTY_SPACE;
994 status = loadauditlist(dirlist, minfree);
995
996 if (status == -1) {
997 (void) __logpost("");
998 *error = strdup(gettext("no directories configured"));
999 return (AUDITD_RETRY);
1000 } else if (status == AUDITD_NO_MEMORY) {
1001 (void) __logpost("");
1002 *error = strdup(gettext("no memory"));
1003 return (status);
1004 } else { /* status is 0 or -2 (no change or changed) */
1005 hung_count = 0;
1006 DPRINT((dbfp, "binfile: loadauditlist returned %d\n",
1007 status));
1008 }
1009 break;
1010 case 1: /* audit -n */
1011 (void) pthread_mutex_lock(&log_mutex);
1012 if (open_log(activeDir) == 1) /* ok */
1013 openNewFile = 0;
1014 (void) pthread_mutex_unlock(&log_mutex);
1015 break;
1016 }
1017
1018 rc = AUDITD_SUCCESS;
1019 *ret_list = NULL;
1020
1021 return (rc);
1022 }
1023
1024 auditd_rc_t
1025 auditd_plugin_close(char **error)
1026 {
1027 *error = NULL;
1028
1029 (void) pthread_mutex_lock(&log_mutex);
1030 close_log(&lastOpenDir, "", "");
1031 freedirlist(activeDir);
1032 activeDir = NULL;
1033 (void) pthread_mutex_unlock(&log_mutex);
1034
1035 DPRINT((dbfp, "binfile: closed\n"));
1036
1037 (void) __logpost("");
1038
1039 if (binfile_is_open) {
1040 (void) pthread_mutex_destroy(&log_mutex);
1041 binfile_is_open = 0;
1042 #if DEBUG
1043 } else {
1044 (void) fprintf(dbfp,
1045 "auditd_plugin_close() called when already closed.");
1046 #endif
1047 }
1048 am_open = 0;
1049 return (AUDITD_SUCCESS);
1050 }
1051