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