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