/* * 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 2022 Tintri by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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. * * On error, returns NULL, and status in user_info->lg_status. * * Equivalent to smbd_krb5ssp_work(). */ smb_token_t * smbd_user_auth_logon(smb_logon_t *user_info) { smb_token_t *token = NULL; smb_logon_t tmp_user; 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) { user_info->lg_status = NT_STATUS_NO_MEMORY; return (NULL); } 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); if (token == NULL && tmp_user.lg_status == 0) /* should not happen */ user_info->lg_status = NT_STATUS_INTERNAL_ERROR; else user_info->lg_status = tmp_user.lg_status; user_info->lg_status = smbd_logon_final(token, &user_info->lg_clnt_ipaddr, tmp_user.lg_e_username, tmp_user.lg_e_domain, user_info->lg_status); free(buf); if (user_info->lg_status != 0) { smb_token_destroy(token); token = NULL; } return (token); } /* Start an audit session and audit the event. */ static boolean_t smbd_logon_audit(smb_token_t *token, smb_inaddr_t *ipaddr, char *username, char *domain) { smb_audit_t *entry; adt_session_data_t *ah = NULL; adt_event_data_t *event; au_tid_addr_t termid; char sidbuf[SMB_SID_STRSZ]; uid_t uid; gid_t gid; char *sid; int status; int retval; assert(username != NULL); assert(domain != NULL); if (token == NULL) { uid = ADT_NO_ATTRIB; gid = ADT_NO_ATTRIB; sid = NT_NULL_SIDSTR; /* use the 'default' username and domain we were given */ 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"); goto errout; } if ((event = adt_alloc_event(ah, ADT_smbd_session)) == NULL) { syslog(LOG_AUTH | LOG_ALERT, "adt_alloc_event(ADT_smbd_session): %m"); goto errout; } (void) memset(&termid, 0, sizeof (au_tid_addr_t)); termid.at_port = IPPORT_SMB; if (ipaddr->a_family == AF_INET) { termid.at_addr[0] = ipaddr->a_ipv4; termid.at_type = AU_IPv4; } else { bcopy(&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); 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"); goto errout; } entry->sa_handle = ah; entry->sa_uid = uid; entry->sa_gid = gid; entry->sa_username = strdup(username); entry->sa_domain = strdup(domain); smbd_audit_link(entry); token->tkn_audit_sid = entry->sa_audit_sid; } return (B_TRUE); errout: (void) adt_end_session(ah); return (B_FALSE); } /* * Handles all of the work needed to be done after SMB authentication, * regardless of the auth flavor (Kerberos or NTLM). * * This should return the original status to the caller, unless something * here causes us to turn what would be a success into a failure * (or we decide we should override the original error for some reason). */ uint32_t smbd_logon_final(smb_token_t *token, smb_inaddr_t *ipaddr, char *username, char *domain, uint32_t status) { assert(token != NULL || status != 0); if (!smbd_logon_audit(token, ipaddr, username, domain) && status == 0) return (NT_STATUS_AUDIT_FAILED); if (status == 0) smb_autohome_add(token); return (status); } /* * 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 SMB Logoff. If this is the final * logoff for this user on the session, audit the event and terminate * the audit session. * * This is called to logoff both NTLMSSP and KRB5SSP authentications. */ 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); }