xref: /titanic_44/usr/src/lib/auditd_plugins/remote/audit_remote.c (revision 4a16f9a6c1cc74aeed5ff36b4723c3e43bc67666)
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) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23  *
24  * send audit records to remote host
25  *
26  */
27 
28 /*
29  * auditd_plugin_open(), auditd_plugin() and auditd_plugin_close()
30  * implement a replaceable library for use by auditd; they are a
31  * project private interface and may change without notice.
32  */
33 
34 #include <arpa/inet.h>
35 #include <assert.h>
36 #include <audit_plugin.h>
37 #include <bsm/audit.h>
38 #include <bsm/audit_record.h>
39 #include <bsm/libbsm.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <gssapi/gssapi.h>
43 #include <libintl.h>
44 #include <netdb.h>
45 #include <pthread.h>
46 #include <rpc/rpcsec_gss.h>
47 #include <secdb.h>
48 #include <signal.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <strings.h>
53 #include <ctype.h>
54 #include <sys/param.h>
55 #include <sys/socket.h>
56 #include <sys/types.h>
57 #include <unistd.h>
58 #include <poll.h>
59 
60 #include "audit_remote.h"
61 
62 #define	DEFAULT_RETRIES	3	/* default connection retries */
63 #define	DEFAULT_TIMEOUT	5	/* default connection timeout (in secs) */
64 #define	NOSUCCESS_DELAY	20	/* unsuccessful delivery to all p_hosts */
65 
66 #define	FL_SET		B_TRUE	/* set_fdfl(): set the flag */
67 #define	FL_UNSET	B_FALSE	/* set_fdfl(): unset the flag */
68 
69 static int	nosuccess_cnt;	/* unsuccessful delivery counter */
70 
71 
72 static int	retries = DEFAULT_RETRIES;	/* connection retries */
73 int		timeout = DEFAULT_TIMEOUT;	/* connection timeout */
74 static int	timeout_p_timeout = -1;		/* p_timeout attr storage */
75 
76 /* time reset mechanism; x .. timeout_p_timeout */
77 #define	RST_TIMEOUT(x)		(x != -1 ? x : DEFAULT_TIMEOUT)
78 
79 /* semi-exponential timeout back off; x .. attempts, y .. timeout */
80 #define	BOFF_TIMEOUT(x, y)	(x < 3 ? y * 2 * x : y * 8)
81 
82 /* general plugin lock */
83 pthread_mutex_t	plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
84 
85 static struct hostlist_s	*current_host;
86 static struct hostlist_s	*hosts;
87 
88 extern struct transq_hdr_s	transq_hdr;
89 static long			transq_count_max;
90 extern pthread_mutex_t		transq_lock;
91 
92 extern pthread_t	recv_tid;
93 
94 extern boolean_t	notify_pipe_ready;
95 extern int		notify_pipe[2];
96 
97 #if DEBUG
98 FILE		*dfile;		/* debug file */
99 #endif
100 
101 /*
102  * set_transq_count_max() - sets the transq_count_max value based on kernel
103  * audit queue high water mark. This is backup solution for a case, when the
104  * plugin audit_control(4) option lacks (intentionally) the qsize option.
105  */
106 static auditd_rc_t
107 set_transq_count_max()
108 {
109 	struct au_qctrl	qctrl;
110 
111 	if (auditon(A_GETQCTRL, (caddr_t)&qctrl, 0) != -1) {
112 		transq_count_max = qctrl.aq_hiwater;
113 		DPRINT((dfile, "Transmission queue max length set to %ld\n",
114 		    transq_count_max));
115 		return (AUDITD_SUCCESS);
116 	}
117 
118 	DPRINT((dfile, "Setting the transmission queue max length failed\n"));
119 	return (AUDITD_RETRY);
120 }
121 
122 /*
123  * get_port_default() - set the default port number; note, that "solaris-audit"
124  * used below in the code is the IANA assigned service name for the secure
125  * remote solaris audit logging.
126  */
127 static auditd_rc_t
128 get_port_default(int *port_default)
129 {
130 
131 	struct servent  serventry;
132 	char  		serventry_buf[1024];
133 
134 	if (getservbyname_r("solaris-audit", "tcp", &serventry,
135 	    (char *)&serventry_buf, sizeof (serventry_buf)) == NULL) {
136 		DPRINT((dfile, "unable to get default port number\n"));
137 #if DEBUG
138 		if (errno == ERANGE) {
139 			DPRINT((dfile, "low on buffer\n"));
140 		}
141 #endif
142 		return (AUDITD_INVALID);
143 	}
144 	*port_default = ntohs(serventry.s_port);
145 	DPRINT((dfile, "default port: %d\n", *port_default));
146 
147 	return (AUDITD_SUCCESS);
148 }
149 
150 /*
151  * trim_me() - trims the white space characters around the specified string.
152  * Inputs - pointer to the beginning of the string (str_ptr); returns - pointer
153  * to the trimmed string. Function returns NULL pointer in case of received
154  * empty string, NULL pointer or in case the pointed string consists of white
155  * space characters only.
156  */
157 static char *
158 trim_me(char *str_ptr) {
159 
160 	char	*str_end;
161 
162 	if (str_ptr == NULL || *str_ptr == '\0') {
163 		return (NULL);
164 	}
165 
166 	while (isspace(*str_ptr)) {
167 		str_ptr++;
168 	}
169 	if (*str_ptr == '\0') {
170 		return (NULL);
171 	}
172 
173 	str_end = str_ptr + strlen(str_ptr);
174 
175 	while (str_end > str_ptr && isspace(str_end[-1])) {
176 		str_end--;
177 	}
178 	*str_end = '\0';
179 
180 	return (str_ptr);
181 }
182 
183 
184 /*
185  * parsehosts() end parses the host string (hosts_str)
186  */
187 static auditd_rc_t
188 parsehosts(char *hosts_str, char **error)
189 {
190 	char 		*hostportmech, *hpm;
191 	char		*hostname;
192 	char		*port_str;
193 	char		*mech_str;
194 	int		port;
195 	int		port_default = -1;
196 	gss_OID		mech_oid;
197 	char 		*lasts_hpm;
198 	hostlist_t 	*lasthost = NULL;
199 	hostlist_t	*newhost;
200 	struct hostent 	*hostentry;
201 	int		error_num;
202 	int		rc;
203 #if DEBUG
204 	char 		addr_buf[INET6_ADDRSTRLEN];
205 	int		num_of_hosts = 0;
206 #endif
207 
208 	hosts = lasthost;
209 
210 	DPRINT((dfile, "parsing %s\n", hosts_str));
211 	while ((hostportmech = strtok_r(hosts_str, ",", &lasts_hpm)) != NULL) {
212 
213 		hosts_str = NULL;
214 		hostname = NULL;
215 		port_str = NULL;
216 		port = port_default;
217 		mech_str = NULL;
218 		mech_oid = GSS_C_NO_OID;
219 
220 		DPRINT((dfile, "parsing host:port:mech %s\n", hostportmech));
221 
222 		if (strncmp(hostportmech, ":", 1 == 0)) { /* ":port:" case */
223 			*error = strdup(gettext("no hostname specified"));
224 			return (AUDITD_INVALID);
225 		}
226 
227 		/* parse single host:port:mech target */
228 		while ((hpm = strsep(&hostportmech, ":")) != NULL) {
229 
230 			if (hostname == NULL) {
231 				hostname = hpm;
232 				continue;
233 			}
234 			if (port_str == NULL) {
235 				port_str = hpm;
236 				continue;
237 			}
238 			if (mech_str == NULL) {
239 				mech_str = hpm;
240 				continue;
241 			}
242 
243 			/* too many colons in the hostportmech string */
244 			*error = strdup(gettext("invalid host:port:mech "
245 			    "specification"));
246 			return (AUDITD_INVALID);
247 		}
248 
249 		if (hostname == NULL || *hostname == '\0') {
250 			*error = strdup(gettext("invalid hostname "
251 			    "specification"));
252 			return (AUDITD_INVALID);
253 		}
254 
255 		/* trim hostname */
256 		hostname = trim_me(hostname);
257 		if (hostname == NULL || *hostname == '\0') {
258 			*error = strdup(gettext("empty hostname "
259 			    "specification"));
260 			return (AUDITD_INVALID);
261 		}
262 
263 		DPRINT((dfile, "resolving address for %s\n", hostname));
264 
265 		hostentry = getipnodebyname(hostname, AF_INET6, 0, &error_num);
266 		if (!hostentry) {
267 			hostentry = getipnodebyname(hostname, AF_INET, 0,
268 			    &error_num);
269 		}
270 		if (!hostentry) {
271 			if (error_num == TRY_AGAIN) {
272 				*error = strdup(gettext("host not found, "
273 				    "try later"));
274 				return (AUDITD_RETRY);
275 			} else {
276 				*error = strdup(gettext("host not found"));
277 				return (AUDITD_INVALID);
278 			}
279 		}
280 		DPRINT((dfile, "hostentry: h_name=%s, addr_len=%d, addr=%s\n",
281 		    hostentry->h_name, hostentry->h_length,
282 		    inet_ntop(hostentry->h_addrtype,
283 		    hostentry->h_addr_list[0], addr_buf,
284 		    INET6_ADDRSTRLEN)));
285 
286 		/* trim port */
287 		port_str = trim_me(port_str);
288 		if (port_str == NULL || *port_str == '\0') {
289 			if (port_default == -1 &&
290 			    (rc = get_port_default(&port_default))
291 			    != AUDITD_SUCCESS) {
292 				*error = strdup(gettext(
293 				    "unable to get default port number"));
294 				return (rc);
295 			}
296 			port = port_default;
297 			DPRINT((dfile, "port: %d (default)\n", port));
298 		} else {
299 			errno = 0;
300 			port = atoi(port_str);
301 			if (errno != 0 || port < 1 || port > USHRT_MAX) {
302 				*error = strdup(gettext("invalid port number"));
303 				return (AUDITD_INVALID);
304 			}
305 			DPRINT((dfile, "port: %d\n", port));
306 		}
307 
308 		/* trim mechanism */
309 		mech_str = trim_me(mech_str);
310 		if (mech_str != NULL && *mech_str != '\0') {
311 			if (rpc_gss_mech_to_oid(mech_str, &mech_oid) != TRUE) {
312 				*error = strdup(gettext("unknown mechanism"));
313 				return (AUDITD_INVALID);
314 			}
315 			DPRINT((dfile, "mechanism: %s\n", mech_str));
316 #if DEBUG
317 		} else {
318 			DPRINT((dfile, "mechanism: null (default)\n"));
319 #endif
320 		}
321 
322 		/* add this host to host list */
323 		newhost = malloc(sizeof (hostlist_t));
324 		if (newhost == NULL) {
325 			*error = strdup(gettext("no memory"));
326 			return (AUDITD_NO_MEMORY);
327 		}
328 		newhost->host = hostentry;
329 		newhost->port = htons(port);
330 		newhost->mech = mech_oid;
331 		newhost->next_host = NULL;
332 		if (lasthost != NULL) {
333 			lasthost->next_host = newhost;
334 			lasthost = lasthost->next_host;
335 		} else {
336 			lasthost = newhost;
337 			hosts = newhost;
338 		}
339 #if DEBUG
340 		num_of_hosts++;
341 #endif
342 	}
343 
344 	current_host = hosts;
345 	DPRINT((dfile, "Configured %d hosts.\n", num_of_hosts));
346 
347 	return (AUDITD_SUCCESS);
348 }
349 
350 
351 /*
352  * Frees host list
353  */
354 static void
355 freehostlist()
356 {
357 	hostlist_t *h, *n;
358 
359 	(void) pthread_mutex_lock(&plugin_mutex);
360 	h = hosts;
361 	while (h) {
362 		n = h->next_host;
363 		freehostent(h->host);
364 		free(h);
365 		h = n;
366 	}
367 	current_host = NULL;
368 	hosts = NULL;
369 	(void) pthread_mutex_unlock(&plugin_mutex);
370 }
371 
372 #if DEBUG
373 static char *
374 auditd_message(auditd_rc_t msg_code) {
375 	char 	*rc_msg;
376 
377 	switch (msg_code) {
378 	case AUDITD_SUCCESS:
379 		rc_msg = strdup("ok");
380 		break;
381 	case AUDITD_RETRY:
382 		rc_msg = strdup("retry after a delay");
383 		break;
384 	case AUDITD_NO_MEMORY:
385 		rc_msg = strdup("can't allocate memory");
386 		break;
387 	case AUDITD_INVALID:
388 		rc_msg = strdup("bad input");
389 		break;
390 	case AUDITD_COMM_FAIL:
391 		rc_msg = strdup("communications failure");
392 		break;
393 	case AUDITD_FATAL:
394 		rc_msg = strdup("other error");
395 		break;
396 	case AUDITD_FAIL:
397 		rc_msg = strdup("other non-fatal error");
398 		break;
399 	}
400 	return (rc_msg);
401 }
402 #endif
403 
404 /*
405  * rsn_to_msg() - translation of the reason of closure identifier to the more
406  * human readable/understandable form.
407  */
408 static char *
409 rsn_to_msg(close_rsn_t reason)
410 {
411 	char 	*rc_msg;
412 
413 	switch (reason) {
414 	case RSN_UNDEFINED:
415 		rc_msg = strdup(gettext("not defined reason of failure"));
416 		break;
417 	case RSN_INIT_POLL:
418 		rc_msg = strdup(gettext("poll() initialization failed"));
419 		break;
420 	case RSN_TOK_RECV_FAILED:
421 		rc_msg = strdup(gettext("token receiving failed"));
422 		break;
423 	case RSN_TOK_TOO_BIG:
424 		rc_msg = strdup(gettext("unacceptable token size"));
425 		break;
426 	case RSN_TOK_UNVERIFIABLE:
427 		rc_msg = strdup(gettext("received unverifiable token"));
428 		break;
429 	case RSN_SOCKET_CLOSE:
430 		rc_msg = strdup(gettext("closed socket"));
431 		break;
432 	case RSN_SOCKET_CREATE:
433 		rc_msg = strdup(gettext("socket creation failed"));
434 		break;
435 	case RSN_CONNECTION_CREATE:
436 		rc_msg = strdup(gettext("connection creation failed"));
437 		break;
438 	case RSN_PROTOCOL_NEGOTIATE:
439 		rc_msg = strdup(gettext("protocol negotiation failed"));
440 		break;
441 	case RSN_GSS_CTX_ESTABLISH:
442 		rc_msg = strdup(gettext("context establishing failed"));
443 		break;
444 	case RSN_GSS_CTX_EXP:
445 		rc_msg = strdup(gettext("context expired"));
446 		break;
447 	case RSN_UNKNOWN_AF:
448 		rc_msg = strdup(gettext("unknown address family"));
449 		break;
450 	case RSN_MEMORY_ALLOCATE:
451 		rc_msg = strdup(gettext("memory allocation failed"));
452 		break;
453 	default:	/* RSN_OTHER_ERR */
454 		rc_msg = strdup(gettext("other, not classified error"));
455 		break;
456 	}
457 	return (rc_msg);
458 }
459 
460 /*
461  * set_fdfl() - based on set_fl (FL_SET/FL_UNSET) un/sets the fl flag associated
462  * with fd file descriptor.
463  */
464 static boolean_t
465 set_fdfl(int fd, int fl, boolean_t set_fl)
466 {
467 	int	flags;
468 
469 	/* power of two test - only single bit flags are allowed */
470 	if (!fl || (fl & (fl-1))) {
471 		DPRINT((dfile, "incorrect flag - %d isn't power of two\n", fl));
472 		return (B_FALSE);
473 	}
474 
475 	if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
476 		DPRINT((dfile, "cannot get file descriptor flags\n"));
477 		return (B_FALSE);
478 	}
479 
480 	if (set_fl) {	/* set the fl flag */
481 		if (flags & fl) {
482 			return (B_TRUE);
483 		}
484 
485 		flags |= fl;
486 
487 	} else {	/* unset the fl flag */
488 		if (~flags & fl) {
489 			return (B_TRUE);
490 		}
491 
492 		flags &= ~fl;
493 	}
494 
495 	if (fcntl(fd, F_SETFL, flags) == -1) {
496 		DPRINT((dfile, "cannot %s file descriptor flags\n",
497 		    (set_fl ? "set" : "unset")));
498 		return (B_FALSE);
499 	}
500 
501 	DPRINT((dfile, "fd: %d - flag: 0%o was %s\n", fd, fl,
502 	    (set_fl ? "set" : "unset")));
503 	return (B_TRUE);
504 }
505 
506 
507 /*
508  * create_notify_pipe() - creates the notification pipe. Function returns
509  * B_TRUE/B_FALSE on success/failure.
510  */
511 static boolean_t
512 create_notify_pipe(int *notify_pipe, char **error)
513 {
514 
515 	if (pipe(notify_pipe) < 0) {
516 		DPRINT((dfile, "Cannot create notify pipe: %s\n",
517 		    strerror(errno)));
518 		*error = strdup(gettext("failed to create notification pipe"));
519 		return (B_FALSE);
520 	} else {
521 		DPRINT((dfile, "Pipe created in:%d out:%d\n", notify_pipe[0],
522 		    notify_pipe[1]));
523 		/* make (only) the pipe "in" end nonblocking */
524 		if (!set_fdfl(notify_pipe[0], O_NONBLOCK, FL_UNSET) ||
525 		    !set_fdfl(notify_pipe[1], O_NONBLOCK, FL_SET)) {
526 			DPRINT((dfile, "Cannot prepare blocking scheme on top "
527 			    "of the notification pipe: %s\n", strerror(errno)));
528 			(void) close(notify_pipe[0]);
529 			(void) close(notify_pipe[1]);
530 
531 			*error = strdup(gettext("failed to prepare blocking "
532 			    "scheme on top of the notification pipe"));
533 			return (B_FALSE);
534 		}
535 	}
536 
537 	return (B_TRUE);
538 }
539 
540 
541 /*
542  * auditd_plugin() sends a record via a tcp connection.
543  *
544  * Operation:
545  *   - 1 tcp connection opened at a time, referenced by current_host->sockfd
546  *   - tries to (open and) send a record to the current_host where its address
547  *     is taken from the first hostent h_addr_list entry
548  *   - if connection times out, tries second host
549  *   - if all hosts where tried tries again for retries number of times
550  *   - if everything fails, it bails out with AUDITD_RETRY
551  *
552  *   Note, that space on stack allocated for any error message returned along
553  *   with AUDITD_RETRY is subsequently freed by auditd.
554  *
555  */
556 auditd_rc_t
557 auditd_plugin(const char *input, size_t in_len, uint64_t sequence, char **error)
558 {
559 	int 		rc = AUDITD_FAIL;
560 	int 		send_record_rc = SEND_RECORD_FAIL;
561 	hostlist_t 	*start_host;
562 	int 		attempts = 0;
563 	char		*ext_error;	/* extended error string */
564 	close_rsn_t	err_rsn = RSN_UNDEFINED;
565 	char		*rsn_msg;
566 
567 #if DEBUG
568 	char		*rc_msg;
569 	static uint64_t	last_sequence = 0;
570 
571 	if ((last_sequence > 0) && (sequence != last_sequence + 1)) {
572 		DPRINT((dfile, "audit_remote: buffer sequence=%llu "
573 		    "but prev=%llu\n", sequence, last_sequence));
574 	}
575 	last_sequence = sequence;
576 
577 	DPRINT((dfile, "audit_remote: input seq=%llu, len=%d\n",
578 	    sequence, in_len));
579 #endif
580 
581 	(void) pthread_mutex_lock(&transq_lock);
582 
583 	if (transq_hdr.count == transq_count_max) {
584 		DPRINT((dfile, "Transmission queue is full (%ld)\n",
585 		    transq_hdr.count));
586 		(void) pthread_mutex_unlock(&transq_lock);
587 		*error = strdup(gettext("retransmission queue is full"));
588 		return (AUDITD_RETRY);
589 	}
590 	(void) pthread_mutex_unlock(&transq_lock);
591 
592 
593 	(void) pthread_mutex_lock(&plugin_mutex);
594 
595 	/* cycle over the hosts and possibly deliver the record */
596 	start_host = current_host;
597 	while (rc != AUDITD_SUCCESS) {
598 		DPRINT((dfile, "Trying to send record to %s [attempt:%d/%d]\n",
599 		    current_host->host->h_name, attempts + 1, retries));
600 
601 		send_record_rc = send_record(current_host, input, in_len,
602 		    sequence, &err_rsn);
603 		DPRINT((dfile, "send_record() returned %d - ", send_record_rc));
604 
605 		switch (send_record_rc) {
606 		case SEND_RECORD_SUCCESS:
607 			DPRINT((dfile, "success\n"));
608 			nosuccess_cnt = 0;
609 			rc = AUDITD_SUCCESS;
610 			break;
611 		case SEND_RECORD_NEXT:
612 			DPRINT((dfile, "retry the same host: %s (penalty)\n",
613 			    current_host->host->h_name));
614 			attempts++;
615 			break;
616 		case SEND_RECORD_RETRY:
617 			DPRINT((dfile, "retry the same host: %s (no penalty)\n",
618 			    current_host->host->h_name));
619 			break;
620 		}
621 
622 
623 		if (send_record_rc == SEND_RECORD_NEXT) {
624 
625 			/* warn about unsuccessful auditd record delivery */
626 			rsn_msg = rsn_to_msg(err_rsn);
627 			(void) asprintf(&ext_error,
628 			    "retry %d connection %s:%d %s", attempts + 1,
629 			    current_host->host->h_name,
630 			    ntohs(current_host->port), rsn_msg);
631 			if (ext_error == NULL) {
632 				free(rsn_msg);
633 				*error = strdup(gettext("no memory"));
634 				rc = AUDITD_NO_MEMORY;
635 				break;
636 			}
637 			__audit_dowarn2("plugin", "audit_remote.so", "retry",
638 			    ext_error, attempts + 1);
639 			free(rsn_msg);
640 			free(ext_error);
641 
642 
643 			if (attempts < retries) {
644 				/* semi-exponential timeout back off */
645 				timeout = BOFF_TIMEOUT(attempts, timeout);
646 				DPRINT((dfile, "New timeout=%d\n", timeout));
647 			} else {
648 				/* get next host */
649 				current_host = current_host->next_host;
650 				if (current_host == NULL) {
651 					current_host = hosts;
652 				}
653 				timeout = RST_TIMEOUT(timeout_p_timeout);
654 				DPRINT((dfile, "New timeout=%d\n", timeout));
655 				attempts = 0;
656 			}
657 
658 
659 			/* one cycle finished */
660 			if (current_host == start_host && attempts == 0) {
661 				nosuccess_cnt++;
662 				(void) asprintf(&ext_error, "all hosts defined "
663 				    "as p_hosts were tried to deliver "
664 				    "the audit record to with no success "
665 				    "- sleeping for %d seconds",
666 				    NOSUCCESS_DELAY);
667 				if (ext_error == NULL) {
668 					*error = strdup(gettext("no memory"));
669 					rc = AUDITD_NO_MEMORY;
670 					break;
671 				}
672 				__audit_dowarn2("plugin", "audit_remote.so",
673 				    "retry", ext_error, nosuccess_cnt);
674 				free(ext_error);
675 				(void) sleep(NOSUCCESS_DELAY);
676 			}
677 
678 		} /* if (send_record_rc == SEND_RECORD_NEXT) */
679 
680 		err_rsn = RSN_UNDEFINED;
681 
682 	} /* while (rc != AUDITD_SUCCESS) */
683 
684 	(void) pthread_mutex_unlock(&plugin_mutex);
685 
686 #if DEBUG
687 	rc_msg = auditd_message(rc);
688 	DPRINT((dfile, "audit_remote: returning: %s\n", rc_msg));
689 	free(rc_msg);
690 #endif
691 
692 	return (rc);
693 }
694 
695 /*
696  * auditd_plugin_open() may be called multiple times; on initial open or
697  * `audit -s`, then kvlist != NULL; on `audit -n`, then kvlist == NULL.
698  * For more information see audit(1M).
699  *
700  * Note, that space on stack allocated for any error message returned along
701  * with AUDITD_RETRY is subsequently freed by auditd.
702  *
703  */
704 auditd_rc_t
705 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
706 {
707 	kva_t	*kv;
708 	char	*val_str;
709 	int	val;
710 	long	val_l;
711 	int	rc = 0;
712 
713 	*error = NULL;
714 	*ret_list = NULL;
715 	kv = (kva_t *)kvlist;
716 
717 #if DEBUG
718 	dfile = __auditd_debug_file_open();
719 #endif
720 
721 	/* initial open or audit -s */
722 	if (kvlist != NULL) {
723 		DPRINT((dfile, "Action: initial open or `audit -s`\n"));
724 		val_str = kva_match(kv, "p_timeout");
725 		if (val_str != NULL) {
726 			DPRINT((dfile, "val_str=%s\n", val_str));
727 			errno = 0;
728 			val = atoi(val_str);
729 			if (errno == 0 && val >= 1) {
730 				timeout_p_timeout = val;
731 				timeout = val;
732 			}
733 		}
734 
735 		val_str = kva_match(kv, "p_retries");
736 		if (val_str != NULL) {
737 			DPRINT((dfile, "val_str=%s\n", val_str));
738 			errno = 0;
739 			val = atoi(val_str);
740 			if (errno == 0 && val >= 0) {
741 				retries = val;
742 			}
743 		}
744 
745 		val_str = kva_match(kv, "qsize");
746 		if (val_str != NULL) {
747 			DPRINT((dfile, "qsize=%s\n", val_str));
748 			errno = 0;
749 			val_l = atol(val_str);
750 			if (errno == 0 && val_l > 0) {
751 				transq_count_max = val_l;
752 			}
753 
754 		} else {
755 			DPRINT((dfile, "qsize not in kvlist\n"));
756 			if ((rc = set_transq_count_max()) != AUDITD_SUCCESS) {
757 				*error = strdup(gettext("cannot get kernel "
758 				    "auditd queue high water mark\n"));
759 				return (rc);
760 			}
761 		}
762 		DPRINT((dfile, "timeout=%d, retries=%d, transq_count_max=%ld\n",
763 		    timeout, retries, transq_count_max));
764 
765 		val_str = kva_match(kv, "p_hosts");
766 		if (val_str == NULL) {
767 			*error = strdup(gettext("no hosts configured"));
768 			return (AUDITD_RETRY);
769 		}
770 		if ((rc = parsehosts(val_str, error)) != AUDITD_SUCCESS) {
771 			return (rc);
772 		}
773 
774 		/* create the notification pipe towards the receiving thread */
775 		if (!notify_pipe_ready) {
776 			if (create_notify_pipe(notify_pipe, error)) {
777 				notify_pipe_ready = B_TRUE;
778 			} else {
779 				return (AUDITD_RETRY);
780 			}
781 		}
782 
783 #if DEBUG
784 	} else { /* audit -n */
785 		DPRINT((dfile, "Action: `audit -n`\n"));
786 #endif
787 	}
788 
789 	return (AUDITD_SUCCESS);
790 }
791 
792 /*
793  * auditd_plugin_close() performs shutdown operations. The return values are
794  * used by auditd to output warnings via the audit_warn(1M) script and the
795  * string returned via "error_text", is passed to audit_warn.
796  *
797  * Note, that space on stack allocated for any error message returned along
798  * with AUDITD_RETRY is subsequently freed by auditd.
799  *
800  */
801 auditd_rc_t
802 auditd_plugin_close(char **error)
803 {
804 	reset_transport(DO_EXIT, DO_SYNC);
805 	if (pthread_join(recv_tid, NULL) != 0) {
806 		*error = strdup(gettext("unable to close receiving thread"));
807 		return (AUDITD_RETRY);
808 	}
809 
810 	freehostlist();
811 	*error = NULL;
812 	return (AUDITD_SUCCESS);
813 }
814