audit-bsm.c revision 323134
1/*
2 * TODO
3 *
4 * - deal with overlap between this and sys_auth_allowed_user
5 *   sys_auth_record_login and record_failed_login.
6 */
7
8/*
9 * Copyright 1988-2002 Sun Microsystems, Inc.  All rights reserved.
10 * Use is subject to license terms.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 *    notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 *    notice, this list of conditions and the following disclaimer in the
19 *    documentation and/or other materials provided with the distribution.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 *
32 */
33/* #pragma ident	"@(#)bsmaudit.c	1.1	01/09/17 SMI" */
34
35#include "includes.h"
36#if defined(USE_BSM_AUDIT)
37
38#include <sys/types.h>
39
40#include <errno.h>
41#include <netdb.h>
42#include <stdarg.h>
43#include <string.h>
44#include <unistd.h>
45
46#ifdef BROKEN_BSM_API
47#include <libscf.h>
48#endif
49
50#include "ssh.h"
51#include "log.h"
52#include "key.h"
53#include "hostfile.h"
54#include "auth.h"
55#include "xmalloc.h"
56
57#ifndef AUE_openssh
58# define AUE_openssh     32800
59#endif
60#include <bsm/audit.h>
61#include <bsm/libbsm.h>
62#include <bsm/audit_uevents.h>
63#include <bsm/audit_record.h>
64#include <locale.h>
65
66#if defined(HAVE_GETAUDIT_ADDR)
67#define	AuditInfoStruct		auditinfo_addr
68#define AuditInfoTermID		au_tid_addr_t
69#define SetAuditFunc(a,b)	setaudit_addr((a),(b))
70#define SetAuditFuncText	"setaudit_addr"
71#define AUToSubjectFunc		au_to_subject_ex
72#define AUToReturnFunc(a,b)	au_to_return32((a), (int32_t)(b))
73#else
74#define	AuditInfoStruct		auditinfo
75#define AuditInfoTermID		au_tid_t
76#define SetAuditFunc(a,b)	setaudit(a)
77#define SetAuditFuncText	"setaudit"
78#define AUToSubjectFunc		au_to_subject
79#define AUToReturnFunc(a,b)	au_to_return((a), (u_int)(b))
80#endif
81
82#ifndef cannot_audit
83extern int	cannot_audit(int);
84#endif
85extern void	aug_init(void);
86extern void	aug_save_auid(au_id_t);
87extern void	aug_save_uid(uid_t);
88extern void	aug_save_euid(uid_t);
89extern void	aug_save_gid(gid_t);
90extern void	aug_save_egid(gid_t);
91extern void	aug_save_pid(pid_t);
92extern void	aug_save_asid(au_asid_t);
93extern void	aug_save_tid(dev_t, unsigned int);
94extern void	aug_save_tid_ex(dev_t, u_int32_t *, u_int32_t);
95extern int	aug_save_me(void);
96extern int	aug_save_namask(void);
97extern void	aug_save_event(au_event_t);
98extern void	aug_save_sorf(int);
99extern void	aug_save_text(char *);
100extern void	aug_save_text1(char *);
101extern void	aug_save_text2(char *);
102extern void	aug_save_na(int);
103extern void	aug_save_user(char *);
104extern void	aug_save_path(char *);
105extern int	aug_save_policy(void);
106extern void	aug_save_afunc(int (*)(int));
107extern int	aug_audit(void);
108extern int	aug_na_selected(void);
109extern int	aug_selected(void);
110extern int	aug_daemon_session(void);
111
112#ifndef HAVE_GETTEXT
113# define gettext(a)	(a)
114#endif
115
116extern Authctxt *the_authctxt;
117static AuditInfoTermID ssh_bsm_tid;
118
119#ifdef BROKEN_BSM_API
120/* For some reason this constant is no longer defined
121   in Solaris 11. */
122#define BSM_TEXTBUFSZ 256
123#endif
124
125/* Below is the low-level BSM interface code */
126
127/*
128 * aug_get_machine is only required on IPv6 capable machines, we use a
129 * different mechanism in audit_connection_from() for IPv4-only machines.
130 * getaudit_addr() is only present on IPv6 capable machines.
131 */
132#if defined(HAVE_AUG_GET_MACHINE) || !defined(HAVE_GETAUDIT_ADDR)
133extern int 	aug_get_machine(char *, u_int32_t *, u_int32_t *);
134#else
135static int
136aug_get_machine(char *host, u_int32_t *addr, u_int32_t *type)
137{
138	struct addrinfo *ai;
139	struct sockaddr_in *in4;
140	struct sockaddr_in6 *in6;
141	int ret = 0, r;
142
143	if ((r = getaddrinfo(host, NULL, NULL, &ai)) != 0) {
144		error("BSM audit: getaddrinfo failed for %.100s: %.100s", host,
145		    r == EAI_SYSTEM ? strerror(errno) : gai_strerror(r));
146		return -1;
147	}
148
149	switch (ai->ai_family) {
150	case AF_INET:
151		in4 = (struct sockaddr_in *)ai->ai_addr;
152		*type = AU_IPv4;
153		memcpy(addr, &in4->sin_addr, sizeof(struct in_addr));
154		break;
155#ifdef AU_IPv6
156	case AF_INET6:
157		in6 = (struct sockaddr_in6 *)ai->ai_addr;
158		*type = AU_IPv6;
159		memcpy(addr, &in6->sin6_addr, sizeof(struct in6_addr));
160		break;
161#endif
162	default:
163		error("BSM audit: unknown address family for %.100s: %d",
164		    host, ai->ai_family);
165		ret = -1;
166	}
167	freeaddrinfo(ai);
168	return ret;
169}
170#endif
171
172#ifdef BROKEN_BSM_API
173/*
174  In Solaris 11 the audit daemon has been moved to SMF. In the process
175  they simply dropped getacna() from the API, since it read from a now
176  non-existent config file. This function re-implements getacna() to
177  read from the SMF repository instead.
178 */
179int
180getacna(char *auditstring, int len)
181{
182	scf_handle_t *handle = NULL;
183	scf_property_t *property = NULL;
184	scf_value_t *value = NULL;
185	int ret = 0;
186
187	handle = scf_handle_create(SCF_VERSION);
188	if (handle == NULL)
189	        return -2; /* The man page for getacna on Solaris 10 states
190			      we should return -2 in case of error and set
191			      errno to indicate the error. We don't bother
192			      with errno here, though, since the only use
193			      of this function below doesn't check for errors
194			      anyway.
195			   */
196
197	ret = scf_handle_bind(handle);
198	if (ret == -1)
199	        return -2;
200
201	property = scf_property_create(handle);
202	if (property == NULL)
203	        return -2;
204
205	ret = scf_handle_decode_fmri(handle,
206	     "svc:/system/auditd:default/:properties/preselection/naflags",
207				     NULL, NULL, NULL, NULL, property, 0);
208	if (ret == -1)
209	        return -2;
210
211	value = scf_value_create(handle);
212	if (value == NULL)
213	        return -2;
214
215	ret = scf_property_get_value(property, value);
216	if (ret == -1)
217	        return -2;
218
219	ret = scf_value_get_astring(value, auditstring, len);
220	if (ret == -1)
221	        return -2;
222
223	scf_value_destroy(value);
224	scf_property_destroy(property);
225	scf_handle_destroy(handle);
226
227	return 0;
228}
229#endif
230
231/*
232 * Check if the specified event is selected (enabled) for auditing.
233 * Returns 1 if the event is selected, 0 if not and -1 on failure.
234 */
235static int
236selected(char *username, uid_t uid, au_event_t event, int sf)
237{
238	int rc, sorf;
239	char naflags[512];
240	struct au_mask mask;
241
242	mask.am_success = mask.am_failure = 0;
243	if (uid < 0) {
244		/* get flags for non-attributable (to a real user) events */
245		rc = getacna(naflags, sizeof(naflags));
246		if (rc == 0)
247			(void) getauditflagsbin(naflags, &mask);
248	} else
249		rc = au_user_mask(username, &mask);
250
251	sorf = (sf == 0) ? AU_PRS_SUCCESS : AU_PRS_FAILURE;
252	return(au_preselect(event, &mask, sorf, AU_PRS_REREAD));
253}
254
255static void
256bsm_audit_record(int typ, char *string, au_event_t event_no)
257{
258	int		ad, rc, sel;
259	uid_t		uid = -1;
260	gid_t		gid = -1;
261	pid_t		pid = getpid();
262	AuditInfoTermID	tid = ssh_bsm_tid;
263
264	if (the_authctxt != NULL && the_authctxt->valid) {
265		uid = the_authctxt->pw->pw_uid;
266		gid = the_authctxt->pw->pw_gid;
267	}
268
269	rc = (typ == 0) ? 0 : -1;
270	sel = selected(the_authctxt->user, uid, event_no, rc);
271	debug3("BSM audit: typ %d rc %d \"%s\"", typ, rc, string);
272	if (!sel)
273		return;	/* audit event does not match mask, do not write */
274
275	debug3("BSM audit: writing audit new record");
276	ad = au_open();
277
278	(void) au_write(ad, AUToSubjectFunc(uid, uid, gid, uid, gid,
279	    pid, pid, &tid));
280	(void) au_write(ad, au_to_text(string));
281	(void) au_write(ad, AUToReturnFunc(typ, rc));
282
283#ifdef BROKEN_BSM_API
284	/* The last argument is the event modifier flags. For
285	   some seemingly undocumented reason it was added in
286	   Solaris 11. */
287	rc = au_close(ad, AU_TO_WRITE, event_no, 0);
288#else
289	rc = au_close(ad, AU_TO_WRITE, event_no);
290#endif
291
292	if (rc < 0)
293		error("BSM audit: %s failed to write \"%s\" record: %s",
294		    __func__, string, strerror(errno));
295}
296
297static void
298bsm_audit_session_setup(void)
299{
300	int rc;
301	struct AuditInfoStruct info;
302	au_mask_t mask;
303
304	if (the_authctxt == NULL) {
305		error("BSM audit: session setup internal error (NULL ctxt)");
306		return;
307	}
308
309	if (the_authctxt->valid)
310		info.ai_auid = the_authctxt->pw->pw_uid;
311	else
312		info.ai_auid = -1;
313	info.ai_asid = getpid();
314	mask.am_success = 0;
315	mask.am_failure = 0;
316
317	(void) au_user_mask(the_authctxt->user, &mask);
318
319	info.ai_mask.am_success  = mask.am_success;
320	info.ai_mask.am_failure  = mask.am_failure;
321
322	info.ai_termid = ssh_bsm_tid;
323
324	rc = SetAuditFunc(&info, sizeof(info));
325	if (rc < 0)
326		error("BSM audit: %s: %s failed: %s", __func__,
327		    SetAuditFuncText, strerror(errno));
328}
329
330static void
331bsm_audit_bad_login(const char *what)
332{
333	char textbuf[BSM_TEXTBUFSZ];
334
335	if (the_authctxt->valid) {
336		(void) snprintf(textbuf, sizeof (textbuf),
337			gettext("invalid %s for user %s"),
338			    what, the_authctxt->user);
339		bsm_audit_record(4, textbuf, AUE_openssh);
340	} else {
341		(void) snprintf(textbuf, sizeof (textbuf),
342			gettext("invalid user name \"%s\""),
343			    the_authctxt->user);
344		bsm_audit_record(3, textbuf, AUE_openssh);
345	}
346}
347
348/* Below is the sshd audit API code */
349
350void
351audit_connection_from(const char *host, int port)
352{
353	AuditInfoTermID *tid = &ssh_bsm_tid;
354	char buf[1024];
355
356	if (cannot_audit(0))
357		return;
358	debug3("BSM audit: connection from %.100s port %d", host, port);
359
360	/* populate our terminal id structure */
361#if defined(HAVE_GETAUDIT_ADDR)
362	tid->at_port = (dev_t)port;
363	aug_get_machine((char *)host, &(tid->at_addr[0]), &(tid->at_type));
364	snprintf(buf, sizeof(buf), "%08x %08x %08x %08x", tid->at_addr[0],
365	    tid->at_addr[1], tid->at_addr[2], tid->at_addr[3]);
366	debug3("BSM audit: iptype %d machine ID %s", (int)tid->at_type, buf);
367#else
368	/* this is used on IPv4-only machines */
369	tid->port = (dev_t)port;
370	tid->machine = inet_addr(host);
371	snprintf(buf, sizeof(buf), "%08x", tid->machine);
372	debug3("BSM audit: machine ID %s", buf);
373#endif
374}
375
376void
377audit_run_command(const char *command)
378{
379	/* not implemented */
380}
381
382void
383audit_session_open(struct logininfo *li)
384{
385	/* not implemented */
386}
387
388void
389audit_session_close(struct logininfo *li)
390{
391	/* not implemented */
392}
393
394void
395audit_event(ssh_audit_event_t event)
396{
397	char    textbuf[BSM_TEXTBUFSZ];
398	static int logged_in = 0;
399	const char *user = the_authctxt ? the_authctxt->user : "(unknown user)";
400
401	if (cannot_audit(0))
402		return;
403
404	switch(event) {
405	case SSH_AUTH_SUCCESS:
406		logged_in = 1;
407		bsm_audit_session_setup();
408		snprintf(textbuf, sizeof(textbuf),
409		    gettext("successful login %s"), user);
410		bsm_audit_record(0, textbuf, AUE_openssh);
411		break;
412
413	case SSH_CONNECTION_CLOSE:
414		/*
415		 * We can also get a close event if the user attempted auth
416		 * but never succeeded.
417		 */
418		if (logged_in) {
419			snprintf(textbuf, sizeof(textbuf),
420			    gettext("sshd logout %s"), the_authctxt->user);
421			bsm_audit_record(0, textbuf, AUE_logout);
422		} else {
423			debug("%s: connection closed without authentication",
424			    __func__);
425		}
426		break;
427
428	case SSH_NOLOGIN:
429		bsm_audit_record(1,
430		    gettext("logins disabled by /etc/nologin"), AUE_openssh);
431		break;
432
433	case SSH_LOGIN_EXCEED_MAXTRIES:
434		snprintf(textbuf, sizeof(textbuf),
435		    gettext("too many tries for user %s"), the_authctxt->user);
436		bsm_audit_record(1, textbuf, AUE_openssh);
437		break;
438
439	case SSH_LOGIN_ROOT_DENIED:
440		bsm_audit_record(2, gettext("not_console"), AUE_openssh);
441		break;
442
443	case SSH_AUTH_FAIL_PASSWD:
444		bsm_audit_bad_login("password");
445		break;
446
447	case SSH_AUTH_FAIL_KBDINT:
448		bsm_audit_bad_login("interactive password entry");
449		break;
450
451	default:
452		debug("%s: unhandled event %d", __func__, event);
453	}
454}
455#endif /* BSM */
456