| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright 2017 Nexenta Systems, Inc. All rights reserved. |
| */ |
| |
| #include <sys/types.h> |
| #include <errno.h> |
| #include <synch.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <syslog.h> |
| #include <fcntl.h> |
| #include <bsm/adt.h> |
| #include <bsm/adt_event.h> |
| #include <bsm/audit_uevents.h> |
| #include <pwd.h> |
| #include <nss_dbdefs.h> |
| #include <sys/idmap.h> |
| #include "smbd.h" |
| |
| |
| /* |
| * An audit session is established at user logon and terminated at user |
| * logoff. |
| * |
| * SMB audit handles are allocated when users logon (SmbSessionSetupX) |
| * and deallocted when a user logs off (SmbLogoffX). Each time an SMB |
| * audit handle is allocated it is added to a global list. |
| */ |
| typedef struct smb_audit { |
| struct smb_audit *sa_next; |
| adt_session_data_t *sa_handle; |
| uid_t sa_uid; |
| gid_t sa_gid; |
| uint32_t sa_audit_sid; |
| uint32_t sa_refcnt; |
| char *sa_domain; |
| char *sa_username; |
| } smb_audit_t; |
| |
| static smb_audit_t *smbd_audit_list; |
| static mutex_t smbd_audit_lock; |
| |
| /* |
| * Unique identifier for audit sessions in the audit list. |
| * Used to lookup an audit session on logoff. |
| */ |
| static uint32_t smbd_audit_sid; |
| |
| static void smbd_audit_link(smb_audit_t *); |
| static smb_audit_t *smbd_audit_unlink(uint32_t); |
| |
| |
| /* |
| * Invoked at user logon due to SmbSessionSetupX. Authenticate the |
| * user, start an audit session and audit the event. |
| * |
| * On error, returns NULL, and status in user_info->lg_status |
| */ |
| smb_token_t * |
| smbd_user_auth_logon(smb_logon_t *user_info) |
| { |
| smb_token_t *token = NULL; |
| smb_audit_t *entry; |
| adt_session_data_t *ah = NULL; |
| adt_event_data_t *event; |
| smb_logon_t tmp_user; |
| au_tid_addr_t termid; |
| char sidbuf[SMB_SID_STRSZ]; |
| char *username; |
| char *domain; |
| uid_t uid; |
| gid_t gid; |
| char *sid; |
| int status; |
| int retval; |
| char *p; |
| char *buf = NULL; |
| |
| if (user_info->lg_username == NULL || |
| user_info->lg_domain == NULL || |
| user_info->lg_workstation == NULL) { |
| user_info->lg_status = NT_STATUS_INVALID_PARAMETER; |
| return (NULL); |
| } |
| |
| /* |
| * Avoid modifying the caller-provided struct because it |
| * may or may not point to allocated strings etc. |
| * Copy to tmp_user, auth, then copy the (out) lg_status |
| * member back to the caller-provided struct. |
| */ |
| tmp_user = *user_info; |
| if (tmp_user.lg_username[0] == '\0') { |
| tmp_user.lg_flags |= SMB_ATF_ANON; |
| tmp_user.lg_e_username = "anonymous"; |
| } else { |
| tmp_user.lg_e_username = tmp_user.lg_username; |
| } |
| |
| /* Handle user@domain format. */ |
| if (tmp_user.lg_domain[0] == '\0' && |
| (p = strchr(tmp_user.lg_e_username, '@')) != NULL) { |
| buf = strdup(tmp_user.lg_e_username); |
| if (buf == NULL) |
| goto errout; |
| p = buf + (p - tmp_user.lg_e_username); |
| *p = '\0'; |
| tmp_user.lg_e_domain = p + 1; |
| tmp_user.lg_e_username = buf; |
| } else { |
| tmp_user.lg_e_domain = tmp_user.lg_domain; |
| } |
| |
| token = smb_logon(&tmp_user); |
| user_info->lg_status = tmp_user.lg_status; |
| |
| if (token == NULL) { |
| if (user_info->lg_status == 0) /* should not happen */ |
| user_info->lg_status = NT_STATUS_INTERNAL_ERROR; |
| uid = ADT_NO_ATTRIB; |
| gid = ADT_NO_ATTRIB; |
| sid = NT_NULL_SIDSTR; |
| username = tmp_user.lg_e_username; |
| domain = tmp_user.lg_e_domain; |
| status = ADT_FAILURE; |
| retval = ADT_FAIL_VALUE_AUTH; |
| } else { |
| uid = token->tkn_user.i_id; |
| gid = token->tkn_primary_grp.i_id; |
| smb_sid_tostr(token->tkn_user.i_sid, sidbuf); |
| sid = sidbuf; |
| username = token->tkn_account_name; |
| domain = token->tkn_domain_name; |
| status = ADT_SUCCESS; |
| retval = ADT_SUCCESS; |
| } |
| |
| if (adt_start_session(&ah, NULL, 0)) { |
| syslog(LOG_AUTH | LOG_ALERT, "adt_start_session: %m"); |
| user_info->lg_status = NT_STATUS_AUDIT_FAILED; |
| goto errout; |
| } |
| |
| if ((event = adt_alloc_event(ah, ADT_smbd_session)) == NULL) { |
| syslog(LOG_AUTH | LOG_ALERT, |
| "adt_alloc_event(ADT_smbd_session): %m"); |
| user_info->lg_status = NT_STATUS_AUDIT_FAILED; |
| goto errout; |
| } |
| |
| (void) memset(&termid, 0, sizeof (au_tid_addr_t)); |
| termid.at_port = user_info->lg_local_port; |
| |
| if (user_info->lg_clnt_ipaddr.a_family == AF_INET) { |
| termid.at_addr[0] = user_info->lg_clnt_ipaddr.a_ipv4; |
| termid.at_type = AU_IPv4; |
| } else { |
| bcopy(&user_info->lg_clnt_ipaddr.a_ip, termid.at_addr, |
| sizeof (in6_addr_t)); |
| termid.at_type = AU_IPv6; |
| } |
| adt_set_termid(ah, &termid); |
| |
| if (adt_set_user(ah, uid, gid, uid, gid, NULL, ADT_NEW)) { |
| syslog(LOG_AUTH | LOG_ALERT, "adt_set_user: %m"); |
| adt_free_event(event); |
| user_info->lg_status = NT_STATUS_AUDIT_FAILED; |
| goto errout; |
| } |
| |
| event->adt_smbd_session.domain = domain; |
| event->adt_smbd_session.username = username; |
| event->adt_smbd_session.sid = sid; |
| |
| if (adt_put_event(event, status, retval)) |
| syslog(LOG_AUTH | LOG_ALERT, "adt_put_event: %m"); |
| |
| adt_free_event(event); |
| |
| if (token) { |
| if ((entry = malloc(sizeof (smb_audit_t))) == NULL) { |
| syslog(LOG_ERR, "smbd_user_auth_logon: %m"); |
| user_info->lg_status = |
| NT_STATUS_INSUFFICIENT_RESOURCES; |
| goto errout; |
| } |
| |
| entry->sa_handle = ah; |
| entry->sa_uid = uid; |
| entry->sa_gid = gid; |
| entry->sa_username = strdup(username); |
| entry->sa_domain = strdup(domain); |
| |
| smb_autohome_add(token); |
| smbd_audit_link(entry); |
| token->tkn_audit_sid = entry->sa_audit_sid; |
| |
| user_info->lg_status = NT_STATUS_SUCCESS; |
| } |
| |
| free(buf); |
| |
| return (token); |
| |
| errout: |
| free(buf); |
| (void) adt_end_session(ah); |
| smb_token_destroy(token); |
| return (NULL); |
| } |
| |
| /* |
| * Logon due to a subsequent SmbSessionSetupX on an existing session. |
| * The user was authenticated during the initial session setup. |
| */ |
| void |
| smbd_user_nonauth_logon(uint32_t audit_sid) |
| { |
| smb_audit_t *entry; |
| |
| (void) mutex_lock(&smbd_audit_lock); |
| entry = smbd_audit_list; |
| |
| while (entry) { |
| if (entry->sa_audit_sid == audit_sid) { |
| ++entry->sa_refcnt; |
| break; |
| } |
| |
| entry = entry->sa_next; |
| } |
| |
| (void) mutex_unlock(&smbd_audit_lock); |
| } |
| |
| /* |
| * Invoked at user logoff due to SmbLogoffX. If this is the final |
| * logoff for this user on the session, audit the event and terminate |
| * the audit session. |
| */ |
| void |
| smbd_user_auth_logoff(uint32_t audit_sid) |
| { |
| smb_audit_t *entry; |
| adt_session_data_t *ah; |
| adt_event_data_t *event; |
| struct passwd pw; |
| char buf[NSS_LINELEN_PASSWD]; |
| |
| if ((entry = smbd_audit_unlink(audit_sid)) == NULL) |
| return; |
| |
| if (IDMAP_ID_IS_EPHEMERAL(entry->sa_uid)) { |
| smb_autohome_remove(entry->sa_username); |
| } else { |
| if (getpwuid_r(entry->sa_uid, &pw, buf, sizeof (buf)) == NULL) |
| return; |
| |
| smb_autohome_remove(pw.pw_name); |
| } |
| |
| ah = entry->sa_handle; |
| |
| if ((event = adt_alloc_event(ah, ADT_smbd_logoff)) == NULL) { |
| syslog(LOG_AUTH | LOG_ALERT, |
| "adt_alloc_event(ADT_smbd_logoff): %m"); |
| } else { |
| event->adt_smbd_logoff.domain = entry->sa_domain; |
| event->adt_smbd_logoff.username = entry->sa_username; |
| |
| if (adt_put_event(event, ADT_SUCCESS, ADT_SUCCESS)) |
| syslog(LOG_AUTH | LOG_ALERT, "adt_put_event: %m"); |
| |
| adt_free_event(event); |
| } |
| |
| (void) adt_end_session(ah); |
| |
| free(entry->sa_username); |
| free(entry->sa_domain); |
| free(entry); |
| } |
| |
| /* |
| * Allocate an id and link an audit handle onto the global list. |
| */ |
| static void |
| smbd_audit_link(smb_audit_t *entry) |
| { |
| (void) mutex_lock(&smbd_audit_lock); |
| |
| do { |
| ++smbd_audit_sid; |
| } while ((smbd_audit_sid == 0) || (smbd_audit_sid == (uint32_t)-1)); |
| |
| entry->sa_audit_sid = smbd_audit_sid; |
| entry->sa_refcnt = 1; |
| entry->sa_next = smbd_audit_list; |
| smbd_audit_list = entry; |
| |
| (void) mutex_unlock(&smbd_audit_lock); |
| } |
| |
| /* |
| * Unlink an audit handle. If the reference count reaches 0, the entry |
| * is removed from the list and returned. Otherwise the entry remains |
| * on the list and a null pointer is returned. |
| */ |
| static smb_audit_t * |
| smbd_audit_unlink(uint32_t audit_sid) |
| { |
| smb_audit_t *entry; |
| smb_audit_t **ppe; |
| |
| (void) mutex_lock(&smbd_audit_lock); |
| ppe = &smbd_audit_list; |
| |
| while (*ppe) { |
| entry = *ppe; |
| |
| if (entry->sa_audit_sid == audit_sid) { |
| if (entry->sa_refcnt == 0) |
| break; |
| |
| if ((--entry->sa_refcnt) != 0) |
| break; |
| |
| *ppe = entry->sa_next; |
| (void) mutex_unlock(&smbd_audit_lock); |
| return (entry); |
| } |
| |
| ppe = &(*ppe)->sa_next; |
| } |
| |
| (void) mutex_unlock(&smbd_audit_lock); |
| return (NULL); |
| } |