xref: /illumos-gate/usr/src/lib/auditd_plugins/remote/audit_remote.c (revision e8d80663e4f91871f843bb8ad9108dc0b76dfcf3)
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_TIMEOUT	5	/* default connection timeout (in secs) */
63 #define	NOSUCCESS_DELAY	20	/* unsuccessful delivery to all p_hosts */
64 
65 #define	FL_SET		B_TRUE	/* set_fdfl(): set the flag */
66 #define	FL_UNSET	B_FALSE	/* set_fdfl(): unset the flag */
67 
68 static int	nosuccess_cnt;	/* unsuccessful delivery counter */
69 
70 static int	retries;		/* connection retries */
71 int		timeout;		/* connection timeout */
72 static int	timeout_p_timeout;	/* p_timeout attr storage */
73 
74 /* semi-exponential timeout back off; x .. attempts, y .. timeout */
75 #define	BOFF_TIMEOUT(x, y)	(x < 3 ? y * 2 * x : y * 8)
76 
77 /* general plugin lock */
78 pthread_mutex_t	plugin_mutex = PTHREAD_MUTEX_INITIALIZER;
79 
80 static struct hostlist_s	*current_host;
81 static struct hostlist_s	*hosts;
82 static struct hostlist_s	*hosts_prev;
83 
84 extern struct transq_hdr_s	transq_hdr;
85 static long			transq_count_max;
86 extern pthread_mutex_t		transq_lock;
87 
88 extern pthread_t	recv_tid;
89 
90 extern boolean_t	notify_pipe_ready;
91 extern int		notify_pipe[2];
92 
93 #if DEBUG
94 FILE		*dfile;		/* debug file */
95 #endif
96 
97 /*
98  * set_transq_count_max() - sets the transq_count_max value based on kernel
99  * audit queue high water mark. This is backup solution for a case, when the
100  * the default qsize zero value is (intentionally) set in the audit_remote(5)
101  * plugin configuration.
102  */
103 static auditd_rc_t
104 set_transq_count_max()
105 {
106 	struct au_qctrl	qctrl;
107 
108 	if (auditon(A_GETQCTRL, (caddr_t)&qctrl, 0) != -1) {
109 		transq_count_max = qctrl.aq_hiwater;
110 		DPRINT((dfile, "Transmission queue max length set to %ld\n",
111 		    transq_count_max));
112 		return (AUDITD_SUCCESS);
113 	}
114 
115 	DPRINT((dfile, "Setting the transmission queue max length failed\n"));
116 	return (AUDITD_RETRY);
117 }
118 
119 /*
120  * get_port_default() - set the default port number; note, that "solaris-audit"
121  * used below in the code is the IANA assigned service name for the secure
122  * remote solaris audit logging.
123  */
124 static auditd_rc_t
125 get_port_default(int *port_default)
126 {
127 
128 	struct servent  serventry;
129 	char  		serventry_buf[1024];
130 
131 	if (getservbyname_r("solaris-audit", "tcp", &serventry,
132 	    (char *)&serventry_buf, sizeof (serventry_buf)) == NULL) {
133 		DPRINT((dfile, "unable to get default port number\n"));
134 #if DEBUG
135 		if (errno == ERANGE) {
136 			DPRINT((dfile, "low on buffer\n"));
137 		}
138 #endif
139 		return (AUDITD_INVALID);
140 	}
141 	*port_default = ntohs(serventry.s_port);
142 	DPRINT((dfile, "default port: %d\n", *port_default));
143 
144 	return (AUDITD_SUCCESS);
145 }
146 
147 /*
148  * trim_me() - trims the white space characters around the specified string.
149  * Inputs - pointer to the beginning of the string (str_ptr); returns - pointer
150  * to the trimmed string. Function returns NULL pointer in case of received
151  * empty string, NULL pointer or in case the pointed string consists of white
152  * space characters only.
153  */
154 static char *
155 trim_me(char *str_ptr) {
156 
157 	char	*str_end;
158 
159 	if (str_ptr == NULL || *str_ptr == '\0') {
160 		return (NULL);
161 	}
162 
163 	while (isspace(*str_ptr)) {
164 		str_ptr++;
165 	}
166 	if (*str_ptr == '\0') {
167 		return (NULL);
168 	}
169 
170 	str_end = str_ptr + strlen(str_ptr);
171 
172 	while (str_end > str_ptr && isspace(str_end[-1])) {
173 		str_end--;
174 	}
175 	*str_end = '\0';
176 
177 	return (str_ptr);
178 }
179 
180 /*
181  * Frees host list - should be called while keeping auditd_mutex.
182  */
183 static void
184 freehostlist(hostlist_t **hostlist_ptr)
185 {
186 	hostlist_t *h, *n;
187 
188 	h = *hostlist_ptr;
189 
190 	while (h != NULL)  {
191 		n = h->next_host;
192 		freehostent(h->host);
193 		free(h);
194 		h = n;
195 	}
196 	*hostlist_ptr = NULL;
197 }
198 
199 /*
200  * parsehosts() end parses the host string (hosts_str)
201  */
202 static auditd_rc_t
203 parsehosts(char *hosts_str, char **error)
204 {
205 	char 		*hostportmech, *hpm;
206 	char		*hostname;
207 	char		*port_str;
208 	char		*mech_str;
209 	int		port;
210 	int		port_default = -1;
211 	gss_OID		mech_oid;
212 	char 		*lasts_hpm;
213 	hostlist_t 	*lasthost = NULL;
214 	hostlist_t 	*hosts_new = NULL;
215 	hostlist_t	*newhost;
216 	struct hostent 	*hostentry;
217 	int		error_num;
218 	int		rc;
219 #if DEBUG
220 	char 		addr_buf[INET6_ADDRSTRLEN];
221 	int		num_of_hosts = 0;
222 #endif
223 
224 	DPRINT((dfile, "parsing %s\n", hosts_str));
225 	while ((hostportmech = strtok_r(hosts_str, ",", &lasts_hpm)) != NULL) {
226 
227 		hosts_str = NULL;
228 		hostname = NULL;
229 		port_str = NULL;
230 		port = port_default;
231 		mech_str = NULL;
232 		mech_oid = GSS_C_NO_OID;
233 
234 		DPRINT((dfile, "parsing host:port:mech %s\n", hostportmech));
235 
236 		if (strncmp(hostportmech, ":", 1 == 0)) { /* ":port:" case */
237 			*error = strdup(gettext("no hostname specified"));
238 			return (AUDITD_INVALID);
239 		}
240 
241 		/* parse single host:port:mech target */
242 		while ((hpm = strsep(&hostportmech, ":")) != NULL) {
243 
244 			if (hostname == NULL) {
245 				hostname = hpm;
246 				continue;
247 			}
248 			if (port_str == NULL) {
249 				port_str = hpm;
250 				continue;
251 			}
252 			if (mech_str == NULL) {
253 				mech_str = hpm;
254 				continue;
255 			}
256 
257 			/* too many colons in the hostportmech string */
258 			*error = strdup(gettext("invalid host:port:mech "
259 			    "specification"));
260 			return (AUDITD_INVALID);
261 		}
262 
263 		if (hostname == NULL || *hostname == '\0') {
264 			*error = strdup(gettext("invalid hostname "
265 			    "specification"));
266 			return (AUDITD_INVALID);
267 		}
268 
269 		/* trim hostname */
270 		hostname = trim_me(hostname);
271 		if (hostname == NULL || *hostname == '\0') {
272 			*error = strdup(gettext("empty hostname "
273 			    "specification"));
274 			return (AUDITD_INVALID);
275 		}
276 
277 		DPRINT((dfile, "resolving address for %s\n", hostname));
278 
279 		hostentry = getipnodebyname(hostname, AF_INET6, 0, &error_num);
280 		if (!hostentry) {
281 			hostentry = getipnodebyname(hostname, AF_INET, 0,
282 			    &error_num);
283 		}
284 		if (!hostentry) {
285 			if (error_num == TRY_AGAIN) {
286 				*error = strdup(gettext("host not found, "
287 				    "try later"));
288 				return (AUDITD_RETRY);
289 			} else {
290 				*error = strdup(gettext("host not found"));
291 				return (AUDITD_INVALID);
292 			}
293 		}
294 		DPRINT((dfile, "hostentry: h_name=%s, addr_len=%d, addr=%s\n",
295 		    hostentry->h_name, hostentry->h_length,
296 		    inet_ntop(hostentry->h_addrtype,
297 		    hostentry->h_addr_list[0], addr_buf,
298 		    INET6_ADDRSTRLEN)));
299 
300 		/* trim port */
301 		port_str = trim_me(port_str);
302 		if (port_str == NULL || *port_str == '\0') {
303 			if (port_default == -1 &&
304 			    (rc = get_port_default(&port_default))
305 			    != AUDITD_SUCCESS) {
306 				*error = strdup(gettext(
307 				    "unable to get default port number"));
308 				return (rc);
309 			}
310 			port = port_default;
311 			DPRINT((dfile, "port: %d (default)\n", port));
312 		} else {
313 			errno = 0;
314 			port = atoi(port_str);
315 			if (errno != 0 || port < 1 || port > USHRT_MAX) {
316 				*error = strdup(gettext("invalid port number"));
317 				return (AUDITD_INVALID);
318 			}
319 			DPRINT((dfile, "port: %d\n", port));
320 		}
321 
322 		/* trim mechanism */
323 		mech_str = trim_me(mech_str);
324 		if (mech_str != NULL && *mech_str != '\0') {
325 			if (rpc_gss_mech_to_oid(mech_str, &mech_oid) != TRUE) {
326 				*error = strdup(gettext("unknown mechanism"));
327 				return (AUDITD_INVALID);
328 			}
329 			DPRINT((dfile, "mechanism: %s\n", mech_str));
330 #if DEBUG
331 		} else {
332 			DPRINT((dfile, "mechanism: null (default)\n"));
333 #endif
334 		}
335 
336 		/* add this host to host list */
337 		newhost = malloc(sizeof (hostlist_t));
338 		if (newhost == NULL) {
339 			*error = strdup(gettext("no memory"));
340 			return (AUDITD_NO_MEMORY);
341 		}
342 		newhost->host = hostentry;
343 		newhost->port = htons(port);
344 		newhost->mech = mech_oid;
345 		newhost->next_host = NULL;
346 		if (lasthost != NULL) {
347 			lasthost->next_host = newhost;
348 			lasthost = lasthost->next_host;
349 		} else {
350 			lasthost = newhost;
351 			hosts_new = newhost;
352 		}
353 #if DEBUG
354 		num_of_hosts++;
355 #endif
356 	}
357 
358 	(void) pthread_mutex_lock(&plugin_mutex);
359 	if (hosts_prev == NULL) {
360 		hosts_prev = hosts;
361 	}
362 	hosts = hosts_new;
363 	current_host = hosts;
364 	(void) pthread_mutex_unlock(&plugin_mutex);
365 
366 	DPRINT((dfile, "Configured %d hosts.\n", num_of_hosts));
367 
368 	return (AUDITD_SUCCESS);
369 }
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 			if (hosts_prev != NULL) {
611 				freehostlist(&hosts_prev);
612 				DPRINT((dfile, "stale host list freed\n"));
613 			}
614 			break;
615 		case SEND_RECORD_NEXT:
616 			DPRINT((dfile, "retry the same host: %s (penalty) "
617 			    "rsn:%d\n", current_host->host->h_name, err_rsn));
618 			attempts++;
619 			break;
620 		case SEND_RECORD_RETRY:
621 			DPRINT((dfile, "retry the same host: %s (no penalty) "
622 			    "rsn:%d\n", current_host->host->h_name, err_rsn));
623 			break;
624 		}
625 
626 		if (send_record_rc == SEND_RECORD_NEXT) {
627 
628 			/* warn about unsuccessful auditd record delivery */
629 			rsn_msg = rsn_to_msg(err_rsn);
630 			(void) asprintf(&ext_error,
631 			    "retry %d connection %s:%d %s", attempts + 1,
632 			    current_host->host->h_name,
633 			    ntohs(current_host->port), rsn_msg);
634 			if (ext_error == NULL) {
635 				free(rsn_msg);
636 				*error = strdup(gettext("no memory"));
637 				rc = AUDITD_NO_MEMORY;
638 				break;
639 			}
640 			__audit_dowarn2("plugin", "audit_remote.so", "retry",
641 			    ext_error, attempts + 1);
642 			free(rsn_msg);
643 			free(ext_error);
644 
645 			if (attempts < retries) {
646 				/* semi-exponential timeout back off */
647 				timeout = BOFF_TIMEOUT(attempts, timeout);
648 				DPRINT((dfile, "New timeout=%d\n", timeout));
649 			} else {
650 				/* get next host */
651 				current_host = current_host->next_host;
652 				if (current_host == NULL) {
653 					current_host = hosts;
654 				}
655 				timeout = timeout_p_timeout;
656 				DPRINT((dfile, "New timeout=%d\n", timeout));
657 				attempts = 0;
658 			}
659 
660 			/* one cycle finished */
661 			if (current_host == start_host && attempts == 0) {
662 				nosuccess_cnt++;
663 				(void) asprintf(&ext_error, "all hosts defined "
664 				    "as p_hosts were tried to deliver "
665 				    "the audit record to with no success "
666 				    "- sleeping for %d seconds",
667 				    NOSUCCESS_DELAY);
668 				if (ext_error == NULL) {
669 					*error = strdup(gettext("no memory"));
670 					rc = AUDITD_NO_MEMORY;
671 					break;
672 				}
673 				__audit_dowarn2("plugin", "audit_remote.so",
674 				    "retry", ext_error, nosuccess_cnt);
675 				free(ext_error);
676 				(void) sleep(NOSUCCESS_DELAY);
677 			}
678 
679 		} /* if (send_record_rc == SEND_RECORD_NEXT) */
680 
681 		err_rsn = RSN_UNDEFINED;
682 
683 	} /* while (rc != AUDITD_SUCCESS) */
684 
685 	(void) pthread_mutex_unlock(&plugin_mutex);
686 
687 #if DEBUG
688 	rc_msg = auditd_message(rc);
689 	DPRINT((dfile, "audit_remote: returning: %s\n", rc_msg));
690 	free(rc_msg);
691 #endif
692 
693 	return (rc);
694 }
695 
696 /*
697  * auditd_plugin_open() may be called multiple times; on initial open or
698  * `audit -s`, then kvlist != NULL; on `audit -n`, then kvlist == NULL.
699  * For more information see audit(1M).
700  *
701  * Note, that space on stack allocated for any error message returned along
702  * with AUDITD_RETRY is subsequently freed by auditd.
703  *
704  */
705 auditd_rc_t
706 auditd_plugin_open(const kva_t *kvlist, char **ret_list, char **error)
707 {
708 	kva_t	*kv;
709 	char	*val_str;
710 	int	val;
711 	long	val_l;
712 	int	rc = 0;
713 
714 	*error = NULL;
715 	*ret_list = NULL;
716 	kv = (kva_t *)kvlist;
717 
718 #if DEBUG
719 	dfile = __auditd_debug_file_open();
720 #endif
721 
722 	/* initial open or audit -s */
723 	if (kvlist != NULL) {
724 		DPRINT((dfile, "Action: initial open or `audit -s`\n"));
725 		val_str = kva_match(kv, "p_timeout");
726 		if (val_str == NULL) {
727 			*error = strdup(
728 			    gettext("p_timeout attribute not found"));
729 			return (AUDITD_RETRY);
730 		}
731 		DPRINT((dfile, "val_str=%s\n", val_str));
732 		errno = 0;
733 		val = atoi(val_str);
734 		if (errno == 0 && val >= 1) {
735 			timeout_p_timeout = val;
736 			timeout = val;
737 		} else {
738 			timeout_p_timeout = DEFAULT_TIMEOUT;
739 			timeout = timeout_p_timeout;
740 			DPRINT((dfile, "p_timeout set to default value: %d\n",
741 			    timeout));
742 		}
743 
744 		val_str = kva_match(kv, "p_retries");
745 		if (val_str == NULL) {
746 			*error = strdup(
747 			    gettext("p_retries attribute not found"));
748 			return (AUDITD_RETRY);
749 		}
750 		DPRINT((dfile, "val_str=%s\n", val_str));
751 		errno = 0;
752 		val = atoi(val_str);
753 		if (errno == 0 && val >= 0) {
754 			retries = val;
755 		}
756 
757 		val_str = kva_match(kv, "qsize");
758 		if (val_str == NULL) {
759 			*error = strdup(gettext("qsize attribute not found"));
760 			return (AUDITD_RETRY);
761 		}
762 		DPRINT((dfile, "qsize=%s\n", val_str));
763 		errno = 0;
764 		val_l = atol(val_str);
765 		if (errno == 0 && val_l >= 0) {
766 			transq_count_max = val_l;
767 		}
768 		if (transq_count_max == 0 &&
769 		    (rc = set_transq_count_max()) != AUDITD_SUCCESS) {
770 			*error = strdup(gettext("cannot get kernel "
771 			    "auditd queue high water mark\n"));
772 			return (rc);
773 		}
774 		DPRINT((dfile, "timeout=%d, retries=%d, transq_count_max=%ld\n",
775 		    timeout, retries, transq_count_max));
776 
777 		val_str = kva_match(kv, "p_hosts");
778 		if (val_str == NULL) {
779 			*error = strdup(gettext("no hosts configured"));
780 			return (AUDITD_RETRY);
781 		}
782 		if ((rc = parsehosts(val_str, error)) != AUDITD_SUCCESS) {
783 			return (rc);
784 		}
785 
786 		/* create the notification pipe towards the receiving thread */
787 		if (!notify_pipe_ready) {
788 			if (create_notify_pipe(notify_pipe, error)) {
789 				notify_pipe_ready = B_TRUE;
790 			} else {
791 				return (AUDITD_RETRY);
792 			}
793 		}
794 
795 #if DEBUG
796 	} else { /* audit -n */
797 		DPRINT((dfile, "Action: `audit -n`\n"));
798 #endif
799 	}
800 
801 	return (AUDITD_SUCCESS);
802 }
803 
804 /*
805  * auditd_plugin_close() performs shutdown operations. The return values are
806  * used by auditd to output warnings via the audit_warn(1M) script and the
807  * string returned via "error_text", is passed to audit_warn.
808  *
809  * Note, that space on stack allocated for any error message returned along
810  * with AUDITD_RETRY is subsequently freed by auditd.
811  *
812  */
813 auditd_rc_t
814 auditd_plugin_close(char **error)
815 {
816 	reset_transport(DO_EXIT, DO_SYNC);
817 	if (pthread_join(recv_tid, NULL) != 0) {
818 		*error = strdup(gettext("unable to close receiving thread"));
819 		return (AUDITD_RETRY);
820 	}
821 
822 	(void) pthread_mutex_lock(&plugin_mutex);
823 	freehostlist(&hosts);
824 	freehostlist(&hosts_prev);
825 	(void) pthread_mutex_unlock(&plugin_mutex);
826 	current_host = NULL;
827 	*error = NULL;
828 	return (AUDITD_SUCCESS);
829 }
830