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 && the_authctxt->valid) {
267		uid = the_authctxt->pw->pw_uid;
268		gid = the_authctxt->pw->pw_gid;
269	}
270
271	rc = (typ == 0) ? 0 : -1;
272	sel = selected(the_authctxt->user, uid, event_no, rc);
273	debug3("BSM audit: typ %d rc %d \"%s\"", typ, rc, string);
274	if (!sel)
275		return;	/* audit event does not match mask, do not write */
276
277	debug3("BSM audit: writing audit new record");
278	ad = au_open();
279
280	(void) au_write(ad, AUToSubjectFunc(uid, uid, gid, uid, gid,
281	    pid, pid, &tid));
282	(void) au_write(ad, au_to_text(string));
283	(void) au_write(ad, AUToReturnFunc(typ, rc));
284
285#ifdef BROKEN_BSM_API
286	/* The last argument is the event modifier flags. For
287	   some seemingly undocumented reason it was added in
288	   Solaris 11. */
289	rc = au_close(ad, AU_TO_WRITE, event_no, 0);
290#else
291	rc = au_close(ad, AU_TO_WRITE, event_no);
292#endif
293
294	if (rc < 0)
295		error("BSM audit: %s failed to write \"%s\" record: %s",
296		    __func__, string, strerror(errno));
297}
298
299static void
300bsm_audit_session_setup(void)
301{
302	int rc;
303	struct AuditInfoStruct info;
304	au_mask_t mask;
305
306	if (the_authctxt == NULL) {
307		error("BSM audit: session setup internal error (NULL ctxt)");
308		return;
309	}
310
311	if (the_authctxt->valid)
312		info.ai_auid = the_authctxt->pw->pw_uid;
313	else
314		info.ai_auid = -1;
315	info.ai_asid = getpid();
316	mask.am_success = 0;
317	mask.am_failure = 0;
318
319	(void) au_user_mask(the_authctxt->user, &mask);
320
321	info.ai_mask.am_success  = mask.am_success;
322	info.ai_mask.am_failure  = mask.am_failure;
323
324	info.ai_termid = ssh_bsm_tid;
325
326	rc = SetAuditFunc(&info, sizeof(info));
327	if (rc < 0)
328		error("BSM audit: %s: %s failed: %s", __func__,
329		    SetAuditFuncText, strerror(errno));
330}
331
332static void
333bsm_audit_bad_login(const char *what)
334{
335	char textbuf[BSM_TEXTBUFSZ];
336
337	if (the_authctxt->valid) {
338		(void) snprintf(textbuf, sizeof (textbuf),
339			gettext("invalid %s for user %s"),
340			    what, the_authctxt->user);
341		bsm_audit_record(4, textbuf, AUE_openssh);
342	} else {
343		(void) snprintf(textbuf, sizeof (textbuf),
344			gettext("invalid user name \"%s\""),
345			    the_authctxt->user);
346		bsm_audit_record(3, textbuf, AUE_openssh);
347	}
348}
349
350/* Below is the sshd audit API code */
351
352void
353audit_connection_from(const char *host, int port)
354{
355	AuditInfoTermID *tid = &ssh_bsm_tid;
356	char buf[1024];
357
358	if (cannot_audit(0))
359		return;
360	debug3("BSM audit: connection from %.100s port %d", host, port);
361
362	/* populate our terminal id structure */
363#if defined(HAVE_GETAUDIT_ADDR)
364	tid->at_port = (dev_t)port;
365	aug_get_machine((char *)host, &(tid->at_addr[0]), &(tid->at_type));
366	snprintf(buf, sizeof(buf), "%08x %08x %08x %08x", tid->at_addr[0],
367	    tid->at_addr[1], tid->at_addr[2], tid->at_addr[3]);
368	debug3("BSM audit: iptype %d machine ID %s", (int)tid->at_type, buf);
369#else
370	/* this is used on IPv4-only machines */
371	tid->port = (dev_t)port;
372	tid->machine = inet_addr(host);
373	snprintf(buf, sizeof(buf), "%08x", tid->machine);
374	debug3("BSM audit: machine ID %s", buf);
375#endif
376}
377
378void
379audit_run_command(const char *command)
380{
381	/* not implemented */
382}
383
384void
385audit_session_open(struct logininfo *li)
386{
387	/* not implemented */
388}
389
390void
391audit_session_close(struct logininfo *li)
392{
393	/* not implemented */
394}
395
396void
397audit_event(ssh_audit_event_t event)
398{
399	char    textbuf[BSM_TEXTBUFSZ];
400	static int logged_in = 0;
401	const char *user = the_authctxt ? the_authctxt->user : "(unknown user)";
402
403	if (cannot_audit(0))
404		return;
405
406	switch(event) {
407	case SSH_AUTH_SUCCESS:
408		logged_in = 1;
409		bsm_audit_session_setup();
410		snprintf(textbuf, sizeof(textbuf),
411		    gettext("successful login %s"), user);
412		bsm_audit_record(0, textbuf, AUE_openssh);
413		break;
414
415	case SSH_CONNECTION_CLOSE:
416		/*
417		 * We can also get a close event if the user attempted auth
418		 * but never succeeded.
419		 */
420		if (logged_in) {
421			snprintf(textbuf, sizeof(textbuf),
422			    gettext("sshd logout %s"), the_authctxt->user);
423			bsm_audit_record(0, textbuf, AUE_logout);
424		} else {
425			debug("%s: connection closed without authentication",
426			    __func__);
427		}
428		break;
429
430	case SSH_NOLOGIN:
431		bsm_audit_record(1,
432		    gettext("logins disabled by /etc/nologin"), AUE_openssh);
433		break;
434
435	case SSH_LOGIN_EXCEED_MAXTRIES:
436		snprintf(textbuf, sizeof(textbuf),
437		    gettext("too many tries for user %s"), the_authctxt->user);
438		bsm_audit_record(1, textbuf, AUE_openssh);
439		break;
440
441	case SSH_LOGIN_ROOT_DENIED:
442		bsm_audit_record(2, gettext("not_console"), AUE_openssh);
443		break;
444
445	case SSH_AUTH_FAIL_PASSWD:
446		bsm_audit_bad_login("password");
447		break;
448
449	case SSH_AUTH_FAIL_KBDINT:
450		bsm_audit_bad_login("interactive password entry");
451		break;
452
453	default:
454		debug("%s: unhandled event %d", __func__, event);
455	}
456}
457#endif /* BSM */
458