/* * 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 2020 Tintri by DDN, Inc. All rights reserved. * Copyright 2020-2023 RackTop Systems, Inc. */ #ifndef _LIBSMB_H #define _LIBSMB_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include /* LIFNAMSIZ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_VARSMB_DIR "/var/smb" #define SMB_VARRUN_DIR "/var/run/smb" #define SMB_CCACHE_FILE "ccache" #define SMB_CCACHE_PATH SMB_VARRUN_DIR "/" SMB_CCACHE_FILE /* Max value length of all SMB properties */ #define MAX_VALUE_BUFLEN 512 #define SMBD_FMRI_PREFIX "network/smb/server" #define SMBD_DEFAULT_INSTANCE_FMRI "svc:/network/smb/server:default" #define SMBD_PG_NAME "smbd" #define SMBD_PROTECTED_PG_NAME "read" #define SMBD_EXEC_PG_NAME "exec" #define SMBD_SMF_OK 0 #define SMBD_SMF_NO_MEMORY 1 /* no memory for data structures */ #define SMBD_SMF_SYSTEM_ERR 2 /* system error, use errno */ #define SMBD_SMF_NO_PERMISSION 3 /* no permission for operation */ #define SMBD_SMF_INVALID_ARG 4 #define SCH_STATE_UNINIT 0 #define SCH_STATE_INITIALIZING 1 #define SCH_STATE_INIT 2 typedef struct smb_scfhandle { scf_handle_t *scf_handle; int scf_state; scf_service_t *scf_service; scf_scope_t *scf_scope; scf_transaction_t *scf_trans; scf_transaction_entry_t *scf_entry; scf_propertygroup_t *scf_pg; scf_instance_t *scf_instance; scf_iter_t *scf_inst_iter; scf_iter_t *scf_pg_iter; } smb_scfhandle_t; /* * CIFS Configuration Management */ typedef enum { SMB_CI_VERSION = 0, SMB_CI_OPLOCK_ENABLE, SMB_CI_AUTOHOME_MAP, SMB_CI_DOMAIN_SID, SMB_CI_DOMAIN_MEMB, SMB_CI_DOMAIN_NAME, SMB_CI_DOMAIN_FQDN, SMB_CI_DOMAIN_FOREST, SMB_CI_DOMAIN_GUID, SMB_CI_DOMAIN_SRV, SMB_CI_WINS_SRV1, SMB_CI_WINS_SRV2, SMB_CI_WINS_EXCL, SMB_CI_MAX_WORKERS, SMB_CI_MAX_CONNECTIONS, SMB_CI_KEEPALIVE, SMB_CI_RESTRICT_ANON, SMB_CI_SIGNING_ENABLE, SMB_CI_SIGNING_REQD, SMB_CI_SYNC_ENABLE, SMB_CI_SECURITY, SMB_CI_NETBIOS_ENABLE, SMB_CI_NBSCOPE, SMB_CI_SYS_CMNT, SMB_CI_LM_LEVEL, SMB_CI_ADS_SITE, SMB_CI_DYNDNS_ENABLE, SMB_CI_MACHINE_PASSWD, SMB_CI_MACHINE_UUID, SMB_CI_KPASSWD_SRV, SMB_CI_KPASSWD_DOMAIN, SMB_CI_KPASSWD_SEQNUM, SMB_CI_NETLOGON_SEQNUM, SMB_CI_IPV6_ENABLE, SMB_CI_PRINT_ENABLE, SMB_CI_MAP, SMB_CI_UNMAP, SMB_CI_DISPOSITION, SMB_CI_DFS_STDROOT_NUM, SMB_CI_TRAVERSE_MOUNTS, SMB_CI_SMB2_ENABLE_OLD, /* obsolete */ SMB_CI_INITIAL_CREDITS, SMB_CI_MAXIMUM_CREDITS, SMB_CI_MAX_PROTOCOL, SMB_CI_ENCRYPT, SMB_CI_MIN_PROTOCOL, SMB_CI_BYPASS_TRAVERSE_CHECKING, SMB_CI_ENCRYPT_CIPHERS, SMB_CI_NETLOGON_FLAGS, SMB_CI_SHORT_NAMES, SMB_CI_MAX_OPENS, SMB_CI_SIGN_ALGS, SMB_CI_MAX } smb_cfg_id_t; /* SMF helper functions */ extern smb_scfhandle_t *smb_smf_scf_init(char *); extern void smb_smf_scf_fini(smb_scfhandle_t *); extern int smb_smf_start_transaction(smb_scfhandle_t *); extern int smb_smf_end_transaction(smb_scfhandle_t *); extern int smb_smf_set_string_property(smb_scfhandle_t *, char *, char *); extern int smb_smf_get_string_property(smb_scfhandle_t *, char *, char *, size_t); extern int smb_smf_set_integer_property(smb_scfhandle_t *, char *, int64_t); extern int smb_smf_get_integer_property(smb_scfhandle_t *, char *, int64_t *); extern int smb_smf_set_boolean_property(smb_scfhandle_t *, char *, uint8_t); extern int smb_smf_get_boolean_property(smb_scfhandle_t *, char *, uint8_t *); extern int smb_smf_set_opaque_property(smb_scfhandle_t *, char *, void *, size_t); extern int smb_smf_get_opaque_property(smb_scfhandle_t *, char *, void *, size_t); extern int smb_smf_create_service_pgroup(smb_scfhandle_t *, char *); extern int smb_smf_delete_property(smb_scfhandle_t *, char *); extern int smb_smf_restart_service(void); extern int smb_smf_maintenance_mode(void); /* ZFS interface */ struct libzfs_handle; int smb_getdataset(struct libzfs_handle *, const char *, char *, size_t); /* Configuration management functions */ extern int smb_config_get(smb_cfg_id_t, char *, int); extern char *smb_config_getname(smb_cfg_id_t); extern int smb_config_getstr(smb_cfg_id_t, char *, int); extern int smb_config_getnum(smb_cfg_id_t, int64_t *); extern boolean_t smb_config_getbool(smb_cfg_id_t); extern int smb_config_set(smb_cfg_id_t, char *); extern int smb_config_setstr(smb_cfg_id_t, char *); extern int smb_config_setnum(smb_cfg_id_t, int64_t); extern int smb_config_setbool(smb_cfg_id_t, boolean_t); extern boolean_t smb_config_get_ads_enable(void); extern int smb_config_get_debug(void); extern uint8_t smb_config_get_fg_flag(void); extern char *smb_config_get_localsid(void); extern int smb_config_get_localuuid(uuid_t); extern int smb_config_secmode_fromstr(char *); extern char *smb_config_secmode_tostr(int); extern int smb_config_get_secmode(void); extern int smb_config_set_secmode(int); extern int smb_config_set_idmap_domain(char *); extern int smb_config_refresh_idmap(void); extern int smb_config_getip(smb_cfg_id_t, smb_inaddr_t *); extern void smb_config_get_version(smb_version_t *); uint32_t smb_config_get_execinfo(char *, char *, size_t); extern void smb_config_get_negtok(uchar_t *, uint32_t *); extern uint32_t smb_config_get_max_protocol(void); extern uint32_t smb_config_get_min_protocol(void); extern uint32_t smb_convert_version_str(const char *); extern void smb_config_upgrade(void); extern uint32_t smb_config_get_encrypt_ciphers(void); extern int smb_convert_encrypt_ciphers(const char *); extern uint32_t smb_config_get_signing_algs(void); extern int smb_convert_signing_algs(const char *); extern smb_cfg_val_t smb_config_get_require(smb_cfg_id_t); extern void smb_load_kconfig(smb_kmod_cfg_t *kcfg); extern uint32_t smb_crc_gen(uint8_t *, size_t); extern boolean_t smb_match_netlogon_seqnum(void); extern int smb_setdomainprops(char *, char *, char *); extern void smb_update_netlogon_seqnum(void); /* maximum password length on Windows 2000 and above */ #define SMB_PASSWD_MAXLEN 127 #define SMB_USERNAME_MAXLEN 40 /* See also: smb_joininfo_xdr() */ typedef struct smb_joininfo { uint32_t mode; char domain_name[MAXHOSTNAMELEN]; char container_name[MAXHOSTNAMELEN]; char domain_username[SMB_USERNAME_MAXLEN + 1]; char domain_passwd[SMB_PASSWD_MAXLEN + 1]; } smb_joininfo_t; /* See also: smb_joinres_xdr() */ typedef struct smb_joinres { uint32_t status; int join_err; char dc_name[MAXHOSTNAMELEN]; } smb_joinres_t; /* APIs to communicate with SMB daemon via door calls */ int smb_join(smb_joininfo_t *, smb_joinres_t *info); bool_t smb_joininfo_xdr(XDR *, smb_joininfo_t *); bool_t smb_joinres_xdr(XDR *, smb_joinres_t *); boolean_t smb_find_ads_server(char *, char *, int); int smb_notify_dc_changed(void); extern void smb_config_getdomaininfo(char *, char *, char *, char *, char *); extern void smb_config_setdomaininfo(char *, char *, char *, char *, char *); extern uint32_t smb_get_dcinfo(char *, uint32_t, smb_inaddr_t *); /* * buffer context structure. This is used to keep track of the buffer * context. * * basep: points to the beginning of the buffer * curp: points to the current offset * endp: points to the limit of the buffer */ typedef struct { unsigned char *basep; unsigned char *curp; unsigned char *endp; } smb_ctxbuf_t; extern int smb_ctxbuf_init(smb_ctxbuf_t *ctx, unsigned char *buf, size_t buflen); extern int smb_ctxbuf_len(smb_ctxbuf_t *ctx); extern int smb_ctxbuf_printf(smb_ctxbuf_t *ctx, const char *fmt, ...); void smb_idmap_check(const char *, idmap_stat); /* Miscellaneous functions */ extern void hexdump(unsigned char *, int); extern size_t bintohex(const char *, size_t, char *, size_t); extern size_t hextobin(const char *, size_t, char *, size_t); extern char *strstrip(char *, const char *); extern char *strtrim(char *, const char *); extern char *trim_whitespace(char *); extern void randomize(char *, unsigned); extern void rand_hash(unsigned char *, size_t, unsigned char *, size_t); extern int smb_getdomainname(char *, size_t); extern int smb_getfqdomainname(char *, size_t); typedef enum smb_caseconv { SMB_CASE_PRESERVE = 0, SMB_CASE_UPPER, SMB_CASE_LOWER } smb_caseconv_t; extern int smb_gethostname(char *, size_t, smb_caseconv_t); extern int smb_getfqhostname(char *, size_t); extern int smb_getnetbiosname(char *, size_t); extern struct hostent *smb_gethostbyname(const char *, int *); extern struct hostent *smb_gethostbyaddr(const char *, int, int, int *); #define SMB_SAMACCT_MAXLEN (NETBIOS_NAME_SZ + 1) extern int smb_getsamaccount(char *, size_t); extern int smb_get_nameservers(smb_inaddr_t *, int); extern void smb_tonetbiosname(char *, char *, char); extern int smb_chk_hostaccess(smb_inaddr_t *, char *); extern int smb_getnameinfo(smb_inaddr_t *, char *, int, int); extern uint32_t smb_get_netlogon_flags(void); void smb_trace(const char *s); void smb_tracef(const char *fmt, ...); const char *xlate_nt_status(unsigned int); void libsmb_redirect_syslog(__FILE_TAG *fp, int priority); /* * Authentication */ #define SMBAUTH_LM_MAGIC_STR "KGS!@#$%" #define SMBAUTH_HASH_SZ 16 /* also LM/NTLM/NTLMv2 Hash size */ #define SMBAUTH_LM_RESP_SZ 24 /* also NTLM Response size */ #define SMBAUTH_LM_PWD_SZ 14 /* LM password size */ #define SMBAUTH_CHAL_SZ 8 /* both LMv2 and NTLMv2 */ #define SMBAUTH_SESSION_KEY_SZ SMBAUTH_HASH_SZ #define SMBAUTH_HEXHASH_SZ (SMBAUTH_HASH_SZ * 2) #define SMBAUTH_RETRY 2 #define SMBAUTH_FAILURE 1 #define SMBAUTH_SUCCESS 0 #define MD_DIGEST_LEN 16 /* * Name Types * * The list of names near the end of the data blob (i.e. the ndb_names * field of the smb_auth_data_blob_t data structure) can be classify into * the following types: * * 0x0000 Indicates the end of the list. * 0x0001 The name is a NetBIOS machine name (e.g. server name) * 0x0002 The name is an NT Domain NetBIOS name. * 0x0003 The name is the server's DNS hostname. * 0x0004 The name is a W2K Domain name (a DNS name). */ #define SMBAUTH_NAME_TYPE_LIST_END 0x0000 #define SMBAUTH_NAME_TYPE_SERVER_NETBIOS 0x0001 #define SMBAUTH_NAME_TYPE_DOMAIN_NETBIOS 0x0002 #define SMBAUTH_NAME_TYPE_SERVER_DNS 0x0003 #define SMBAUTH_NAME_TYPE_DOMAIN_DNS 0x0004 /* * smb_auth_name_entry_t * * Each name entry in the data blob consists of the following 3 fields: * * nne_type - name type * nne_len - the length of the name * nne_name - the name, in uppercase UCS-2LE Unicode format */ typedef struct smb_auth_name_entry { unsigned short nne_type; unsigned short nne_len; smb_wchar_t nne_name[SMB_PI_MAX_DOMAIN * 2]; } smb_auth_name_entry_t; /* * smb_auth_data_blob * * The format of this NTLMv2 data blob structure is as follow: * * - Blob Signature 0x01010000 (4 bytes) * - Reserved (0x00000000) (4 bytes) * - Timestamp Little-endian, 64-bit signed value representing * the number of tenths of a microsecond since January 1, 1601. * (8 bytes) * - Client Challenge (8 bytes) * - Unknown1 (4 bytes) * - List of Target Information (variable length) * - Unknown2 (4 bytes) */ typedef struct smb_auth_data_blob { unsigned char ndb_signature[4]; unsigned char ndb_reserved[4]; uint64_t ndb_timestamp; unsigned char ndb_clnt_challenge[SMBAUTH_CHAL_SZ]; unsigned char ndb_unknown[4]; smb_auth_name_entry_t ndb_names[2]; unsigned char ndb_unknown2[4]; } smb_auth_data_blob_t; #define SMBAUTH_BLOB_MAXLEN (sizeof (smb_auth_data_blob_t)) #define SMBAUTH_CI_MAXLEN SMBAUTH_LM_RESP_SZ #define SMBAUTH_CS_MAXLEN (SMBAUTH_BLOB_MAXLEN + SMBAUTH_HASH_SZ) /* * smb_auth_info_t * * The structure contains all the authentication information * needed for the preparaton of the SMBSessionSetupAndx request * and the user session key. * * hash - NTLM hash * hash_v2 - NTLMv2 hash * ci_len - the length of the case-insensitive password * ci - case-insensitive password * (If NTLMv2 authentication mechanism is used, it * represents the LMv2 response. Otherwise, it * is empty.) * cs_len - the length of the case-sensitive password * cs - case-sensitive password * (If NTLMv2 authentication mechanism is used, it * represents the NTLMv2 response. Otherwise, it * represents the NTLM response.) * data_blob - NTLMv2 data blob */ typedef struct smb_auth_info { unsigned char hash[SMBAUTH_HASH_SZ]; unsigned char hash_v2[SMBAUTH_HASH_SZ]; unsigned short ci_len; unsigned char ci[SMBAUTH_CI_MAXLEN]; unsigned short cs_len; unsigned char cs[SMBAUTH_CS_MAXLEN]; int lmcompatibility_lvl; smb_auth_data_blob_t data_blob; } smb_auth_info_t; /* * SMB password management */ #define SMB_PWF_LM 0x01 /* LM hash is present */ #define SMB_PWF_NT 0x02 /* NT hash is present */ #define SMB_PWF_DISABLE 0x04 /* Account is disabled */ typedef struct smb_passwd { uid_t pw_uid; uint32_t pw_flags; char pw_name[SMB_USERNAME_MAXLEN]; uint8_t pw_lmhash[SMBAUTH_HASH_SZ]; uint8_t pw_nthash[SMBAUTH_HASH_SZ]; } smb_passwd_t; /* * Control flags passed to smb_pwd_setcntl */ #define SMB_PWC_DISABLE 0x01 #define SMB_PWC_ENABLE 0x02 #define SMB_PWC_NOLM 0x04 #define SMB_PWC_DELETE 0x08 #define SMB_PWE_SUCCESS 0 #define SMB_PWE_USER_UNKNOWN 1 #define SMB_PWE_USER_DISABLE 2 #define SMB_PWE_CLOSE_FAILED 3 #define SMB_PWE_OPEN_FAILED 4 #define SMB_PWE_WRITE_FAILED 6 #define SMB_PWE_UPDATE_FAILED 7 #define SMB_PWE_STAT_FAILED 8 #define SMB_PWE_BUSY 9 #define SMB_PWE_DENIED 10 #define SMB_PWE_SYSTEM_ERROR 11 #define SMB_PWE_INVALID_PARAM 12 #define SMB_PWE_NO_MEMORY 13 #define SMB_PWE_MAX 14 typedef struct smb_pwditer { void *spi_next; } smb_pwditer_t; typedef struct smb_luser { char *su_name; char *su_fullname; char *su_desc; uint32_t su_rid; uint32_t su_ctrl; } smb_luser_t; extern void smb_pwd_init(boolean_t); extern void smb_pwd_fini(void); extern smb_passwd_t *smb_pwd_getpwnam(const char *, smb_passwd_t *); extern smb_passwd_t *smb_pwd_getpwuid(uid_t, smb_passwd_t *); extern int smb_pwd_setpasswd(const char *, const char *); extern int smb_pwd_setcntl(const char *, int); extern int smb_pwd_iteropen(smb_pwditer_t *); extern smb_luser_t *smb_pwd_iterate(smb_pwditer_t *); extern void smb_pwd_iterclose(smb_pwditer_t *); extern int smb_auth_qnd_unicode(smb_wchar_t *, const char *, int); extern int smb_auth_hmac_md5(unsigned char *, int, unsigned char *, int, unsigned char *); /* * A variation on HMAC-MD5 known as HMACT64 is used by Windows systems. * The HMACT64() function is the same as the HMAC-MD5() except that * it truncates the input key to 64 bytes rather than hashing it down * to 16 bytes using the MD5() function. */ #define SMBAUTH_HMACT64(D, Ds, K, Ks, digest) \ smb_auth_hmac_md5(D, Ds, K, (Ks > 64) ? 64 : Ks, digest) extern int smb_auth_DES(unsigned char *, int, unsigned char *, int, unsigned char *, int); extern int smb_auth_RC4(unsigned char *, int, unsigned char *, int, unsigned char *, int); extern int smb_auth_md4(unsigned char *, unsigned char *, int); extern int smb_auth_lm_hash(const char *, unsigned char *); extern int smb_auth_ntlm_hash(const char *, unsigned char *); extern void smb_auth_ntlm2_mkchallenge(char *, const char *, const char *); extern void smb_auth_ntlm2_kxkey(unsigned char *, const char *, const char *, unsigned char *); extern int smb_auth_set_info(char *, char *, unsigned char *, char *, unsigned char *, int, int, smb_auth_info_t *); extern int smb_auth_ntlmv2_hash(unsigned char *, char *, char *, unsigned char *); boolean_t smb_auth_validate(smb_passwd_t *, char *, char *, uchar_t *, uint_t, uchar_t *, uint_t, uchar_t *, uint_t, uchar_t *); int smb_gen_random_passwd(char *passwd, size_t bufsz); /* * SMB authenticated IPC */ extern void smb_ipc_commit(void); extern void smb_ipc_get_user(char *, size_t); extern void smb_ipc_get_passwd(uint8_t *, size_t); extern void smb_ipc_init(void); extern void smb_ipc_rollback(void); extern void smb_ipc_set(char *, uint8_t *); /* * Signing flags: * * SMB_SCF_ENABLE Signing is enabled. * * SMB_SCF_REQUIRED Signing is enabled and required. * This flag shouldn't be set if * SMB_SCF_ENABLE isn't set. * * SMB_SCF_STARTED Signing will start after receiving * the first non-anonymous SessionSetup * request. * * SMB_SCF_KEY_ISSET_THIS_LOGON Indicates whether the MAC key has just * been set for this logon. (prior to * sending the SMBSessionSetup request) * */ #define SMB_SCF_ENABLE 0x01 #define SMB_SCF_REQUIRED 0x02 #define SMB_SCF_STARTED 0x04 #define SMB_SCF_KEY_ISSET_THIS_LOGON 0x08 /* * Each domain is categorized using the enum values below. * The local domain refers to the local machine and is named * after the local hostname. The primary domain is the domain * that the system joined. All other domains are either * trusted or untrusted, as defined by the primary domain PDC. */ typedef enum smb_domain_type { SMB_DOMAIN_NULL, SMB_DOMAIN_BUILTIN, SMB_DOMAIN_LOCAL, SMB_DOMAIN_PRIMARY, SMB_DOMAIN_ACCOUNT, SMB_DOMAIN_TRUSTED, SMB_DOMAIN_UNTRUSTED, SMB_DOMAIN_NUM_TYPES } smb_domain_type_t; /* * Information specific to trusted domains */ typedef struct smb_domain_trust { uint32_t dti_trust_direction; uint32_t dti_trust_type; uint32_t dti_trust_attrs; } smb_domain_trust_t; /* * DNS information for domain types that this info is * obtained/available. Currently this is only obtained * for the primary domain. */ typedef struct smb_domain_dns { char ddi_forest[MAXHOSTNAMELEN]; char ddi_guid[UUID_PRINTABLE_STRING_LENGTH]; } smb_domain_dns_t; /* * This is the information that is held about each domain. */ typedef struct smb_domain { list_node_t di_lnd; smb_domain_type_t di_type; char di_sid[SMB_SID_STRSZ]; char di_nbname[NETBIOS_NAME_SZ]; char di_fqname[MAXHOSTNAMELEN]; smb_sid_t *di_binsid; union { smb_domain_dns_t di_dns; smb_domain_trust_t di_trust; } di_u; } smb_domain_t; typedef struct smb_trusted_domains { uint32_t td_num; smb_domain_t *td_domains; } smb_trusted_domains_t; #define SMB_DOMAIN_SUCCESS 0 #define SMB_DOMAIN_NOMACHINE_SID 1 #define SMB_DOMAIN_NODOMAIN_SID 2 #define SMB_DOMAIN_NODOMAIN_NAME 3 #define SMB_DOMAIN_INTERNAL_ERR 4 #define SMB_DOMAIN_INVALID_ARG 5 #define SMB_DOMAIN_NO_MEMORY 6 #define SMB_DOMAIN_NO_CACHE 7 typedef struct smb_dcinfo { char dc_name[MAXHOSTNAMELEN]; smb_inaddr_t dc_addr; uint32_t dc_flags; } smb_dcinfo_t; /* * This structure could contain information about * the primary domain the name of selected domain controller * for the primary domain and a list of trusted domains if * any. The "ex" in the structure name stands for extended. * This is to differentiate this structure from smb_domain_t * which only contains information about a single domain. */ typedef struct smb_domainex { smb_dcinfo_t d_dci; smb_domain_t d_primary; smb_trusted_domains_t d_trusted; } smb_domainex_t; int smb_domain_init(uint32_t); void smb_domain_fini(void); void smb_domain_show(void); void smb_domain_save(void); boolean_t smb_domain_lookup_name(char *, smb_domain_t *); boolean_t smb_domain_lookup_sid(smb_sid_t *, smb_domain_t *); boolean_t smb_domain_lookup_type(smb_domain_type_t, smb_domain_t *); boolean_t smb_domain_getinfo(smb_domainex_t *); void smb_domain_update(smb_domainex_t *); uint32_t smb_domain_start_update(void); void smb_domain_end_update(void); void smb_domain_set_basic_info(char *, char *, char *, smb_domain_t *); void smb_domain_set_dns_info(char *, char *, char *, char *, char *, smb_domain_t *); void smb_domain_set_trust_info(char *, char *, char *, uint32_t, uint32_t, uint32_t, smb_domain_t *); void smb_domain_current_dc(smb_dcinfo_t *); void smb_domain_bad_dc(void); typedef struct smb_gsid { smb_sid_t *gs_sid; uint16_t gs_type; } smb_gsid_t; struct sqlite_vm; struct sqlite; typedef struct smb_giter { struct sqlite_vm *sgi_vm; struct sqlite *sgi_db; uint32_t sgi_nerr; } smb_giter_t; typedef struct smb_group { char *sg_name; char *sg_cmnt; uint32_t sg_attr; uint32_t sg_rid; smb_gsid_t sg_id; smb_domain_type_t sg_domain; smb_privset_t *sg_privs; uint32_t sg_nmembers; smb_gsid_t *sg_members; } smb_group_t; int smb_lgrp_start(void); void smb_lgrp_stop(void); int smb_lgrp_add(char *, char *); int smb_lgrp_rename(char *, char *); int smb_lgrp_delete(char *); int smb_lgrp_setcmnt(char *, char *); int smb_lgrp_getcmnt(char *, char **); int smb_lgrp_getpriv(char *, uint8_t, boolean_t *); int smb_lgrp_setpriv(char *, uint8_t, boolean_t); int smb_lgrp_add_member(char *, smb_sid_t *, uint16_t); int smb_lgrp_del_member(char *, smb_sid_t *, uint16_t); int smb_lgrp_getbyname(char *, smb_group_t *); int smb_lgrp_getbyrid(uint32_t, smb_domain_type_t, smb_group_t *); void smb_lgrp_free(smb_group_t *); uint32_t smb_lgrp_err_to_ntstatus(uint32_t); boolean_t smb_lgrp_is_member(smb_group_t *, smb_sid_t *); char *smb_lgrp_strerror(int); int smb_lgrp_iteropen(smb_giter_t *); void smb_lgrp_iterclose(smb_giter_t *); boolean_t smb_lgrp_itererror(smb_giter_t *); int smb_lgrp_iterate(smb_giter_t *, smb_group_t *); int smb_lookup_sid(const char *, lsa_account_t *); int smb_lookup_lsid(const char *, lsa_account_t *); int smb_lookup_name(const char *, sid_type_t, lsa_account_t *); int smb_lookup_lname(const char *, sid_type_t, lsa_account_t *); #define SMB_LGRP_SUCCESS 0 #define SMB_LGRP_INVALID_ARG 1 #define SMB_LGRP_INVALID_MEMBER 2 #define SMB_LGRP_INVALID_NAME 3 #define SMB_LGRP_NOT_FOUND 4 #define SMB_LGRP_EXISTS 5 #define SMB_LGRP_NO_SID 6 #define SMB_LGRP_NO_LOCAL_SID 7 #define SMB_LGRP_SID_NOTLOCAL 8 #define SMB_LGRP_WKSID 9 #define SMB_LGRP_NO_MEMORY 10 #define SMB_LGRP_DB_ERROR 11 #define SMB_LGRP_DBINIT_ERROR 12 #define SMB_LGRP_INTERNAL_ERROR 13 #define SMB_LGRP_MEMBER_IN_GROUP 14 #define SMB_LGRP_MEMBER_NOT_IN_GROUP 15 #define SMB_LGRP_NO_SUCH_PRIV 16 #define SMB_LGRP_NO_SUCH_DOMAIN 17 #define SMB_LGRP_PRIV_HELD 18 #define SMB_LGRP_PRIV_NOT_HELD 19 #define SMB_LGRP_BAD_DATA 20 #define SMB_LGRP_NO_MORE 21 #define SMB_LGRP_DBOPEN_FAILED 22 #define SMB_LGRP_DBEXEC_FAILED 23 #define SMB_LGRP_DBINIT_FAILED 24 #define SMB_LGRP_DOMLKP_FAILED 25 #define SMB_LGRP_DOMINS_FAILED 26 #define SMB_LGRP_INSERT_FAILED 27 #define SMB_LGRP_DELETE_FAILED 28 #define SMB_LGRP_UPDATE_FAILED 29 #define SMB_LGRP_LOOKUP_FAILED 30 #define SMB_LGRP_NOT_SUPPORTED 31 #define SMB_LGRP_OFFLINE 32 #define SMB_LGRP_POSIXCREATE_FAILED 33 #define SMB_LGRP_COMMENT_MAX 256 /* * values for smb_nic_t.smbflags */ #define SMB_NICF_NBEXCL 0x01 /* Excluded from Netbios activities */ #define SMB_NICF_ALIAS 0x02 /* This is an alias */ /* * smb_nic_t * nic_host actual host name * nic_nbname 16-byte NetBIOS host name */ typedef struct { char nic_host[MAXHOSTNAMELEN]; char nic_nbname[NETBIOS_NAME_SZ]; char nic_cmnt[SMB_PI_MAX_COMMENT]; char nic_ifname[LIFNAMSIZ]; smb_inaddr_t nic_ip; uint32_t nic_mask; uint32_t nic_bcast; uint32_t nic_smbflags; uint64_t nic_sysflags; } smb_nic_t; typedef struct smb_niciter { smb_nic_t ni_nic; int ni_cookie; int ni_seqnum; } smb_niciter_t; /* NIC config functions */ int smb_nic_init(void); void smb_nic_fini(void); int smb_nic_getnum(char *); int smb_nic_addhost(const char *, const char *, int, const char **); int smb_nic_delhost(const char *); int smb_nic_getfirst(smb_niciter_t *); int smb_nic_getnext(smb_niciter_t *); boolean_t smb_nic_is_local(smb_inaddr_t *); boolean_t smb_nic_is_same_subnet(smb_inaddr_t *); #define SMB_NIC_SUCCESS 0 #define SMB_NIC_INVALID_ARG 1 #define SMB_NIC_NOT_FOUND 2 #define SMB_NIC_NO_HOST 3 #define SMB_NIC_NO_MEMORY 4 #define SMB_NIC_DB_ERROR 5 #define SMB_NIC_DBINIT_ERROR 6 #define SMB_NIC_BAD_DATA 7 #define SMB_NIC_NO_MORE 8 #define SMB_NIC_DBOPEN_FAILED 9 #define SMB_NIC_DBEXEC_FAILED 10 #define SMB_NIC_DBINIT_FAILED 11 #define SMB_NIC_INSERT_FAILED 12 #define SMB_NIC_DELETE_FAILED 13 #define SMB_NIC_SOCK 14 #define SMB_NIC_IOCTL 15 #define SMB_NIC_CHANGED 16 /* * Well-known account structure * * A security identifier (SID) is a unique value of variable length that * is used to identify a security principal or security group in * Windows. Well-known SIDs are a group of SIDs that identify generic * users or generic groups. Their values remain constant across all * operating systems. * * This structure is defined to store these SIDs and other related * information about them (e.g. account and domain names) in a * predefined table. */ typedef struct smb_wka { uint8_t wka_domidx; char *wka_sid; char *wka_name; uint16_t wka_type; uint16_t wka_flags; char *wka_desc; smb_sid_t *wka_binsid; } smb_wka_t; /* * Defined values for smb_wka.wka_flags * * SMB_WKAFLG_LGRP_ENABLE Can be added as local group */ #define SMB_WKAFLG_LGRP_ENABLE 0x1 /* * Well-known account interfaces */ smb_wka_t *smb_wka_lookup_builtin(const char *); smb_wka_t *smb_wka_lookup_name(const char *); smb_wka_t *smb_wka_lookup_sid(smb_sid_t *); smb_sid_t *smb_wka_get_sid(const char *); char *smb_wka_get_domain(int); uint32_t smb_wka_token_groups(uint32_t, smb_ids_t *); /* * In memory account representation */ typedef struct smb_account { char *a_name; char *a_domain; uint16_t a_type; smb_sid_t *a_sid; smb_sid_t *a_domsid; uint32_t a_rid; uint32_t a_flags; } smb_account_t; uint32_t smb_sam_lookup_name(char *, char *, uint16_t, smb_account_t *); uint32_t smb_sam_lookup_sid(smb_sid_t *, smb_account_t *); int smb_sam_usr_cnt(void); uint32_t smb_sam_usr_groups(smb_sid_t *, smb_ids_t *); int smb_sam_grp_cnt(smb_domain_type_t); void smb_account_free(smb_account_t *); boolean_t smb_account_validate(smb_account_t *); /* * Security Descriptor functions. */ uint32_t smb_sd_read(char *path, smb_sd_t *, uint32_t); uint32_t smb_sd_write(char *path, smb_sd_t *, uint32_t); uint32_t smb_sd_fromfs(smb_fssd_t *, smb_sd_t *); /* Kernel Module Interface */ int smb_kmod_bind(boolean_t); boolean_t smb_kmod_isbound(void); int smb_kmod_setcfg(smb_kmod_cfg_t *); int smb_kmod_setgmtoff(int32_t); int smb_kmod_start(int, int, int); void smb_kmod_stop(void); int smb_kmod_event_notify(uint32_t); void smb_kmod_unbind(void); int smb_kmod_share(nvlist_t *); int smb_kmod_unshare(nvlist_t *); int smb_kmod_shareinfo(char *, boolean_t *); int smb_kmod_shareaccess(smb_netuserinfo_t *, smb_share_t *); int smb_kmod_get_open_num(smb_opennum_t *); int smb_kmod_enum(smb_netsvc_t *); smb_netsvc_t *smb_kmod_enum_init(smb_svcenum_t *); void smb_kmod_enum_fini(smb_netsvc_t *); int smb_kmod_session_close(const char *, const char *); int smb_kmod_file_close(uint32_t); int smb_kmod_get_spool_doc(uint32_t *, char *, char *, smb_inaddr_t *); void smb_name_parse(char *, char **, char **); uint32_t smb_name_validate_share(const char *); uint32_t smb_name_validate_account(const char *); uint32_t smb_name_validate_domain(const char *); uint32_t smb_name_validate_nbdomain(const char *); uint32_t smb_name_validate_workgroup(const char *); uint32_t smb_name_validate_rpath(const char *); /* * Interposer library validation */ #define SMBEX_VERSION 1 #define SMBEX_KEY "82273fdc-e32a-18c3-3f78-827929dc23ea" typedef struct smbex_version { uint32_t v_version; uuid_t v_uuid; } smbex_version_t; void *smb_dlopen(void); void smb_dlclose(void *); /* * General purpose multi-thread safe cache based on * AVL tree */ typedef struct smb_cache { avl_tree_t ch_cache; rwlock_t ch_cache_lck; uint32_t ch_state; uint32_t ch_nops; uint32_t ch_wait; uint32_t ch_sequence; size_t ch_datasz; mutex_t ch_mtx; cond_t ch_cv; void (*ch_free)(void *); void (*ch_copy)(const void *, void *, size_t); } smb_cache_t; typedef struct smb_cache_node { avl_node_t cn_link; void *cn_data; } smb_cache_node_t; typedef struct smb_cache_cursor { void *cc_next; uint32_t cc_sequence; } smb_cache_cursor_t; /* * flags used with smb_cache_add() * * SMB_CACHE_ADD If object doesn't exist add, otherwise fail * SMB_CACHE_REPLACE If object doesn't exist add, otherwise replace */ #define SMB_CACHE_ADD 1 #define SMB_CACHE_REPLACE 2 void smb_cache_create(smb_cache_t *, uint32_t, int (*cmpfn) (const void *, const void *), void (*freefn)(void *), void (*copyfn)(const void *, void *, size_t), size_t); void smb_cache_destroy(smb_cache_t *); void smb_cache_flush(smb_cache_t *); uint32_t smb_cache_num(smb_cache_t *); int smb_cache_refreshing(smb_cache_t *); void smb_cache_ready(smb_cache_t *); int smb_cache_add(smb_cache_t *, const void *, int); void smb_cache_remove(smb_cache_t *, const void *); void smb_cache_iterinit(smb_cache_t *, smb_cache_cursor_t *); boolean_t smb_cache_iterate(smb_cache_t *, smb_cache_cursor_t *, void *); /* * Values returned by smb_reparse_stat() */ #define SMB_REPARSE_NOTFOUND 1 /* object does not exist */ #define SMB_REPARSE_NOTREPARSE 2 /* object is NOT a reparse point */ #define SMB_REPARSE_ISREPARSE 3 /* object is a reparse point */ /* * Reparse Point API */ int smb_reparse_stat(const char *, uint32_t *); int smb_reparse_svcadd(const char *, const char *, const char *); int smb_reparse_svcdel(const char *, const char *); int smb_reparse_svcget(const char *, const char *, char **); uint32_t smb_get_txid(void); void smb_syslog(int, const char *, ...); void smb_vsyslog(int, const char *, va_list ap); char *smb_syslog_fmt_m(char *, int, const char *, int); #ifdef __cplusplus } #endif #endif /* _LIBSMB_H */ # 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 2020 Tintri by DDN, Inc. All rights reserved. # Copyright 2022-2023 RackTop Systems, Inc. # # # MAPFILE HEADER START # # WARNING: STOP NOW. DO NOT MODIFY THIS FILE. # Object versioning must comply with the rules detailed in # # usr/src/lib/README.mapfiles # # You should not be making modifications here until you've read the most current # copy of that file. If you need help, contact a gatekeeper for guidance. # # MAPFILE HEADER END # $mapfile_version 2 SYMBOL_VERSION SUNWprivate { global: bintohex; dfs_referral_query_xdr; dfs_referral_response_xdr; hexdump; hextobin; ht_add_item; ht_clean_table; ht_clear_delete; ht_create_table; ht_destroy_table; ht_find_item; ht_findfirst; ht_findnext; ht_findnext; ht_get_total_items; ht_mark_delete; ht_register_callback; ht_remove_item; ht_replace_item; ht_set_cmpfn; lsa_account_xdr; oemtoucs; rand_hash; randomize; smb_account_free; smb_account_validate; smb_acl_alloc; smb_acl_free; smb_acl_from_zfs; smb_acl_isvalid; smb_acl_len; smb_acl_sort; smb_acl_to_zfs; smb_auth_DES; smb_auth_RC4; smb_auth_hmac_md5; smb_auth_ntlm_hash; smb_auth_ntlm2_kxkey; smb_auth_ntlm2_mkchallenge; smb_auth_ntlmv2_hash; smb_auth_qnd_unicode; smb_auth_validate; smb_buf32_xdr; smb_cache_add; smb_cache_create; smb_cache_destroy; smb_cache_flush; smb_cache_iterate; smb_cache_iterinit; smb_cache_num; smb_cache_ready; smb_cache_refreshing; smb_cache_remove; smb_cfg_set_require; smb_chk_hostaccess; smb_codepage_init; smb_common_decode; smb_common_encode; smb_config_get; smb_config_get_ads_enable; smb_config_get_debug; smb_config_get_execinfo; smb_config_get_fg_flag; smb_config_get_localsid; smb_config_get_require; smb_config_get_secmode; smb_config_get_version; smb_config_getbool; smb_config_getdomaininfo; smb_config_getip; smb_config_getname; smb_config_getnum; smb_config_getstr; smb_config_refresh_idmap; smb_config_secmode_fromstr; smb_config_secmode_tostr; smb_config_set; smb_config_set_idmap_domain; smb_config_set_secmode; smb_config_setbool; smb_config_setdomaininfo; smb_config_setnum; smb_config_setstr; smb_config_upgrade; smb_convert_encrypt_ciphers; smb_convert_signing_algs; smb_convert_version_str; smb_crc_gen; smb_ctxbuf_init; smb_ctxbuf_len; smb_ctxbuf_printf; smb_dlclose; smb_dlopen; smb_domain_bad_dc; smb_domain_current_dc; smb_domain_end_update; smb_domain_fini; smb_domain_getinfo; smb_domain_init; smb_domain_lookup_name; smb_domain_lookup_sid; smb_domain_lookup_type; smb_domain_save; smb_domain_set_basic_info; smb_domain_set_dns_info; smb_domain_set_trust_info; smb_domain_show; smb_domain_start_update; smb_domain_update; smb_doorhdr_encode; smb_doorhdr_decode; smb_doorhdr_opname; smb_doorhdr_xdr; smb_dr_decode_finish; smb_dr_decode_start; smb_dr_encode_finish; smb_dr_encode_start; smb_dr_free_string; smb_dr_get_BYTE; smb_dr_get_buf; smb_dr_get_dword; smb_dr_get_int32; smb_dr_get_share; smb_dr_get_short; smb_dr_get_string; smb_dr_get_uint32; smb_dr_get_uint64; smb_dr_get_ushort; smb_dr_get_word; smb_dr_put_BYTE; smb_dr_put_buf; smb_dr_put_dword; smb_dr_put_int32; smb_dr_put_share; smb_dr_put_short; smb_dr_put_string; smb_dr_put_uint32; smb_dr_put_uint64; smb_dr_put_ushort; smb_dr_put_word; smb_find_ads_server; smb_fsacl_alloc; smb_fsacl_free; smb_fssd_init; smb_fssd_term; smb_gen_random_passwd; smb_get_dcinfo; smb_get_nameservers; smb_get_netlogon_flags; smb_get_txid; smb_getdataset; smb_getdomainname; smb_getfqdomainname; smb_getfqhostname; smb_gethostbyaddr; smb_gethostbyname; smb_gethostname; smb_getnameinfo; smb_getnetbiosname; smb_getsamaccount; smb_gmttoken_query_xdr; smb_gmttoken_response_xdr; smb_gmttoken_snapname_xdr; smb_joininfo_xdr; smb_joinres_xdr; smb_idmap_batch_create; smb_idmap_batch_destroy; smb_idmap_batch_getid; smb_idmap_batch_getmappings; smb_idmap_batch_getsid; smb_idmap_check; smb_idmap_getid; smb_idmap_getsid; smb_ids_free; smb_inet_equal; smb_inet_iszero; smb_inet_ntop; smb_ipc_set; smb_ipc_commit; smb_ipc_init; smb_ipc_rollback; smb_ipc_get_user; smb_ipc_get_passwd; smb_islower; smb_isupper; smb_isstrlwr; smb_isstrupr; smb_kmod_bind { FLAGS = NODIRECT }; smb_kmod_enum; smb_kmod_enum_init; smb_kmod_enum_fini; smb_kmod_event_notify; smb_kmod_file_close; smb_kmod_get_open_num; smb_kmod_get_spool_doc; smb_kmod_ioctl { FLAGS = NODIRECT }; smb_kmod_isbound { FLAGS = NODIRECT }; smb_kmod_session_close; smb_kmod_setcfg; smb_kmod_setgmtoff; smb_kmod_share; smb_kmod_shareaccess; smb_kmod_shareinfo; smb_kmod_start { FLAGS = NODIRECT }; smb_kmod_stop { FLAGS = NODIRECT }; smb_kmod_unbind { FLAGS = NODIRECT }; smb_kmod_unshare; smb_join; smb_lgrp_add; smb_lgrp_add_member; smb_lgrp_delete; smb_lgrp_del_member; smb_lgrp_err_to_ntstatus; smb_lgrp_free; smb_lgrp_getbyname; smb_lgrp_getbyrid; smb_lgrp_getcmnt; smb_lgrp_getpriv; smb_lgrp_is_member; smb_lgrp_iterate; smb_lgrp_iterclose; smb_lgrp_itererror; smb_lgrp_iteropen; smb_lgrp_rename; smb_lgrp_setcmnt; smb_lgrp_setpriv; smb_lgrp_start; smb_lgrp_stop; smb_lgrp_strerror; smb_load_kconfig; smb_logon_decode; smb_logon_free; smb_logon_xdr; smb_lookup_lname; smb_lookup_lsid; smb_lookup_name; smb_lookup_sid; smb_match_netlogon_seqnum; smb_mbstooem; smb_mbstowcs; smb_mbtowc; smb_msgbuf_base; smb_msgbuf_decode; smb_msgbuf_dword_align; smb_msgbuf_encode; smb_msgbuf_fclear; smb_msgbuf_fset; smb_msgbuf_has_space; smb_msgbuf_init; smb_msgbuf_size; smb_msgbuf_term; smb_msgbuf_used; smb_msgbuf_word_align; smb_name_parse; smb_name_validate_account; smb_name_validate_share; smb_name_validate_domain; smb_name_validate_nbdomain; smb_name_validate_rpath; smb_name_validate_workgroup; smb_netconnectinfo_decode; smb_netconnectinfo_encode; smb_netfileinfo_decode; smb_netfileinfo_encode; smb_netuserinfo_decode; smb_netuserinfo_encode; smb_netuserinfo_xdr; smb_nic_addhost; smb_nic_delhost; smb_nic_is_local; smb_nic_is_same_subnet; smb_nic_fini; smb_nic_getfirst; smb_nic_getnext; smb_nic_getnum; smb_nic_init; smb_notify_dc_changed; smb_oemtombs; smb_priv_getbyname; smb_priv_getbyvalue; smb_priv_presentable_ids; smb_priv_presentable_num; smb_privset_copy; smb_privset_enable; smb_privset_free; smb_privset_init; smb_privset_log; smb_privset_merge; smb_privset_new; smb_privset_query; smb_privset_size; smb_privset_validate; smb_pwd_fini; smb_pwd_getpwnam; smb_pwd_getpwuid; smb_pwd_init; smb_pwd_iterate; smb_pwd_iterclose; smb_pwd_iteropen; smb_pwd_setcntl; smb_pwd_setpasswd; smb_reparse_stat; smb_reparse_svcadd; smb_reparse_svcdel; smb_reparse_svcget; smb_sam_lookup_name; smb_sam_lookup_sid; smb_sam_grp_cnt; smb_sam_usr_cnt; smb_sam_usr_groups; smb_shr_execinfo_xdr; smb_shr_hostaccess_query_xdr; smb_quota_query_xdr; smb_quota_response_xdr; smb_quota_set_xdr; smb_sbequiv_strlen; smb_sd_get_secinfo; smb_sd_fromfs; smb_sd_init; smb_sd_len; smb_sd_term; smb_sd_tofs; smb_sd_read; smb_sd_write; smb_setdomainprops; smb_sid_cmp; smb_sid_dup; smb_sid_free; smb_sid_fromstr; smb_sid_getrid; smb_sid_indomain; smb_sid_islocal; smb_sid_isvalid; smb_sid_len; smb_sid_splice; smb_sid_split; smb_sid_splitstr; smb_sid_tostr; smb_sid_type2str; smb_smf_create_service_pgroup; smb_smf_get_boolean_property; smb_smf_maintenance_mode; smb_smf_restart_service; smb_smf_scf_fini; smb_smf_scf_init; smb_strcasecmp; smb_string_decode; smb_string_encode; smb_string_xdr; smb_strlwr; smb_strupr; smb_syslog { FLAGS = NODIRECT }; smb_syslog_fmt_m; smb_tolower; smb_toupper; smb_token_encode; smb_token_query_privilege; smb_token_valid; smb_token_xdr; smb_tonetbiosname; smb_trace { FLAGS = NODIRECT }; smb_tracef { FLAGS = NODIRECT }; smb_unc_free; smb_unc_init; smb_update_netlogon_seqnum; smb_vsyslog { FLAGS = NODIRECT }; smb_wcequiv_strlen; smb_wcstombs; smb_wctomb; smb_wka_get_domain; smb_wka_get_sid; smb_wka_lookup_builtin; smb_wka_lookup_name; smb_wka_lookup_sid; smb_wka_token_groups; smbnative_lm_str; smbnative_lm_value; smbnative_os_str; smbnative_os_value; smbnative_pdc_value; strcanon; strstrip; strsubst; strtrim; trim_whitespace; ucstooem; xlate_nt_status; local: *; }; /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2023 RackTop Systems, Inc. */ /* * SMB server interfaces for ACL conversion (smb_acl_...) * * There are two variants of this interface: * This is the library version. See also: * $SRC/uts/common/fs/smbsrv/smb_acl.c */ #include #include #include #include #include #include #include #define ACE_ALL_TYPES 0x001F /* * ACE groups within a DACL * * This is from lower to higher ACE order priority */ #define SMB_AG_START 0 #define SMB_AG_ALW_INHRT 0 #define SMB_AG_DNY_INHRT 1 #define SMB_AG_ALW_DRCT 2 #define SMB_AG_DNY_DRCT 3 #define SMB_AG_NUM 4 #define DEFAULT_DACL_ACENUM 2 acl_t *acl_alloc(enum acl_type); static idmap_stat smb_fsacl_getsids(smb_idmap_batch_t *, acl_t *); static acl_t *smb_fsacl_null_empty(boolean_t); static boolean_t smb_ace_isvalid(smb_ace_t *, int); static uint16_t smb_ace_len(smb_ace_t *); static uint32_t smb_ace_mask_g2s(uint32_t); static uint16_t smb_ace_flags_tozfs(uint8_t); static uint8_t smb_ace_flags_fromzfs(uint16_t); static boolean_t smb_ace_wellknown_update(const char *, ace_t *); smb_acl_t * smb_acl_alloc(uint8_t revision, uint16_t bsize, uint16_t acecnt) { smb_acl_t *acl; int size; size = sizeof (smb_acl_t) + (acecnt * sizeof (smb_ace_t)); if ((acl = malloc(size)) == NULL) return (NULL); acl->sl_revision = revision; acl->sl_bsize = bsize; acl->sl_acecnt = acecnt; acl->sl_aces = (smb_ace_t *)(acl + 1); list_create(&acl->sl_sorted, sizeof (smb_ace_t), offsetof(smb_ace_t, se_sln)); return (acl); } void smb_acl_free(smb_acl_t *acl) { int i; void *ace; if (acl == NULL) return; for (i = 0; i < acl->sl_acecnt; i++) smb_sid_free(acl->sl_aces[i].se_sid); while ((ace = list_head(&acl->sl_sorted)) != NULL) list_remove(&acl->sl_sorted, ace); list_destroy(&acl->sl_sorted); free(acl); } /* * smb_acl_len * * Returns the size of given ACL in bytes. Note that this * is not an in-memory size, it's the ACL's size as it would * appear on the wire */ uint16_t smb_acl_len(smb_acl_t *acl) { return ((acl) ? acl->sl_bsize : 0); } boolean_t smb_acl_isvalid(smb_acl_t *acl, int which_acl) { int i; if (acl->sl_bsize < SMB_ACL_HDRSIZE) return (B_FALSE); if (acl->sl_revision != ACL_REVISION) { /* * we are rejecting ACLs with object-specific ACEs for now */ return (B_FALSE); } for (i = 0; i < acl->sl_acecnt; i++) { if (!smb_ace_isvalid(&acl->sl_aces[i], which_acl)) return (B_FALSE); } return (B_TRUE); } /* * smb_acl_sort * * Sorts the given ACL in place if it needs to be sorted. * * The following is an excerpt from MSDN website. * * Order of ACEs in a DACL * * For Windows NT versions 4.0 and earlier, the preferred order of ACEs * is simple: In a DACL, all access-denied ACEs should precede any * access-allowed ACEs. * * For Windows 2000 or later, the proper order of ACEs is more complicated * because of the introduction of object-specific ACEs and automatic * inheritance. * * The following describes the preferred order: * * To ensure that noninherited ACEs have precedence over inherited ACEs, * place all noninherited ACEs in a group before any inherited ACEs. This * ordering ensures, for example, that a noninherited access-denied ACE * is enforced regardless of any inherited ACE that allows access. * Within the groups of noninherited ACEs and inherited ACEs, order ACEs * according to ACE type, as the following shows: * . Access-denied ACEs that apply to the object itself * . Access-denied ACEs that apply to a subobject of the * object, such as a property set or property * . Access-allowed ACEs that apply to the object itself * . Access-allowed ACEs that apply to a subobject of the object * * So, here is the desired ACE order * * deny-direct, allow-direct, deny-inherited, allow-inherited * * Of course, not all ACE types are required in an ACL. */ void smb_acl_sort(smb_acl_t *acl) { list_t ace_grps[SMB_AG_NUM]; list_t *alist; smb_ace_t *ace; uint8_t ace_flags; int ag, i; assert(acl); if (acl->sl_acecnt == 0) { /* * ACL with no entry is a valid ACL and it means * no access for anybody. */ return; } for (i = SMB_AG_START; i < SMB_AG_NUM; i++) { list_create(&ace_grps[i], sizeof (smb_ace_t), offsetof(smb_ace_t, se_sln)); } for (i = 0, ace = acl->sl_aces; i < acl->sl_acecnt; ++i, ace++) { ace_flags = ace->se_hdr.se_flags; switch (ace->se_hdr.se_type) { case ACCESS_DENIED_ACE_TYPE: ag = (ace_flags & INHERITED_ACE) ? SMB_AG_DNY_INHRT : SMB_AG_DNY_DRCT; break; case ACCESS_ALLOWED_ACE_TYPE: ag = (ace_flags & INHERITED_ACE) ? SMB_AG_ALW_INHRT : SMB_AG_ALW_DRCT; break; default: /* * This is the lowest priority group so we put * evertything unknown here. */ ag = SMB_AG_ALW_INHRT; break; } /* Add the ACE to the selected group */ list_insert_tail(&ace_grps[ag], ace); } /* * start with highest priority ACE group and append * the ACEs to the ACL. */ for (i = SMB_AG_NUM - 1; i >= SMB_AG_START; i--) { alist = &ace_grps[i]; while ((ace = list_head(alist)) != NULL) { list_remove(alist, ace); list_insert_tail(&acl->sl_sorted, ace); } list_destroy(alist); } } /* * Error handling call-back for smb_idmap_batch_getmappings. * Would be nice if this could report the path, but that's not * passed down here. For now, use a dtrace fbt probe here. */ static void smb_acl_bgm_error(smb_idmap_batch_t *sib, smb_idmap_t *sim) { if ((sib->sib_flags & SMB_IDMAP_SKIP_ERRS) != 0) return; if ((sib->sib_flags & SMB_IDMAP_ID2SID) != 0) { /* * Note: The ID and type we asked idmap to map * were saved in *sim_id and sim_idtype. */ uid_t id = (sim->sim_id == NULL) ? (uid_t)-1 : *sim->sim_id; syslog(LOG_ERR, "!smb_acl: Can't get SID for " "ID=%u type=%d, status=%d", id, sim->sim_idtype, sim->sim_stat); } if ((sib->sib_flags & SMB_IDMAP_SID2ID) != 0) { syslog(LOG_ERR, "!smb_acl: Can't get ID for " "SID %s-%u, status=%d", sim->sim_domsid, sim->sim_rid, sim->sim_stat); } } /* * smb_acl_from_zfs * * Converts given ZFS ACL to a Windows ACL. * * A pointer to allocated memory for the Windows ACL will be * returned upon successful conversion. */ smb_acl_t * smb_acl_from_zfs(acl_t *zacl) { ace_t *zace; int numaces; smb_acl_t *acl; smb_ace_t *ace; smb_idmap_batch_t sib; smb_idmap_t *sim; idmap_stat idm_stat; idm_stat = smb_idmap_batch_create(&sib, zacl->acl_cnt, SMB_IDMAP_ID2SID); if (idm_stat != IDMAP_SUCCESS) return (NULL); /* * Note that smb_fsacl_getsids sets up references in * sib.sib_maps to the zace->a_who fields that live * until smb_idmap_batch_destroy is called. */ if (smb_fsacl_getsids(&sib, zacl) != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (NULL); } acl = smb_acl_alloc(ACL_REVISION, SMB_ACL_HDRSIZE, zacl->acl_cnt); sim = sib.sib_maps; for (numaces = 0, zace = zacl->acl_aclp; numaces < zacl->acl_cnt; zace++, numaces++, sim++) { assert(sim->sim_sid); if (sim->sim_sid == NULL) { smb_acl_free(acl); acl = NULL; break; } ace = &acl->sl_aces[numaces]; ace->se_hdr.se_type = zace->a_type; ace->se_hdr.se_flags = smb_ace_flags_fromzfs(zace->a_flags); ace->se_mask = zace->a_access_mask; ace->se_sid = smb_sid_dup(sim->sim_sid); ace->se_hdr.se_bsize = smb_ace_len(ace); acl->sl_bsize += ace->se_hdr.se_bsize; } smb_idmap_batch_destroy(&sib); return (acl); } /* * smb_acl_to_zfs * * Converts given Windows ACL to a ZFS ACL. * * fs_acl will contain a pointer to the created ZFS ACL. * The allocated memory should be freed by calling * smb_fsacl_free(). * * Since the output parameter, fs_acl, is allocated in this * function, the caller has to make sure *fs_acl is NULL which * means it's not pointing to any memory. */ uint32_t smb_acl_to_zfs(smb_acl_t *acl, uint32_t flags, int which_acl, acl_t **fs_acl) { char sidstr[SMB_SID_STRSZ]; smb_ace_t *ace; acl_t *zacl; ace_t *zace; smb_idmap_batch_t sib; smb_idmap_t *sim; idmap_stat idm_stat; int i; assert(fs_acl); assert(*fs_acl == NULL); if (acl && !smb_acl_isvalid(acl, which_acl)) return (NT_STATUS_INVALID_ACL); if ((acl == NULL) || (acl->sl_acecnt == 0)) { if (which_acl == SMB_DACL_SECINFO) { *fs_acl = smb_fsacl_null_empty(acl == NULL); } return (NT_STATUS_SUCCESS); } idm_stat = smb_idmap_batch_create(&sib, acl->sl_acecnt, SMB_IDMAP_SID2ID); if (idm_stat != IDMAP_SUCCESS) return (NT_STATUS_INTERNAL_ERROR); zacl = smb_fsacl_alloc(acl->sl_acecnt, flags); zace = zacl->acl_aclp; ace = acl->sl_aces; sim = sib.sib_maps; for (i = 0; i < acl->sl_acecnt; i++, zace++, ace++, sim++) { zace->a_type = ace->se_hdr.se_type & ACE_ALL_TYPES; zace->a_access_mask = smb_ace_mask_g2s(ace->se_mask); zace->a_flags = smb_ace_flags_tozfs(ace->se_hdr.se_flags); zace->a_who = (uid_t)-1; smb_sid_tostr(ace->se_sid, sidstr); if (!smb_ace_wellknown_update(sidstr, zace)) { sim->sim_id = &zace->a_who; idm_stat = smb_idmap_batch_getid(sib.sib_idmaph, sim, ace->se_sid, SMB_IDMAP_UNKNOWN); if (idm_stat != IDMAP_SUCCESS) { smb_fsacl_free(zacl); smb_idmap_batch_destroy(&sib); return (NT_STATUS_INTERNAL_ERROR); } } } idm_stat = smb_idmap_batch_getmappings(&sib, smb_acl_bgm_error); if (idm_stat != IDMAP_SUCCESS) { smb_fsacl_free(zacl); smb_idmap_batch_destroy(&sib); return (NT_STATUS_NONE_MAPPED); } /* * Set the ACEs group flag based on the type of ID returned. */ zace = zacl->acl_aclp; ace = acl->sl_aces; sim = sib.sib_maps; for (i = 0; i < acl->sl_acecnt; i++, zace++, ace++, sim++) { if (zace->a_who == (uid_t)-1) continue; if (sim->sim_idtype == SMB_IDMAP_GROUP) zace->a_flags |= ACE_IDENTIFIER_GROUP; } smb_idmap_batch_destroy(&sib); *fs_acl = zacl; return (NT_STATUS_SUCCESS); } static boolean_t smb_ace_wellknown_update(const char *sid, ace_t *zace) { struct { char *sid; uint16_t flags; } map[] = { { NT_WORLD_SIDSTR, ACE_EVERYONE }, { NT_BUILTIN_CURRENT_OWNER_SIDSTR, ACE_OWNER }, { NT_BUILTIN_CURRENT_GROUP_SIDSTR, (ACE_GROUP | ACE_IDENTIFIER_GROUP) }, }; int i; for (i = 0; i < (sizeof (map) / sizeof (map[0])); ++i) { if (strcmp(sid, map[i].sid) == 0) { zace->a_flags |= map[i].flags; return (B_TRUE); } } return (B_FALSE); } /* * smb_fsacl_getsids * * Batch all the uid/gid in given ZFS ACL to get their corresponding SIDs. * Note: sib is type SMB_IDMAP_ID2SID, zacl->acl_cnt entries. */ static idmap_stat smb_fsacl_getsids(smb_idmap_batch_t *sib, acl_t *zacl) { ace_t *zace; idmap_stat idm_stat; smb_idmap_t *sim; uid_t id; int i, idtype; sim = sib->sib_maps; for (i = 0, zace = zacl->acl_aclp; i < zacl->acl_cnt; zace++, i++, sim++) { id = (uid_t)-1; /* some types do not need id */ switch (zace->a_flags & ACE_TYPE_FLAGS) { case ACE_OWNER: idtype = SMB_IDMAP_OWNERAT; break; case (ACE_GROUP | ACE_IDENTIFIER_GROUP): /* owning group */ idtype = SMB_IDMAP_GROUPAT; break; case ACE_IDENTIFIER_GROUP: /* regular group */ idtype = SMB_IDMAP_GROUP; id = zace->a_who; /* for smb_acl_bgm_error ID2SID */ sim->sim_id = &zace->a_who; break; case ACE_EVERYONE: idtype = SMB_IDMAP_EVERYONE; break; default: /* user entry */ idtype = SMB_IDMAP_USER; id = zace->a_who; /* for smb_acl_bgm_error ID2SID */ sim->sim_id = &zace->a_who; break; } idm_stat = smb_idmap_batch_getsid(sib->sib_idmaph, sim, id, idtype); if (idm_stat != IDMAP_SUCCESS) { return (idm_stat); } } idm_stat = smb_idmap_batch_getmappings(sib, smb_acl_bgm_error); return (idm_stat); } /* * smb_fsacl_null_empty * * NULL DACL means everyone full-access * Empty DACL means everyone full-deny * * ZFS ACL must have at least one entry so smb server has * to simulate the aforementioned expected behavior by adding * an entry in case the requested DACL is null or empty. Adding * a everyone full-deny entry has proved to be problematic in * tests since a deny entry takes precedence over allow entries. * So, instead of adding a everyone full-deny, an owner ACE with * owner implicit permissions will be set. */ static acl_t * smb_fsacl_null_empty(boolean_t null) { acl_t *zacl; ace_t *zace; zacl = smb_fsacl_alloc(1, ACL_AUTO_INHERIT); zace = zacl->acl_aclp; zace->a_type = ACE_ACCESS_ALLOWED_ACE_TYPE; if (null) { zace->a_access_mask = ACE_ALL_PERMS; zace->a_flags = ACE_EVERYONE; } else { zace->a_access_mask = ACE_READ_ACL | ACE_WRITE_ACL | ACE_READ_ATTRIBUTES; zace->a_flags = ACE_OWNER; } return (zacl); } /* * FS ACL (acl_t) Functions */ acl_t * smb_fsacl_alloc(int acenum, int flags) { acl_t *acl; acl = acl_alloc(ACE_T); acl->acl_cnt = acenum; if ((acl->acl_aclp = malloc(acl->acl_entry_size * acenum)) == NULL) return (NULL); acl->acl_flags = flags; return (acl); } void smb_fsacl_free(acl_t *acl) { if (acl) acl_free(acl); } /* * ACE Functions */ /* * This is generic (ACL version 2) vs. object-specific * (ACL version 4) ACE types. */ boolean_t smb_ace_is_generic(int type) { switch (type) { case ACE_ACCESS_ALLOWED_ACE_TYPE: case ACE_ACCESS_DENIED_ACE_TYPE: case ACE_SYSTEM_AUDIT_ACE_TYPE: case ACE_SYSTEM_ALARM_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE: case ACE_SYSTEM_ALARM_CALLBACK_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } boolean_t smb_ace_is_access(int type) { switch (type) { case ACE_ACCESS_ALLOWED_ACE_TYPE: case ACE_ACCESS_DENIED_ACE_TYPE: case ACE_ACCESS_ALLOWED_COMPOUND_ACE_TYPE: case ACE_ACCESS_ALLOWED_OBJECT_ACE_TYPE: case ACE_ACCESS_DENIED_OBJECT_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_ACE_TYPE: case ACE_ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE: case ACE_ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } boolean_t smb_ace_is_audit(int type) { switch (type) { case ACE_SYSTEM_AUDIT_ACE_TYPE: case ACE_SYSTEM_AUDIT_OBJECT_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_ACE_TYPE: case ACE_SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE: return (B_TRUE); default: break; } return (B_FALSE); } /* * smb_ace_len * * Returns the length of the given ACE as it appears in an * ACL on the wire (i.e. a flat buffer which contains the SID) */ static uint16_t smb_ace_len(smb_ace_t *ace) { assert(ace); assert(ace->se_sid); if (ace == NULL) return (0); return (SMB_ACE_HDRSIZE + sizeof (ace->se_mask) + smb_sid_len(ace->se_sid)); } /* * smb_ace_mask_g2s * * Converts generic access bits in the given mask (if any) * to file specific bits. Generic access masks shouldn't be * stored in filesystem ACEs. */ static uint32_t smb_ace_mask_g2s(uint32_t mask) { if (mask & GENERIC_ALL) { mask &= ~(GENERIC_ALL | GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE); mask |= FILE_ALL_ACCESS; return (mask); } if (mask & GENERIC_READ) { mask &= ~GENERIC_READ; mask |= FILE_GENERIC_READ; } if (mask & GENERIC_WRITE) { mask &= ~GENERIC_WRITE; mask |= FILE_GENERIC_WRITE; } if (mask & GENERIC_EXECUTE) { mask &= ~GENERIC_EXECUTE; mask |= FILE_GENERIC_EXECUTE; } return (mask); } /* * smb_ace_flags_tozfs * * This function maps the flags which have different values * in Windows and Solaris. The ones with the same value are * transferred untouched. */ static uint16_t smb_ace_flags_tozfs(uint8_t c_flags) { uint16_t z_flags = 0; if (c_flags & SUCCESSFUL_ACCESS_ACE_FLAG) z_flags |= ACE_SUCCESSFUL_ACCESS_ACE_FLAG; if (c_flags & FAILED_ACCESS_ACE_FLAG) z_flags |= ACE_FAILED_ACCESS_ACE_FLAG; if (c_flags & INHERITED_ACE) z_flags |= ACE_INHERITED_ACE; z_flags |= (c_flags & ACE_INHERIT_FLAGS); return (z_flags); } static uint8_t smb_ace_flags_fromzfs(uint16_t z_flags) { uint8_t c_flags; c_flags = z_flags & ACE_INHERIT_FLAGS; if (z_flags & ACE_SUCCESSFUL_ACCESS_ACE_FLAG) c_flags |= SUCCESSFUL_ACCESS_ACE_FLAG; if (z_flags & ACE_FAILED_ACCESS_ACE_FLAG) c_flags |= FAILED_ACCESS_ACE_FLAG; if (z_flags & ACE_INHERITED_ACE) c_flags |= INHERITED_ACE; return (c_flags); } static boolean_t smb_ace_isvalid(smb_ace_t *ace, int which_acl) { uint16_t min_len; min_len = sizeof (smb_acehdr_t); if (ace->se_hdr.se_bsize < min_len) return (B_FALSE); if (smb_ace_is_access(ace->se_hdr.se_type) && (which_acl != SMB_DACL_SECINFO)) return (B_FALSE); if (smb_ace_is_audit(ace->se_hdr.se_type) && (which_acl != SMB_SACL_SECINFO)) return (B_FALSE); if (smb_ace_is_generic(ace->se_hdr.se_type)) { if (!smb_sid_isvalid(ace->se_sid)) return (B_FALSE); min_len += sizeof (ace->se_mask); min_len += smb_sid_len(ace->se_sid); if (ace->se_hdr.se_bsize < min_len) return (B_FALSE); } /* * object-specific ACE validation will be added later. */ return (B_TRUE); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include /* libsmbfs */ #include #define NTLM_CHAL_SZ SMBAUTH_CHAL_SZ /* challenge size */ /* * Compute the combined (server+client) challenge per. [MS-NLMP 3.3.1] * MD5(concat(ServerChallenge,ClientChallenge)) */ void smb_auth_ntlm2_mkchallenge(char *result, const char *srv_chal, const char *clnt_chal) { MD5_CTX context; uchar_t challenges[2 * NTLM_CHAL_SZ]; uchar_t digest[SMBAUTH_HASH_SZ]; /* * challenges = ConcatenationOf(ServerChallenge, ClientChallenge) */ (void) memcpy(challenges, srv_chal, NTLM_CHAL_SZ); (void) memcpy(challenges + NTLM_CHAL_SZ, clnt_chal, NTLM_CHAL_SZ); /* * digest = MD5(challenges) */ MD5Init(&context); MD5Update(&context, challenges, sizeof (challenges)); MD5Final(digest, &context); /* * result = digest[0..7] */ (void) memcpy(result, digest, NTLM_CHAL_SZ); } void smb_auth_ntlm2_kxkey(unsigned char *result, const char *srv_chal, const char *clnt_chal, unsigned char *ssn_base_key) { uchar_t challenges[2 * NTLM_CHAL_SZ]; /* * challenges = ConcatenationOf(ServerChallenge, ClientChallenge) */ (void) memcpy(challenges, srv_chal, NTLM_CHAL_SZ); (void) memcpy(challenges + NTLM_CHAL_SZ, clnt_chal, NTLM_CHAL_SZ); /* HMAC_MD5(SessionBaseKey, concat(...)) */ /* SMBAUTH_HMACT64 args: D, Dsz, K, Ksz, digest */ (void) SMBAUTH_HMACT64(challenges, sizeof (challenges), ssn_base_key, SMBAUTH_HASH_SZ, result); } /* * smb_auth_qnd_unicode * * Quick and dirty unicode conversion! * Returns the length of dst in bytes. */ int smb_auth_qnd_unicode(smb_wchar_t *dst, const char *src, int length) { int i; unsigned int count; smb_wchar_t new_char; if ((count = oemtoucs(dst, src, length, OEM_CPG_1252)) == 0) { for (i = 0; i < length; ++i) { new_char = (smb_wchar_t)src[i] & 0xff; dst[i] = LE_IN16(&new_char); } dst[i] = 0; count = length; } return (count * sizeof (smb_wchar_t)); } /* * smb_auth_lmupr * * Converts the given LM password to all uppercase. * The standard strupr cannot * be used here because lm_pwd doesn't have to be * nul terminated. */ static void smb_auth_lmupr(unsigned char *lm_pwd) { unsigned char *p = lm_pwd; int i; for (i = 0; (*p) && (i < SMBAUTH_LM_PWD_SZ); i++) { if (smb_isascii(*p)) { *p = smb_toupper(*p); p++; } } } /* * smb_auth_lm_hash * * Source: Implementing CIFS (Chris Hertel) * * 1. The password, as entered by user, is either padded with nulls * or trimmed to 14 bytes. * . Note that the 14-byte result string is not handled as a * nul-terminated string. * . The given password is OEM not Unicode * * 2. The 14-byte password is converted to all uppercase * * 3. The result is used as key to encrypt the KGS magic string to * make a 16-byte hash. */ int smb_auth_lm_hash(const char *password, unsigned char *lm_hash) { unsigned char lm_pwd[SMBAUTH_LM_PWD_SZ]; bzero((void *)lm_pwd, SMBAUTH_LM_PWD_SZ); (void) strncpy((char *)lm_pwd, password, SMBAUTH_LM_PWD_SZ); smb_auth_lmupr(lm_pwd); return (smb_auth_DES(lm_hash, SMBAUTH_HASH_SZ, lm_pwd, SMBAUTH_LM_PWD_SZ, (unsigned char *)SMBAUTH_LM_MAGIC_STR, sizeof (SMBAUTH_LM_MAGIC_STR))); } /* * smb_auth_lm_response * * Create a LM response from the given LM hash and challenge. * * Returns SMBAUTH_FAILURE if any problems occur, SMBAUTH_SUCCESS if * all goes well. */ static int smb_auth_lm_response(unsigned char *hash, unsigned char *challenge, /* NTLM_CHAL_SZ */ unsigned char *lm_rsp) { unsigned char S21[21]; /* * 14-byte LM Hash should be padded with 5 nul bytes to create * a 21-byte string to be used in producing LM response */ bzero(&S21[SMBAUTH_HASH_SZ], 5); bcopy(hash, S21, SMBAUTH_HASH_SZ); /* padded LM Hash -> LM Response */ return (smb_auth_DES(lm_rsp, SMBAUTH_LM_RESP_SZ, S21, 21, challenge, NTLM_CHAL_SZ)); } /* * smb_auth_ntlm_hash * * Make NTLM Hash (using MD4) from the given password. * The result will contain a 16-byte NTLM hash. */ int smb_auth_ntlm_hash(const char *password, unsigned char *hash) { smb_wchar_t *unicode_password; int length, unicode_len; int rc; if (password == NULL || hash == NULL) return (SMBAUTH_FAILURE); length = strlen(password); unicode_len = (length + 1) * sizeof (smb_wchar_t); unicode_password = malloc(unicode_len); if (unicode_password == NULL) return (SMBAUTH_FAILURE); length = smb_auth_qnd_unicode(unicode_password, password, length); rc = smb_auth_md4(hash, (unsigned char *)unicode_password, length); (void) memset(unicode_password, 0, unicode_len); free(unicode_password); return (rc); } /* * smb_auth_ntlm_response * * Make LM/NTLM response from the given LM/NTLM Hash and given * challenge. */ static int smb_auth_ntlm_response(unsigned char *hash, unsigned char *challenge, /* NTLM_CHAL_SZ */ unsigned char *ntlm_rsp) { unsigned char S21[21]; bcopy(hash, S21, SMBAUTH_HASH_SZ); bzero(&S21[SMBAUTH_HASH_SZ], 5); if (smb_auth_DES((unsigned char *)ntlm_rsp, SMBAUTH_LM_RESP_SZ, S21, 21, challenge, NTLM_CHAL_SZ) == SMBAUTH_FAILURE) return (0); return (SMBAUTH_LM_RESP_SZ); } /* * smb_auth_ntlmv2_hash * * The NTLM v2 hash will be created from the given NTLM hash, username, * and the NETBIOS name of the domain. * * The NTLMv2 hash will be returned via the ntlmv2_hash parameter which * will be used in the calculation of the NTLMv2 and LMv2 responses. */ int smb_auth_ntlmv2_hash(unsigned char *ntlm_hash, char *username, char *ntdomain, unsigned char *ntlmv2_hash) { smb_wchar_t *data; int data_len; unsigned char *buf; int rc; if (username == NULL || ntdomain == NULL) return (SMBAUTH_FAILURE); (void) smb_strupr(username); data_len = strlen(username) + strlen(ntdomain); buf = (unsigned char *)malloc((data_len + 1) * sizeof (char)); if (buf == NULL) return (SMBAUTH_FAILURE); (void) snprintf((char *)buf, data_len + 1, "%s%s", username, ntdomain); data = (smb_wchar_t *)malloc((data_len + 1) * sizeof (smb_wchar_t)); if (data == NULL) { free(buf); return (SMBAUTH_FAILURE); } data_len = smb_auth_qnd_unicode(data, (char *)buf, data_len); rc = SMBAUTH_HMACT64((unsigned char *)data, data_len, ntlm_hash, SMBAUTH_HASH_SZ, ntlmv2_hash); free(buf); free(data); return (rc); } /* * smb_auth_v2_response * * Caculates either the LMv2 or NTLMv2 response. * * Same algorithm is used for calculating both LMv2 or NTLMv2 responses. * This routine will return NTLMv2 response if the data blob information * is passed in as the clnt_data. Otherwise, it will return LMv2 response * with the 8-byte client challenge(a.k.a blip) as the clnt_data. * * (LM/NTLM)v2 response is the hmac-md5 hash of the specified data * (server challenge + NTLMv2 data blob or LMv2 client challenge) * using the NTLMv2 hash as the key. * * Returns the size of the corresponding v2 response upon success. * Otherwise, returns -1 on error. */ static int smb_auth_v2_response( unsigned char *hash, unsigned char *srv_challenge, /* NTLM_CHAL_SZ */ unsigned char *clnt_data, int clen, unsigned char *v2_rsp) { unsigned char *hmac_data; int slen = NTLM_CHAL_SZ; hmac_data = malloc(NTLM_CHAL_SZ + clen); if (!hmac_data) { return (-1); } (void) memcpy(hmac_data, srv_challenge, slen); (void) memcpy(&hmac_data[slen], clnt_data, clen); if (SMBAUTH_HMACT64(hmac_data, slen + clen, (unsigned char *)hash, SMBAUTH_HASH_SZ, (unsigned char *)v2_rsp) != SMBAUTH_SUCCESS) return (-1); (void) memcpy(&v2_rsp[SMBAUTH_HASH_SZ], clnt_data, clen); free(hmac_data); return (SMBAUTH_HASH_SZ + clen); } static boolean_t smb_lm_password_ok( unsigned char *challenge, unsigned char *lm_hash, unsigned char *lm_resp) { unsigned char ok_resp[SMBAUTH_LM_RESP_SZ]; int rc; rc = smb_auth_lm_response(lm_hash, challenge, ok_resp); if (rc != SMBAUTH_SUCCESS) return (B_FALSE); return (bcmp(ok_resp, lm_resp, SMBAUTH_LM_RESP_SZ) == 0); } static boolean_t smb_ntlm_password_ok( unsigned char *challenge, unsigned char *ntlm_hash, unsigned char *nt_resp, unsigned char *session_key) { unsigned char ok_resp[SMBAUTH_LM_RESP_SZ]; int rc; boolean_t ok; rc = smb_auth_ntlm_response(ntlm_hash, challenge, ok_resp); if (rc != SMBAUTH_LM_RESP_SZ) return (B_FALSE); ok = (bcmp(ok_resp, nt_resp, SMBAUTH_LM_RESP_SZ) == 0); if (ok && (session_key)) { rc = smb_auth_md4(session_key, ntlm_hash, SMBAUTH_HASH_SZ); if (rc != SMBAUTH_SUCCESS) ok = B_FALSE; } return (ok); } static boolean_t smb_ntlmv2_password_ok( unsigned char *challenge, unsigned char *ntlm_hash, unsigned char *passwd, int pwdlen, char *domain, char *username, uchar_t *session_key) { unsigned char *clnt_blob; int clnt_blob_len; unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ]; unsigned char *ntlmv2_resp; boolean_t ok = B_FALSE; char *dest[3]; int i; int rc; clnt_blob_len = pwdlen - SMBAUTH_HASH_SZ; clnt_blob = &passwd[SMBAUTH_HASH_SZ]; dest[0] = domain; if ((dest[1] = strdup(domain)) == NULL) return (B_FALSE); (void) smb_strupr(dest[1]); dest[2] = ""; /* * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS" * * The NTLMv2 Hash is created from: * - NTLM hash * - user's username, and * - the name of the logon destination(i.e. the NetBIOS name of either * the SMB server or NT Domain against which the user is trying to * authenticate. * * Experiments show this is not exactly the case. * For Windows Server 2003, the domain name needs to be included and * converted to uppercase. For Vista, the domain name needs to be * included also, but leave the case alone. And in some cases it needs * to be empty. All three variants are tried here. */ ntlmv2_resp = (unsigned char *)malloc(SMBAUTH_HASH_SZ + clnt_blob_len); if (ntlmv2_resp == NULL) { free(dest[1]); return (B_FALSE); } for (i = 0; i < (sizeof (dest) / sizeof (char *)); i++) { if (smb_auth_ntlmv2_hash(ntlm_hash, username, dest[i], ntlmv2_hash) != SMBAUTH_SUCCESS) break; if (smb_auth_v2_response(ntlmv2_hash, challenge, clnt_blob, clnt_blob_len, ntlmv2_resp) < 0) break; ok = (bcmp(passwd, ntlmv2_resp, pwdlen) == 0); if (ok && session_key) { rc = SMBAUTH_HMACT64(ntlmv2_resp, SMBAUTH_HASH_SZ, ntlmv2_hash, SMBAUTH_SESSION_KEY_SZ, session_key); if (rc != SMBAUTH_SUCCESS) { ok = B_FALSE; } break; } } free(dest[1]); free(ntlmv2_resp); return (ok); } static boolean_t smb_lmv2_password_ok( unsigned char *srv_challenge, unsigned char *ntlm_hash, unsigned char *passwd, char *domain, char *username) { unsigned char *clnt_challenge; unsigned char ntlmv2_hash[SMBAUTH_HASH_SZ]; unsigned char lmv2_resp[SMBAUTH_LM_RESP_SZ]; boolean_t ok = B_FALSE; char *dest[3]; int i; clnt_challenge = &passwd[SMBAUTH_HASH_SZ]; dest[0] = domain; if ((dest[1] = strdup(domain)) == NULL) return (B_FALSE); (void) smb_strupr(dest[1]); dest[2] = ""; /* * 15.5.2 The NTLMv2 Password Hash, pg. 279, of the "Implementing CIFS" * * The NTLMv2 Hash is created from: * - NTLM hash * - user's username, and * - the name of the logon destination(i.e. the NetBIOS name of either * the SMB server or NT Domain against which the suer is trying to * authenticate. * * Experiments show this is not exactly the case. * For Windows Server 2003, the domain name needs to be included and * converted to uppercase. For Vista, the domain name needs to be * included also, but leave the case alone. And in some cases it needs * to be empty. All three variants are tried here. */ for (i = 0; i < (sizeof (dest) / sizeof (char *)); i++) { if (smb_auth_ntlmv2_hash(ntlm_hash, username, dest[i], ntlmv2_hash) != SMBAUTH_SUCCESS) break; if (smb_auth_v2_response(ntlmv2_hash, srv_challenge, clnt_challenge, SMBAUTH_CHAL_SZ, lmv2_resp) < 0) break; ok = (bcmp(passwd, lmv2_resp, SMBAUTH_LM_RESP_SZ) == 0); if (ok) break; } free(dest[1]); return (ok); } /* * smb_auth_validate * * Validates given NTLMv2 (or NTLM, LMv2, LM) client responses against * the stored user's password, passed in smbpw. Try those in the order * strongest to weakest, stopping at a point determined by the configured * lmauth_level (LM Compatibility Level). */ boolean_t smb_auth_validate( smb_passwd_t *smbpw, char *domain, char *username, unsigned char *challenge, uint_t clen, unsigned char *nt_resp, uint_t nt_len, unsigned char *lm_resp, uint_t lm_len, uchar_t *session_key) { int64_t lmlevel; boolean_t ok = B_FALSE; if (smb_config_getnum(SMB_CI_LM_LEVEL, &lmlevel) != SMBD_SMF_OK) return (B_FALSE); if (lmlevel > 5) return (B_FALSE); if (clen != NTLM_CHAL_SZ) return (B_FALSE); /* * Accept NTLMv2 at any LM level (0-5). */ if (nt_len > SMBAUTH_LM_RESP_SZ) { ok = smb_ntlmv2_password_ok(challenge, smbpw->pw_nthash, nt_resp, nt_len, domain, username, session_key); if (ok) return (ok); } if (lmlevel == 5) return (B_FALSE); /* * Accept NTLM at levels 0-4 */ if (nt_len == SMBAUTH_LM_RESP_SZ) { ok = smb_ntlm_password_ok(challenge, smbpw->pw_nthash, nt_resp, session_key); if (ok) return (ok); } if (lmlevel == 4) return (B_FALSE); /* * Accept LM/LMv2 auth at levels 0-3 */ if (lm_len != SMBAUTH_LM_RESP_SZ) return (B_FALSE); if (session_key) (void) smb_auth_md4(session_key, smbpw->pw_nthash, SMBAUTH_HASH_SZ); ok = smb_lmv2_password_ok(challenge, smbpw->pw_nthash, lm_resp, domain, username); if (ok) return (ok); ok = smb_lm_password_ok(challenge, smbpw->pw_lmhash, lm_resp); if (ok) return (ok); return (B_FALSE); } /* * smb_gen_random_passwd(buf, len) * Generate a random password of length len-1, and store it in buf, * null terminated. This is used as a machine account password, * which we set when we join a domain. * * [MS-DISO] A machine password is an ASCII string of randomly chosen * characters. Each character's ASCII code is between 32 and 122 inclusive. * That's space through 'z'. */ int smb_gen_random_passwd(char *buf, size_t len) { const uchar_t start = ' '; const uchar_t modulus = 'z' - ' ' + 1; uchar_t t; int i; /* Last byte is the null. */ len--; /* Temporarily put random data in the caller's buffer. */ randomize(buf, len); /* Convert the random data to printable characters. */ for (i = 0; i < len; i++) { /* need unsigned math */ t = (uchar_t)buf[i]; t = (t % modulus) + start; assert(' ' <= t && t <= 'z'); buf[i] = (char)t; } buf[len] = '\0'; return (0); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include /* * Cache lock modes */ #define SMB_CACHE_RDLOCK 0 #define SMB_CACHE_WRLOCK 1 #define SMB_CACHE_STATE_NOCACHE 0 #define SMB_CACHE_STATE_READY 1 #define SMB_CACHE_STATE_REFRESHING 2 #define SMB_CACHE_STATE_DESTROYING 3 static int smb_cache_lock(smb_cache_t *, int); static int smb_cache_rdlock(smb_cache_t *); static int smb_cache_wrlock(smb_cache_t *); static void smb_cache_unlock(smb_cache_t *); static boolean_t smb_cache_wait(smb_cache_t *); static void smb_cache_destroy_nodes(smb_cache_t *); /* * Creates an AVL tree and initializes the given cache handle. * Transfers the cache to READY state. * * This function does not populate the cache. * * chandle pointer to a smb_cache_t structure * waittime see smb_cache_refreshing() comments * cmpfn compare function used by AVL tree * freefn if set, it will be used to free any allocated * memory for the node data stored in the cache when * that node is removed. * copyfn this function has to be set and it is used * to provide a copy of the node data stored in the * cache to the caller of smb_cache_iterate or any other * function that is used to access nodes data. * This can typically be 'bcopy' if data is fixed size. * datasz Size of data stored in the cache if it's fixed size. * This size will be passed to the copy function. */ void smb_cache_create(smb_cache_t *chandle, uint32_t waittime, int (*cmpfn) (const void *, const void *), void (*freefn)(void *), void (*copyfn)(const void *, void *, size_t), size_t datasz) { assert(chandle); assert(copyfn); (void) mutex_lock(&chandle->ch_mtx); if (chandle->ch_state != SMB_CACHE_STATE_NOCACHE) { (void) mutex_unlock(&chandle->ch_mtx); return; } avl_create(&chandle->ch_cache, cmpfn, sizeof (smb_cache_node_t), offsetof(smb_cache_node_t, cn_link)); chandle->ch_state = SMB_CACHE_STATE_READY; chandle->ch_nops = 0; chandle->ch_wait = waittime; chandle->ch_sequence = random(); chandle->ch_datasz = datasz; chandle->ch_free = freefn; chandle->ch_copy = copyfn; (void) mutex_unlock(&chandle->ch_mtx); } /* * Destroys the cache. * * Transfers the cache to DESTROYING state while it's waiting for * in-flight operation to finish, this will prevent any new operation * to start. When all entries are removed the cache is transferred to * NOCACHE state. */ void smb_cache_destroy(smb_cache_t *chandle) { (void) mutex_lock(&chandle->ch_mtx); switch (chandle->ch_state) { case SMB_CACHE_STATE_NOCACHE: case SMB_CACHE_STATE_DESTROYING: (void) mutex_unlock(&chandle->ch_mtx); return; default: break; } chandle->ch_state = SMB_CACHE_STATE_DESTROYING; while (chandle->ch_nops > 0) (void) cond_wait(&chandle->ch_cv, &chandle->ch_mtx); smb_cache_destroy_nodes(chandle); avl_destroy(&chandle->ch_cache); chandle->ch_state = SMB_CACHE_STATE_NOCACHE; (void) mutex_unlock(&chandle->ch_mtx); } /* * Removes and frees all the cache entries without destroy * the cache itself. */ void smb_cache_flush(smb_cache_t *chandle) { if (smb_cache_wrlock(chandle) == 0) { smb_cache_destroy_nodes(chandle); chandle->ch_sequence++; smb_cache_unlock(chandle); } } /* * Based on the specified flag either add or replace given * data. If ADD flag is specified and the item is already in * the cache EEXIST error code is returned. */ int smb_cache_add(smb_cache_t *chandle, const void *data, int flags) { smb_cache_node_t *newnode; smb_cache_node_t *node; avl_index_t where; int rc = 0; assert(data); if ((rc = smb_cache_wrlock(chandle)) != 0) return (rc); if ((newnode = malloc(sizeof (smb_cache_node_t))) == NULL) { smb_cache_unlock(chandle); return (ENOMEM); } newnode->cn_data = (void *)data; node = avl_find(&chandle->ch_cache, newnode, &where); if (node != NULL) { if (flags & SMB_CACHE_REPLACE) { avl_remove(&chandle->ch_cache, node); if (chandle->ch_free) chandle->ch_free(node->cn_data); free(node); } else { free(newnode); smb_cache_unlock(chandle); return (EEXIST); } } avl_insert(&chandle->ch_cache, newnode, where); chandle->ch_sequence++; smb_cache_unlock(chandle); return (rc); } /* * Uses the given 'data' as key to find a cache entry * and remove it. The memory allocated for the found node * and its data is freed. */ void smb_cache_remove(smb_cache_t *chandle, const void *data) { smb_cache_node_t keynode; smb_cache_node_t *node; assert(data); if (smb_cache_wrlock(chandle) != 0) return; keynode.cn_data = (void *)data; node = avl_find(&chandle->ch_cache, &keynode, NULL); if (node) { chandle->ch_sequence++; avl_remove(&chandle->ch_cache, node); if (chandle->ch_free) chandle->ch_free(node->cn_data); free(node); } smb_cache_unlock(chandle); } /* * Initializes the given cursor for iterating the cache */ void smb_cache_iterinit(smb_cache_t *chandle, smb_cache_cursor_t *cursor) { cursor->cc_sequence = chandle->ch_sequence; cursor->cc_next = NULL; } /* * Iterate the cache using the given cursor. * * Data is copied to the given buffer ('data') using the copy function * specified at cache creation time. * * If the cache is modified while an iteration is in progress it causes * the iteration to finish prematurely. This is to avoid the need to lock * the whole cache while it is being iterated. */ boolean_t smb_cache_iterate(smb_cache_t *chandle, smb_cache_cursor_t *cursor, void *data) { smb_cache_node_t *node; assert(data); if (smb_cache_rdlock(chandle) != 0) return (B_FALSE); if (cursor->cc_sequence != chandle->ch_sequence) { smb_cache_unlock(chandle); return (B_FALSE); } if (cursor->cc_next == NULL) node = avl_first(&chandle->ch_cache); else node = AVL_NEXT(&chandle->ch_cache, cursor->cc_next); if (node != NULL) chandle->ch_copy(node->cn_data, data, chandle->ch_datasz); cursor->cc_next = node; smb_cache_unlock(chandle); return (node != NULL); } /* * Returns the number of cache entries */ uint32_t smb_cache_num(smb_cache_t *chandle) { uint32_t num = 0; if (smb_cache_rdlock(chandle) == 0) { num = (uint32_t)avl_numnodes(&chandle->ch_cache); smb_cache_unlock(chandle); } return (num); } /* * Transfers the cache into REFRESHING state. This function needs * to be called when the whole cache is being populated or refereshed * and not for individual changes. * * Calling this function will ensure any read access to the cache will * be stalled until the update is finished, which is to avoid providing * incomplete, inconsistent or stale information. Read accesses will be * stalled for 'ch_wait' seconds (see smb_cache_lock), which is set at * the cache creation time. * * If it is okay for the cache to be accessed while it's being populated * or refreshed, then there is no need to call this function. * * If another thread is already updating the cache, other callers will wait * until cache is no longer in REFRESHING state. The return code is decided * based on the new state of the cache. * * This function does NOT perform the actual refresh. */ int smb_cache_refreshing(smb_cache_t *chandle) { int rc = 0; (void) mutex_lock(&chandle->ch_mtx); switch (chandle->ch_state) { case SMB_CACHE_STATE_READY: chandle->ch_state = SMB_CACHE_STATE_REFRESHING; rc = 0; break; case SMB_CACHE_STATE_REFRESHING: while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING) (void) cond_wait(&chandle->ch_cv, &chandle->ch_mtx); if (chandle->ch_state == SMB_CACHE_STATE_READY) { chandle->ch_state = SMB_CACHE_STATE_REFRESHING; rc = 0; } else { rc = ENODATA; } break; case SMB_CACHE_STATE_NOCACHE: case SMB_CACHE_STATE_DESTROYING: rc = ENODATA; break; default: assert(0); } (void) mutex_unlock(&chandle->ch_mtx); return (rc); } /* * Transfers the cache from REFRESHING to READY state. * * Nothing will happen if the cache is no longer available * or it is being destroyed. * * This function should only be called if smb_cache_refreshing() * has already been invoked. */ void smb_cache_ready(smb_cache_t *chandle) { (void) mutex_lock(&chandle->ch_mtx); switch (chandle->ch_state) { case SMB_CACHE_STATE_REFRESHING: chandle->ch_state = SMB_CACHE_STATE_READY; (void) cond_broadcast(&chandle->ch_cv); break; case SMB_CACHE_STATE_NOCACHE: case SMB_CACHE_STATE_DESTROYING: break; case SMB_CACHE_STATE_READY: default: assert(0); } (void) mutex_unlock(&chandle->ch_mtx); } /* * Lock the cache with the specified mode. * If the cache is in updating state and a read lock is * requested, the lock won't be granted until either the * update is finished or SMB_CACHE_UPDATE_WAIT has passed. * * Whenever a lock is granted, the number of inflight cache * operations is incremented. */ static int smb_cache_lock(smb_cache_t *chandle, int mode) { (void) mutex_lock(&chandle->ch_mtx); switch (chandle->ch_state) { case SMB_CACHE_STATE_NOCACHE: case SMB_CACHE_STATE_DESTROYING: (void) mutex_unlock(&chandle->ch_mtx); return (ENODATA); case SMB_CACHE_STATE_REFRESHING: /* * Read operations should wait until the update * is completed. */ if (mode == SMB_CACHE_RDLOCK) { if (!smb_cache_wait(chandle)) { (void) mutex_unlock(&chandle->ch_mtx); return (ETIME); } } /* FALLTHROUGH */ case SMB_CACHE_STATE_READY: chandle->ch_nops++; break; default: assert(0); } (void) mutex_unlock(&chandle->ch_mtx); /* * Lock has to be taken outside the mutex otherwise * there could be a deadlock */ if (mode == SMB_CACHE_RDLOCK) (void) rw_rdlock(&chandle->ch_cache_lck); else (void) rw_wrlock(&chandle->ch_cache_lck); return (0); } /* * Lock the cache for reading */ static int smb_cache_rdlock(smb_cache_t *chandle) { return (smb_cache_lock(chandle, SMB_CACHE_RDLOCK)); } /* * Lock the cache for modification */ static int smb_cache_wrlock(smb_cache_t *chandle) { return (smb_cache_lock(chandle, SMB_CACHE_WRLOCK)); } /* * Unlock the cache */ static void smb_cache_unlock(smb_cache_t *chandle) { (void) mutex_lock(&chandle->ch_mtx); assert(chandle->ch_nops > 0); chandle->ch_nops--; (void) cond_broadcast(&chandle->ch_cv); (void) mutex_unlock(&chandle->ch_mtx); (void) rw_unlock(&chandle->ch_cache_lck); } /* * Waits for ch_wait seconds if cache is in UPDATING state. * Upon wake up returns true if cache is ready to be used, * otherwise it returns false. */ static boolean_t smb_cache_wait(smb_cache_t *chandle) { timestruc_t to; int err; if (chandle->ch_wait == 0) return (B_TRUE); to.tv_sec = chandle->ch_wait; to.tv_nsec = 0; while (chandle->ch_state == SMB_CACHE_STATE_REFRESHING) { err = cond_reltimedwait(&chandle->ch_cv, &chandle->ch_mtx, &to); if (err == ETIME) break; } return (chandle->ch_state == SMB_CACHE_STATE_READY); } /* * Removes and frees all the cache entries */ static void smb_cache_destroy_nodes(smb_cache_t *chandle) { void *cookie = NULL; smb_cache_node_t *cnode; avl_tree_t *cache; cache = &chandle->ch_cache; while ((cnode = avl_destroy_nodes(cache, &cookie)) != NULL) { if (chandle->ch_free) chandle->ch_free(cnode->cn_data); free(cnode); } } /* * 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. * Copyright 2020-2023 RackTop Systems, Inc. */ /* * CIFS configuration management library */ /* * Checking for things like unsupportable parameter combinations are * the responsibility of callers of these functions. Example include: * trying to set min_protocol above max_protocol, or requiring encryption * with an allowed protocol range that can't support it. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef struct smb_cfg_param { smb_cfg_id_t sc_id; char *sc_name; int sc_type; uint32_t sc_flags; } smb_cfg_param_t; struct str_val { char *str; uint32_t val; }; /* * config parameter flags */ #define SMB_CF_PROTECTED 0x01 #define SMB_CF_EXEC 0x02 /* idmap SMF fmri and Property Group */ #define IDMAP_FMRI_PREFIX "system/idmap" #define MACHINE_SID "machine_sid" #define MACHINE_UUID "machine_uuid" #define IDMAP_DOMAIN "domain_name" #define IDMAP_PREF_DC "preferred_dc" #define IDMAP_SITE_NAME "site_name" #define IDMAP_PG_NAME "config" #define SMB_SECMODE_WORKGRP_STR "workgroup" #define SMB_SECMODE_DOMAIN_STR "domain" #define SMB_ENC_LEN 1024 #define SMB_DEC_LEN 256 static char *b64_data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static smb_cfg_param_t smb_cfg_table[] = { {SMB_CI_VERSION, "sv_version", SCF_TYPE_ASTRING, 0}, /* Oplock configuration, Kernel Only */ {SMB_CI_OPLOCK_ENABLE, "oplock_enable", SCF_TYPE_BOOLEAN, 0}, /* Autohome configuration */ {SMB_CI_AUTOHOME_MAP, "autohome_map", SCF_TYPE_ASTRING, 0}, /* Domain/PDC configuration */ {SMB_CI_DOMAIN_SID, "domain_sid", SCF_TYPE_ASTRING, 0}, {SMB_CI_DOMAIN_MEMB, "domain_member", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_DOMAIN_NAME, "domain_name", SCF_TYPE_ASTRING, 0}, {SMB_CI_DOMAIN_FQDN, "fqdn", SCF_TYPE_ASTRING, 0}, {SMB_CI_DOMAIN_FOREST, "forest", SCF_TYPE_ASTRING, 0}, {SMB_CI_DOMAIN_GUID, "domain_guid", SCF_TYPE_ASTRING, 0}, {SMB_CI_DOMAIN_SRV, "pdc", SCF_TYPE_ASTRING, 0}, /* WINS configuration */ {SMB_CI_WINS_SRV1, "wins_server_1", SCF_TYPE_ASTRING, 0}, {SMB_CI_WINS_SRV2, "wins_server_2", SCF_TYPE_ASTRING, 0}, {SMB_CI_WINS_EXCL, "wins_exclude", SCF_TYPE_ASTRING, 0}, /* Kmod specific configuration */ {SMB_CI_MAX_WORKERS, "max_workers", SCF_TYPE_INTEGER, 0}, {SMB_CI_MAX_CONNECTIONS, "max_connections", SCF_TYPE_INTEGER, 0}, {SMB_CI_KEEPALIVE, "keep_alive", SCF_TYPE_INTEGER, 0}, {SMB_CI_RESTRICT_ANON, "restrict_anonymous", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_SIGNING_ENABLE, "signing_enabled", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_SIGNING_REQD, "signing_required", SCF_TYPE_BOOLEAN, 0}, /* Kmod tuning configuration */ {SMB_CI_SYNC_ENABLE, "sync_enable", SCF_TYPE_BOOLEAN, 0}, /* SMBd configuration */ {SMB_CI_SECURITY, "security", SCF_TYPE_ASTRING, 0}, {SMB_CI_NETBIOS_ENABLE, "netbios_enable", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_NBSCOPE, "netbios_scope", SCF_TYPE_ASTRING, 0}, {SMB_CI_SYS_CMNT, "system_comment", SCF_TYPE_ASTRING, 0}, {SMB_CI_LM_LEVEL, "lmauth_level", SCF_TYPE_INTEGER, 0}, /* ADS Configuration */ {SMB_CI_ADS_SITE, "ads_site", SCF_TYPE_ASTRING, 0}, /* Dynamic DNS */ {SMB_CI_DYNDNS_ENABLE, "ddns_enable", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_MACHINE_PASSWD, "machine_passwd", SCF_TYPE_ASTRING, SMB_CF_PROTECTED}, {SMB_CI_MACHINE_UUID, "machine_uuid", SCF_TYPE_ASTRING, 0}, {SMB_CI_KPASSWD_SRV, "kpasswd_server", SCF_TYPE_ASTRING, 0}, {SMB_CI_KPASSWD_DOMAIN, "kpasswd_domain", SCF_TYPE_ASTRING, 0}, {SMB_CI_KPASSWD_SEQNUM, "kpasswd_seqnum", SCF_TYPE_INTEGER, 0}, {SMB_CI_NETLOGON_SEQNUM, "netlogon_seqnum", SCF_TYPE_INTEGER, 0}, {SMB_CI_IPV6_ENABLE, "ipv6_enable", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_PRINT_ENABLE, "print_enable", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_MAP, "map", SCF_TYPE_ASTRING, SMB_CF_EXEC}, {SMB_CI_UNMAP, "unmap", SCF_TYPE_ASTRING, SMB_CF_EXEC}, {SMB_CI_DISPOSITION, "disposition", SCF_TYPE_ASTRING, SMB_CF_EXEC}, {SMB_CI_DFS_STDROOT_NUM, "dfs_stdroot_num", SCF_TYPE_INTEGER, 0}, {SMB_CI_TRAVERSE_MOUNTS, "traverse_mounts", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_SMB2_ENABLE_OLD, "smb2_enable", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_INITIAL_CREDITS, "initial_credits", SCF_TYPE_INTEGER, 0}, {SMB_CI_MAXIMUM_CREDITS, "maximum_credits", SCF_TYPE_INTEGER, 0}, {SMB_CI_MAX_PROTOCOL, "max_protocol", SCF_TYPE_ASTRING, 0}, {SMB_CI_ENCRYPT, "encrypt", SCF_TYPE_ASTRING, 0}, {SMB_CI_MIN_PROTOCOL, "min_protocol", SCF_TYPE_ASTRING, 0}, {SMB_CI_BYPASS_TRAVERSE_CHECKING, "bypass_traverse_checking", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_ENCRYPT_CIPHERS, "encrypt_ciphers", SCF_TYPE_ASTRING, 0}, {SMB_CI_NETLOGON_FLAGS, "netlogon_flags", SCF_TYPE_INTEGER, 0}, {SMB_CI_SHORT_NAMES, "short_names", SCF_TYPE_BOOLEAN, 0}, {SMB_CI_MAX_OPENS, "max_opens", SCF_TYPE_INTEGER, 0}, {SMB_CI_SIGN_ALGS, "sign_algs", SCF_TYPE_ASTRING, 0}, /* SMB_CI_MAX */ }; /* * We store the max SMB protocol version in SMF as a string, * (for convenience of svccfg etc) but the programmatic get/set * interfaces use the numeric form. * * The numeric values are as defined in the [MS-SMB2] spec. * except for how we represent "1" (for SMB1) which is an * arbitrary value below SMB2_VERS_BASE. */ static struct str_val smb_versions[] = { { "3.11", SMB_VERS_3_11 }, { "3.02", SMB_VERS_3_02 }, { "3.0", SMB_VERS_3_0 }, { "2.1", SMB_VERS_2_1 }, { "2.002", SMB_VERS_2_002 }, { "1", SMB_VERS_1 }, { NULL, 0 } }; /* * Supported encryption ciphers. */ static struct str_val smb31_encrypt_ciphers[] = { { "aes128-ccm", SMB3_CIPHER_FLAG_AES128_CCM }, /* SMB 3.x */ { "aes128-gcm", SMB3_CIPHER_FLAG_AES128_GCM }, /* SMB 3.1.1 */ { "aes256-ccm", SMB3_CIPHER_FLAG_AES256_CCM }, /* SMB 3.1.1 */ { "aes256-gcm", SMB3_CIPHER_FLAG_AES256_GCM }, /* SMB 3.1.1 */ { "all", SMB3_CIPHER_FLAGS_ALL }, { NULL, 0 } }; /* * Supported signing algorithms. */ static struct str_val smb31_sign_algs[] = { { "aes128-cmac", SMB3_SIGN_FLAG_AES128_CMAC }, /* SMB 3.x */ { "aes128-gmac", SMB3_SIGN_FLAG_AES128_GMAC }, /* SMB 3.1.1 */ { "sha256-hmac", SMB3_SIGN_FLAG_SHA256_HMAC }, /* SMB 2.x */ { "all", SMB3_SIGN_FLAGS_ALL }, { NULL, 0 } }; static smb_cfg_param_t *smb_config_getent(smb_cfg_id_t); static boolean_t smb_is_base64(unsigned char c); static char *smb_base64_encode(char *str_to_encode); static char *smb_base64_decode(char *encoded_str); static int smb_config_get_idmap_preferred_dc(char *, int); static int smb_config_set_idmap_preferred_dc(char *); static int smb_config_get_idmap_site_name(char *, int); static int smb_config_set_idmap_site_name(char *); uint32_t smb_convert_version_str(const char *version) { uint32_t dialect = 0; int i; for (i = 0; smb_versions[i].str != NULL; i++) { if (strcmp(version, smb_versions[i].str) == 0) dialect = smb_versions[i].val; } return (dialect); } char * smb_config_getname(smb_cfg_id_t id) { smb_cfg_param_t *cfg; cfg = smb_config_getent(id); return (cfg->sc_name); } static boolean_t smb_is_base64(unsigned char c) { return (isalnum(c) || (c == '+') || (c == '/')); } /* * smb_base64_encode * * Encode a string using base64 algorithm. * Caller should free the returned buffer when done. */ static char * smb_base64_encode(char *str_to_encode) { int ret_cnt = 0; int i = 0, j = 0; char arr_3[3], arr_4[4]; int len = strlen(str_to_encode); char *ret = malloc(SMB_ENC_LEN); if (ret == NULL) { return (NULL); } while (len--) { arr_3[i++] = *(str_to_encode++); if (i == 3) { arr_4[0] = (arr_3[0] & 0xfc) >> 2; arr_4[1] = ((arr_3[0] & 0x03) << 4) + ((arr_3[1] & 0xf0) >> 4); arr_4[2] = ((arr_3[1] & 0x0f) << 2) + ((arr_3[2] & 0xc0) >> 6); arr_4[3] = arr_3[2] & 0x3f; for (i = 0; i < 4; i++) ret[ret_cnt++] = b64_data[(int)arr_4[i]]; i = 0; } } if (i) { for (j = i; j < 3; j++) arr_3[j] = '\0'; arr_4[0] = (arr_3[0] & 0xfc) >> 2; arr_4[1] = ((arr_3[0] & 0x03) << 4) + ((arr_3[1] & 0xf0) >> 4); arr_4[2] = ((arr_3[1] & 0x0f) << 2) + ((arr_3[2] & 0xc0) >> 6); arr_4[3] = arr_3[2] & 0x3f; for (j = 0; j < (i + 1); j++) ret[ret_cnt++] = b64_data[(int)arr_4[j]]; while (i++ < 3) ret[ret_cnt++] = '='; } ret[ret_cnt++] = '\0'; return (ret); } /* * smb_base64_decode * * Decode using base64 algorithm. * Caller should free the returned buffer when done. */ static char * smb_base64_decode(char *encoded_str) { int len = strlen(encoded_str); int i = 0, j = 0; int en_ind = 0; char arr_4[4], arr_3[3]; int ret_cnt = 0; char *ret = malloc(SMB_DEC_LEN); char *p; if (ret == NULL) { return (NULL); } while (len-- && (encoded_str[en_ind] != '=') && smb_is_base64(encoded_str[en_ind])) { arr_4[i++] = encoded_str[en_ind]; en_ind++; if (i == 4) { for (i = 0; i < 4; i++) { if ((p = strchr(b64_data, arr_4[i])) == NULL) { free(ret); return (NULL); } arr_4[i] = (int)(p - b64_data); } arr_3[0] = (arr_4[0] << 2) + ((arr_4[1] & 0x30) >> 4); arr_3[1] = ((arr_4[1] & 0xf) << 4) + ((arr_4[2] & 0x3c) >> 2); arr_3[2] = ((arr_4[2] & 0x3) << 6) + arr_4[3]; for (i = 0; i < 3; i++) ret[ret_cnt++] = arr_3[i]; i = 0; } } if (i) { for (j = i; j < 4; j++) arr_4[j] = 0; for (j = 0; j < 4; j++) { if ((p = strchr(b64_data, arr_4[j])) == NULL) { free(ret); return (NULL); } arr_4[j] = (int)(p - b64_data); } arr_3[0] = (arr_4[0] << 2) + ((arr_4[1] & 0x30) >> 4); arr_3[1] = ((arr_4[1] & 0xf) << 4) + ((arr_4[2] & 0x3c) >> 2); arr_3[2] = ((arr_4[2] & 0x3) << 6) + arr_4[3]; for (j = 0; j < (i - 1); j++) ret[ret_cnt++] = arr_3[j]; } ret[ret_cnt++] = '\0'; return (ret); } static char * smb_config_getenv_generic(char *name, char *svc_fmri_prefix, char *svc_propgrp) { smb_scfhandle_t *handle; char *value; if ((value = malloc(MAX_VALUE_BUFLEN * sizeof (char))) == NULL) return (NULL); handle = smb_smf_scf_init(svc_fmri_prefix); if (handle == NULL) { free(value); return (NULL); } (void) smb_smf_create_service_pgroup(handle, svc_propgrp); if (smb_smf_get_string_property(handle, name, value, sizeof (char) * MAX_VALUE_BUFLEN) != 0) { smb_smf_scf_fini(handle); free(value); return (NULL); } smb_smf_scf_fini(handle); return (value); } static int smb_config_setenv_generic(char *svc_fmri_prefix, char *svc_propgrp, char *name, char *value) { smb_scfhandle_t *handle = NULL; int rc = 0; handle = smb_smf_scf_init(svc_fmri_prefix); if (handle == NULL) { return (1); } (void) smb_smf_create_service_pgroup(handle, svc_propgrp); if (smb_smf_start_transaction(handle) != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (1); } if (smb_smf_set_string_property(handle, name, value) != SMBD_SMF_OK) rc = 1; if (smb_smf_end_transaction(handle) != SMBD_SMF_OK) rc = 1; smb_smf_scf_fini(handle); return (rc); } /* * smb_config_getstr * * Fetch the specified string configuration item from SMF */ int smb_config_getstr(smb_cfg_id_t id, char *cbuf, int bufsz) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; char *pg; char protbuf[SMB_ENC_LEN]; char *tmp; *cbuf = '\0'; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_ASTRING); if (id == SMB_CI_ADS_SITE) return (smb_config_get_idmap_site_name(cbuf, bufsz)); if (id == SMB_CI_DOMAIN_SRV) return (smb_config_get_idmap_preferred_dc(cbuf, bufsz)); handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); if (cfg->sc_flags & SMB_CF_PROTECTED) { if ((rc = smb_smf_create_service_pgroup(handle, SMBD_PROTECTED_PG_NAME)) != SMBD_SMF_OK) goto error; if ((rc = smb_smf_get_string_property(handle, cfg->sc_name, protbuf, sizeof (protbuf))) != SMBD_SMF_OK) goto error; if (*protbuf != '\0') { tmp = smb_base64_decode(protbuf); (void) strlcpy(cbuf, tmp, bufsz); free(tmp); } } else { pg = (cfg->sc_flags & SMB_CF_EXEC) ? SMBD_EXEC_PG_NAME : SMBD_PG_NAME; rc = smb_smf_create_service_pgroup(handle, pg); if (rc == SMBD_SMF_OK) rc = smb_smf_get_string_property(handle, cfg->sc_name, cbuf, bufsz); } error: smb_smf_scf_fini(handle); return (rc); } /* * Translate the value of an astring SMF property into a binary * IP address. If the value is neither a valid IPv4 nor IPv6 * address, attempt to look it up as a hostname using the * configured address type. */ int smb_config_getip(smb_cfg_id_t sc_id, smb_inaddr_t *ipaddr) { int rc, error; int a_family; char ipstr[MAXHOSTNAMELEN]; struct hostent *h; smb_cfg_param_t *cfg; if (ipaddr == NULL) return (SMBD_SMF_INVALID_ARG); bzero(ipaddr, sizeof (smb_inaddr_t)); rc = smb_config_getstr(sc_id, ipstr, sizeof (ipstr)); if (rc == SMBD_SMF_OK) { if (*ipstr == '\0') return (SMBD_SMF_INVALID_ARG); if (inet_pton(AF_INET, ipstr, &ipaddr->a_ipv4) == 1) { ipaddr->a_family = AF_INET; return (SMBD_SMF_OK); } if (inet_pton(AF_INET6, ipstr, &ipaddr->a_ipv6) == 1) { ipaddr->a_family = AF_INET6; return (SMBD_SMF_OK); } /* * The value is neither an IPv4 nor IPv6 address; * so check if it's a hostname. */ a_family = smb_config_getbool(SMB_CI_IPV6_ENABLE) ? AF_INET6 : AF_INET; h = getipnodebyname(ipstr, a_family, AI_DEFAULT, &error); if (h != NULL) { bcopy(*(h->h_addr_list), &ipaddr->a_ip, h->h_length); ipaddr->a_family = a_family; freehostent(h); rc = SMBD_SMF_OK; } else { cfg = smb_config_getent(sc_id); syslog(LOG_ERR, "smbd/%s: %s unable to get %s " "address: %d", cfg->sc_name, ipstr, a_family == AF_INET ? "IPv4" : "IPv6", error); rc = SMBD_SMF_INVALID_ARG; } } return (rc); } /* * smb_config_getnum * * Returns the value of a numeric config param. */ int smb_config_getnum(smb_cfg_id_t id, int64_t *cint) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; *cint = 0; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_INTEGER); handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); rc = smb_smf_create_service_pgroup(handle, SMBD_PG_NAME); if (rc == SMBD_SMF_OK) rc = smb_smf_get_integer_property(handle, cfg->sc_name, cint); smb_smf_scf_fini(handle); return (rc); } /* * smb_config_getbool * * Returns the value of a boolean config param. */ boolean_t smb_config_getbool(smb_cfg_id_t id) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; uint8_t vbool; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_BOOLEAN); handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (B_FALSE); rc = smb_smf_create_service_pgroup(handle, SMBD_PG_NAME); if (rc == SMBD_SMF_OK) rc = smb_smf_get_boolean_property(handle, cfg->sc_name, &vbool); smb_smf_scf_fini(handle); return ((rc == SMBD_SMF_OK) ? (vbool == 1) : B_FALSE); } /* * smb_config_get * * This function returns the value of the requested config * iterm regardless of its type in string format. This should * be used when the config item type is not known by the caller. */ int smb_config_get(smb_cfg_id_t id, char *cbuf, int bufsz) { smb_cfg_param_t *cfg; int64_t cint; int rc; cfg = smb_config_getent(id); switch (cfg->sc_type) { case SCF_TYPE_ASTRING: return (smb_config_getstr(id, cbuf, bufsz)); case SCF_TYPE_INTEGER: rc = smb_config_getnum(id, &cint); if (rc == SMBD_SMF_OK) (void) snprintf(cbuf, bufsz, "%lld", cint); return (rc); case SCF_TYPE_BOOLEAN: if (smb_config_getbool(id)) (void) strlcpy(cbuf, "true", bufsz); else (void) strlcpy(cbuf, "false", bufsz); return (SMBD_SMF_OK); } return (SMBD_SMF_INVALID_ARG); } /* * smb_config_setstr * * Set the specified config param with the given * value. */ int smb_config_setstr(smb_cfg_id_t id, char *value) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; boolean_t protected; char *tmp = NULL; char *pg; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_ASTRING); if (id == SMB_CI_ADS_SITE) return (smb_config_set_idmap_site_name(value)); if (id == SMB_CI_DOMAIN_SRV) return (smb_config_set_idmap_preferred_dc(value)); protected = B_FALSE; switch (cfg->sc_flags) { case SMB_CF_PROTECTED: protected = B_TRUE; pg = SMBD_PROTECTED_PG_NAME; break; case SMB_CF_EXEC: pg = SMBD_EXEC_PG_NAME; break; default: pg = SMBD_PG_NAME; break; } handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); rc = smb_smf_create_service_pgroup(handle, pg); if (rc == SMBD_SMF_OK) rc = smb_smf_start_transaction(handle); if (rc != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (rc); } if (protected && value && (*value != '\0')) { if ((tmp = smb_base64_encode(value)) == NULL) { (void) smb_smf_end_transaction(handle); smb_smf_scf_fini(handle); return (SMBD_SMF_NO_MEMORY); } value = tmp; } rc = smb_smf_set_string_property(handle, cfg->sc_name, value); free(tmp); (void) smb_smf_end_transaction(handle); smb_smf_scf_fini(handle); return (rc); } /* * smb_config_setnum * * Sets a numeric configuration iterm */ int smb_config_setnum(smb_cfg_id_t id, int64_t value) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_INTEGER); handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); rc = smb_smf_create_service_pgroup(handle, SMBD_PG_NAME); if (rc == SMBD_SMF_OK) rc = smb_smf_start_transaction(handle); if (rc != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (rc); } rc = smb_smf_set_integer_property(handle, cfg->sc_name, value); (void) smb_smf_end_transaction(handle); smb_smf_scf_fini(handle); return (rc); } /* * smb_config_setbool * * Sets a boolean configuration iterm */ int smb_config_setbool(smb_cfg_id_t id, boolean_t value) { smb_scfhandle_t *handle; smb_cfg_param_t *cfg; int rc = SMBD_SMF_OK; cfg = smb_config_getent(id); assert(cfg->sc_type == SCF_TYPE_BOOLEAN); handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); rc = smb_smf_create_service_pgroup(handle, SMBD_PG_NAME); if (rc == SMBD_SMF_OK) rc = smb_smf_start_transaction(handle); if (rc != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (rc); } rc = smb_smf_set_boolean_property(handle, cfg->sc_name, value); (void) smb_smf_end_transaction(handle); smb_smf_scf_fini(handle); return (rc); } /* * smb_config_set * * This function sets the value of the specified config * iterm regardless of its type in string format. This should * be used when the config item type is not known by the caller. */ int smb_config_set(smb_cfg_id_t id, char *value) { smb_cfg_param_t *cfg; int64_t cint; cfg = smb_config_getent(id); switch (cfg->sc_type) { case SCF_TYPE_ASTRING: return (smb_config_setstr(id, value)); case SCF_TYPE_INTEGER: cint = atoi(value); return (smb_config_setnum(id, cint)); case SCF_TYPE_BOOLEAN: return (smb_config_setbool(id, strcasecmp(value, "true") == 0)); } return (SMBD_SMF_INVALID_ARG); } int smb_config_get_debug() { int64_t val64; int val = 0; /* default */ smb_scfhandle_t *handle = NULL; handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) { return (val); } if (smb_smf_create_service_pgroup(handle, SMBD_PG_NAME) != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (val); } if (smb_smf_get_integer_property(handle, "debug", &val64) != 0) { smb_smf_scf_fini(handle); return (val); } val = (int)val64; smb_smf_scf_fini(handle); return (val); } uint8_t smb_config_get_fg_flag() { uint8_t run_fg = 0; /* Default is to run in daemon mode */ smb_scfhandle_t *handle = NULL; handle = smb_smf_scf_init(SMBD_FMRI_PREFIX); if (handle == NULL) { return (run_fg); } if (smb_smf_create_service_pgroup(handle, SMBD_PG_NAME) != SMBD_SMF_OK) { smb_smf_scf_fini(handle); return (run_fg); } if (smb_smf_get_boolean_property(handle, "run_fg", &run_fg) != 0) { smb_smf_scf_fini(handle); return (run_fg); } smb_smf_scf_fini(handle); return (run_fg); } /* * smb_config_get_ads_enable * * Returns value of the "config/use_ads" parameter * from the IDMAP SMF configuration repository. * */ boolean_t smb_config_get_ads_enable(void) { smb_scfhandle_t *handle = NULL; uint8_t vbool; int rc = 0; handle = smb_smf_scf_init(IDMAP_FMRI_PREFIX); if (handle == NULL) return (B_FALSE); rc = smb_smf_create_service_pgroup(handle, IDMAP_PG_NAME); if (rc == SMBD_SMF_OK) rc = smb_smf_get_boolean_property(handle, "use_ads", &vbool); smb_smf_scf_fini(handle); return ((rc == SMBD_SMF_OK) ? (vbool == 1) : B_TRUE); } /* * smb_config_get_localsid * * Returns value of the "config/machine_sid" parameter * from the IDMAP SMF configuration repository. * Result is allocated; caller should free. */ char * smb_config_get_localsid(void) { return (smb_config_getenv_generic(MACHINE_SID, IDMAP_FMRI_PREFIX, IDMAP_PG_NAME)); } /* * smb_config_get_localuuid * * Returns value of the "config/machine_uuid" parameter * from the IDMAP SMF configuration repository. * */ int smb_config_get_localuuid(uuid_t uu) { char *s; int rc = 0; uuid_clear(uu); s = smb_config_getenv_generic(MACHINE_UUID, IDMAP_FMRI_PREFIX, IDMAP_PG_NAME); if (s == NULL) return (-1); if (uuid_parse(s, uu) < 0) rc = -1; free(s); return (rc); } static int smb_config_get_idmap_preferred_dc(char *cbuf, int bufsz) { char *s; int len, rc = -1; s = smb_config_getenv_generic(IDMAP_PREF_DC, IDMAP_FMRI_PREFIX, IDMAP_PG_NAME); if (s != NULL) { len = strlcpy(cbuf, s, bufsz); if (len < bufsz) rc = 0; free(s); } return (rc); } static int smb_config_set_idmap_preferred_dc(char *value) { return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME, IDMAP_PREF_DC, value)); } static int smb_config_get_idmap_site_name(char *cbuf, int bufsz) { char *s; int len, rc = -1; s = smb_config_getenv_generic(IDMAP_SITE_NAME, IDMAP_FMRI_PREFIX, IDMAP_PG_NAME); if (s != NULL) { len = strlcpy(cbuf, s, bufsz); if (len < bufsz) rc = 0; free(s); } return (rc); } static int smb_config_set_idmap_site_name(char *value) { return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME, IDMAP_SITE_NAME, value)); } /* * smb_config_set_idmap_domain * * Set the "config/domain_name" parameter from IDMAP SMF repository. */ int smb_config_set_idmap_domain(char *value) { return (smb_config_setenv_generic(IDMAP_FMRI_PREFIX, IDMAP_PG_NAME, IDMAP_DOMAIN, value)); } /* * smb_config_refresh_idmap * * Refresh IDMAP SMF service after making changes to its configuration. */ int smb_config_refresh_idmap(void) { char instance[32]; (void) snprintf(instance, sizeof (instance), "%s:default", IDMAP_FMRI_PREFIX); return (smf_refresh_instance(instance)); } int smb_config_secmode_fromstr(char *secmode) { if (secmode == NULL) return (SMB_SECMODE_WORKGRP); if (strcasecmp(secmode, SMB_SECMODE_DOMAIN_STR) == 0) return (SMB_SECMODE_DOMAIN); return (SMB_SECMODE_WORKGRP); } char * smb_config_secmode_tostr(int secmode) { if (secmode == SMB_SECMODE_DOMAIN) return (SMB_SECMODE_DOMAIN_STR); return (SMB_SECMODE_WORKGRP_STR); } int smb_config_get_secmode() { char p[16]; (void) smb_config_getstr(SMB_CI_SECURITY, p, sizeof (p)); return (smb_config_secmode_fromstr(p)); } int smb_config_set_secmode(int secmode) { char *p; p = smb_config_secmode_tostr(secmode); return (smb_config_setstr(SMB_CI_SECURITY, p)); } void smb_config_getdomaininfo(char *domain, char *fqdn, char *sid, char *forest, char *guid) { if (domain) (void) smb_config_getstr(SMB_CI_DOMAIN_NAME, domain, NETBIOS_NAME_SZ); if (fqdn) (void) smb_config_getstr(SMB_CI_DOMAIN_FQDN, fqdn, MAXHOSTNAMELEN); if (sid) (void) smb_config_getstr(SMB_CI_DOMAIN_SID, sid, SMB_SID_STRSZ); if (forest) (void) smb_config_getstr(SMB_CI_DOMAIN_FOREST, forest, MAXHOSTNAMELEN); if (guid) (void) smb_config_getstr(SMB_CI_DOMAIN_GUID, guid, UUID_PRINTABLE_STRING_LENGTH); } void smb_config_setdomaininfo(char *domain, char *fqdn, char *sid, char *forest, char *guid) { if (domain) (void) smb_config_setstr(SMB_CI_DOMAIN_NAME, domain); if (fqdn) (void) smb_config_setstr(SMB_CI_DOMAIN_FQDN, fqdn); if (sid) (void) smb_config_setstr(SMB_CI_DOMAIN_SID, sid); if (forest) (void) smb_config_setstr(SMB_CI_DOMAIN_FOREST, forest); if (guid) (void) smb_config_setstr(SMB_CI_DOMAIN_GUID, guid); } /* * The version stored in SMF in string format as N.N where * N is a number defined by Microsoft. The first number represents * the major version and the second number is the minor version. * Current defined values can be found here in 'ver_table'. * * This function reads the SMF string value and converts it to * two numbers returned in the given 'version' structure. * Current default version number is 5.0 which is for Windows 2000. */ void smb_config_get_version(smb_version_t *version) { smb_version_t tmpver; char verstr[SMB_VERSTR_LEN]; char *p; int rc, i; static smb_version_t ver_table [] = { { 0, SMB_MAJOR_NT, SMB_MINOR_NT, 1381, 0 }, { 0, SMB_MAJOR_2000, SMB_MINOR_2000, 2195, 0 }, { 0, SMB_MAJOR_XP, SMB_MINOR_XP, 2196, 0 }, { 0, SMB_MAJOR_2003, SMB_MINOR_2003, 2196, 0 }, { 0, SMB_MAJOR_VISTA, SMB_MINOR_VISTA, 6000, 0 }, { 0, SMB_MAJOR_2008, SMB_MINOR_2008, 6000, 0 }, { 0, SMB_MAJOR_2008R2, SMB_MINOR_2008R2, 7007, 0 }, { 0, SMB_MAJOR_7, SMB_MINOR_7, 7007, 0 } }; *version = ver_table[1]; version->sv_size = sizeof (smb_version_t); rc = smb_config_getstr(SMB_CI_VERSION, verstr, sizeof (verstr)); if (rc != SMBD_SMF_OK) return; if ((p = strchr(verstr, '.')) == NULL) return; *p = '\0'; tmpver.sv_major = (uint8_t)atoi(verstr); tmpver.sv_minor = (uint8_t)atoi(p + 1); for (i = 0; i < sizeof (ver_table)/sizeof (ver_table[0]); ++i) { if ((tmpver.sv_major == ver_table[i].sv_major) && (tmpver.sv_minor == ver_table[i].sv_minor)) { *version = ver_table[i]; version->sv_size = sizeof (smb_version_t); break; } } } /* * Reads share exec script properties */ uint32_t smb_config_get_execinfo(char *map, char *unmap, size_t bufsz) { char buf[MAXPATHLEN]; uint32_t flags = 0; if (map == NULL) { map = buf; bufsz = MAXPATHLEN; } *map = '\0'; (void) smb_config_getstr(SMB_CI_MAP, map, bufsz); if (*map != '\0') flags |= SMB_EXEC_MAP; if (unmap == NULL) { unmap = buf; bufsz = MAXPATHLEN; } *unmap = '\0'; (void) smb_config_getstr(SMB_CI_UNMAP, unmap, bufsz); if (*unmap != '\0') flags |= SMB_EXEC_UNMAP; *buf = '\0'; (void) smb_config_getstr(SMB_CI_DISPOSITION, buf, sizeof (buf)); if (*buf != '\0') if (strcasecmp(buf, SMB_EXEC_DISP_TERMINATE) == 0) flags |= SMB_EXEC_TERM; return (flags); } static smb_cfg_param_t * smb_config_getent(smb_cfg_id_t id) { int i; for (i = 0; i < SMB_CI_MAX; i++) if (smb_cfg_table[i].sc_id == id) return (&smb_cfg_table[id]); assert(0); return (NULL); } static uint32_t smb_config_get_protocol(smb_cfg_id_t id, char *name, uint32_t default_val) { char str[SMB_VERSTR_LEN]; int rc; uint32_t val; rc = smb_config_getstr(id, str, sizeof (str)); if (rc == SMBD_SMF_OK) { val = smb_convert_version_str(str); if (val != 0) return (val); if (str[0] != '\0') { syslog(LOG_ERR, "smbd/%s value invalid: %s", name, str); } } return (default_val); } /* * If the service properties min_protocol and max_protocol are empty values, * the built-in defaults allow clients to use any supported protocol version. * Policy choices (such as "let's disable SMB1") should be implemented by * setting values in the SMB service, either via the service manifest (if a * distribution policy) or via svccfg/svcprop (if a local policy). * * max_protocol_default should be the highest implemented protocol version. * See also $UTS/common/fs/smbsrv/smb2_negotiate.c */ uint32_t max_protocol_default = SMB_VERS_3_11; uint32_t smb_config_get_max_protocol(void) { uint32_t max; max = smb_config_get_protocol(SMB_CI_MAX_PROTOCOL, "max_protocol", max_protocol_default); return (max); } /* * See comment above max_protocol_default */ uint32_t min_protocol_default = SMB_VERS_1; uint32_t smb_config_get_min_protocol(void) { uint32_t min; min = smb_config_get_protocol(SMB_CI_MIN_PROTOCOL, "min_protocol", min_protocol_default); return (min); } /* * Convert a delineated list of strings to a bitmask. * Returns mask or -1 for errors. * * Note this is used both below and in libshare_smb * for validation of new setting. */ /* Buffer large enough to hold all current flag strings. */ #define SMB_STRFLAGS_MAXLEN 64 static int smb_convert_str2flags(const char *value, const struct str_val *table) { const char *sep = ",:"; char buf[SMB_STRFLAGS_MAXLEN]; char *last; char *cn; const struct str_val *sv; int flags = 0; if (value == NULL) return (-1); if (strlen(value) >= SMB_STRFLAGS_MAXLEN) return (-1); (void) strlcpy(buf, value, sizeof (buf)); cn = strtok_r(buf, sep, &last); while (cn != NULL) { boolean_t valid = B_FALSE; /* # of entries is small - don't care about O(n2) */ for (sv = table; sv->str != NULL; sv++) { if (strcmp(cn, sv->str) == 0) { flags |= sv->val; valid = B_TRUE; } } if (!valid) return (-1); cn = strtok_r(NULL, sep, &last); } return (flags); } int smb_convert_encrypt_ciphers(const char *value) { return (smb_convert_str2flags(value, smb31_encrypt_ciphers)); } int smb_convert_signing_algs(const char *value) { return (smb_convert_str2flags(value, smb31_sign_algs)); } /* * Return a bitmask indicating enabled cipher algorithms. * If the config does not have at least one known cipher, * that's a configuration error, so just enable all. */ uint32_t smb_config_get_encrypt_ciphers(void) { char buf[SMB_STRFLAGS_MAXLEN]; int ciphers = 0; if (smb_config_getstr(SMB_CI_ENCRYPT_CIPHERS, buf, sizeof (buf)) != SMBD_SMF_OK) buf[0] = '\0'; ciphers = smb_convert_encrypt_ciphers(buf); if (ciphers <= 0) ciphers = SMB3_CIPHER_FLAGS_ALL; return ((uint32_t)ciphers); } /* * Note: SMB3_SIGN_FLAG_SHA256_HMAC is always available, * because SMB 2 does not negotiate signing algorithms. */ const int smb_default_signing_algs = SMB3_SIGN_FLAG_AES128_CMAC | SMB3_SIGN_FLAG_AES128_GMAC; uint32_t smb_config_get_signing_algs(void) { char buf[SMB_STRFLAGS_MAXLEN]; int algs = 0; if (smb_config_getstr(SMB_CI_SIGN_ALGS, buf, sizeof (buf)) != SMBD_SMF_OK) buf[0] = '\0'; algs = smb_convert_signing_algs(buf); if (algs <= 0) algs = smb_default_signing_algs; return ((uint32_t)algs); } /* * Run once at startup convert old SMF settings to current. */ void smb_config_upgrade(void) { } smb_cfg_val_t smb_config_get_require(smb_cfg_id_t id) { int rc; char str[sizeof ("required")]; rc = smb_config_getstr(id, str, sizeof (str)); if (rc != SMBD_SMF_OK) return (SMB_CONFIG_DISABLED); if (strncmp(str, "required", sizeof (str)) == 0) return (SMB_CONFIG_REQUIRED); if (strncmp(str, "enabled", sizeof (str)) == 0) return (SMB_CONFIG_ENABLED); return (SMB_CONFIG_DISABLED); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2012 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include static void smb_initlmkey(unsigned char *keyin, unsigned char *keyout); /* * randomize * * Randomize the contents of the specified buffer. */ void randomize(char *data, unsigned len) { char *p = data; if (pkcs11_get_random(data, len) == 0) return; /* * Implement a "fall back", because current callers * don't expect an error from this. In practice, * we never use this fall back. */ while (len--) { *p++ = (random() >> 24); } } /* * smb_auth_md4 * * Compute an MD4 digest. */ int smb_auth_md4(unsigned char *result, unsigned char *input, int length) { MD4_CTX md4_context; MD4Init(&md4_context); MD4Update(&md4_context, input, length); MD4Final(result, &md4_context); return (SMBAUTH_SUCCESS); } int smb_auth_hmac_md5(unsigned char *data, int data_len, unsigned char *key, int key_len, unsigned char *digest) { CK_RV rv; CK_MECHANISM mechanism; CK_OBJECT_HANDLE hKey; CK_SESSION_HANDLE hSession; CK_ULONG diglen = MD_DIGEST_LEN; mechanism.mechanism = CKM_MD5_HMAC; mechanism.pParameter = 0; mechanism.ulParameterLen = 0; rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); if (rv != CKR_OK) { return (SMBAUTH_FAILURE); } rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, key, key_len, &hKey); if (rv != CKR_OK) { (void) C_CloseSession(hSession); return (SMBAUTH_FAILURE); } /* Initialize the digest operation in the session */ rv = C_SignInit(hSession, &mechanism, hKey); if (rv != CKR_OK) { (void) C_DestroyObject(hSession, hKey); (void) C_CloseSession(hSession); return (SMBAUTH_FAILURE); } rv = C_SignUpdate(hSession, (CK_BYTE_PTR)data, data_len); if (rv != CKR_OK) { (void) C_DestroyObject(hSession, hKey); (void) C_CloseSession(hSession); return (SMBAUTH_FAILURE); } rv = C_SignFinal(hSession, (CK_BYTE_PTR)digest, &diglen); if (rv != CKR_OK) { (void) C_DestroyObject(hSession, hKey); (void) C_CloseSession(hSession); return (SMBAUTH_FAILURE); } (void) C_DestroyObject(hSession, hKey); (void) C_CloseSession(hSession); if (diglen != MD_DIGEST_LEN) { return (SMBAUTH_FAILURE); } return (SMBAUTH_SUCCESS); } int smb_auth_DES(unsigned char *Result, int ResultLen, unsigned char *Key, int KeyLen, unsigned char *Data, int DataLen) { CK_RV rv; CK_MECHANISM mechanism; CK_OBJECT_HANDLE hKey; CK_SESSION_HANDLE hSession; CK_ULONG ciphertext_len; uchar_t des_key[8]; int error = 0; int K, D; int k, d; /* * Calculate proper number of iterations. * Known call cases include: * ResultLen=16, KeyLen=14, DataLen=8 * ResultLen=24, KeyLen=21, DataLen=8 * ResultLen=16, KeyLen=14, DataLen=16 */ K = KeyLen / 7; D = DataLen / 8; if ((KeyLen % 7) || (DataLen % 8)) return (EINVAL); if (K == 0 || D == 0) return (EINVAL); if (ResultLen < (K * 8)) return (EINVAL); /* * Use SUNW convenience function to initialize the cryptoki * library, and open a session with a slot that supports * the mechanism we plan on using. */ mechanism.mechanism = CKM_DES_ECB; mechanism.pParameter = NULL; mechanism.ulParameterLen = 0; rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); if (rv != CKR_OK) { return (SMBAUTH_FAILURE); } for (d = k = 0; k < K; k++, d++) { /* Cycle the input again, as necessary. */ if (d == D) d = 0; smb_initlmkey(&Key[k * 7], des_key); rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, des_key, 8, &hKey); if (rv != CKR_OK) { error = 1; goto exit_session; } /* Initialize the encryption operation in the session */ rv = C_EncryptInit(hSession, &mechanism, hKey); if (rv != CKR_OK) { error = 1; goto exit_encrypt; } ciphertext_len = 8; /* Read in the data and encrypt this portion */ rv = C_EncryptUpdate(hSession, (CK_BYTE_PTR)Data + (d * 8), 8, (CK_BYTE_PTR)Result + (k * 8), &ciphertext_len); if (rv != CKR_OK) { error = 1; goto exit_encrypt; } (void) C_DestroyObject(hSession, hKey); } goto exit_session; exit_encrypt: (void) C_DestroyObject(hSession, hKey); exit_session: (void) C_CloseSession(hSession); if (error) return (SMBAUTH_FAILURE); return (SMBAUTH_SUCCESS); } /* * See "Netlogon Credential Computation" section of MS-NRPC document. */ static void smb_initlmkey(unsigned char *keyin, unsigned char *keyout) { int i; keyout[0] = keyin[0] >> 0x01; keyout[1] = ((keyin[0] & 0x01) << 6) | (keyin[1] >> 2); keyout[2] = ((keyin[1] & 0x03) << 5) | (keyin[2] >> 3); keyout[3] = ((keyin[2] & 0x07) << 4) | (keyin[3] >> 4); keyout[4] = ((keyin[3] & 0x0f) << 3) | (keyin[4] >> 5); keyout[5] = ((keyin[4] & 0x1f) << 2) | (keyin[5] >> 6); keyout[6] = ((keyin[5] & 0x3f) << 1) | (keyin[6] >> 7); keyout[7] = keyin[6] & 0x7f; for (i = 0; i < 8; i++) keyout[i] = (keyout[i] << 1) & 0xfe; } /* * CKM_RC4 */ int smb_auth_RC4(uchar_t *Result, int ResultLen, uchar_t *Key, int KeyLen, uchar_t *Data, int DataLen) { CK_RV rv; CK_MECHANISM mechanism; CK_OBJECT_HANDLE hKey; CK_SESSION_HANDLE hSession; CK_ULONG ciphertext_len; int error = SMBAUTH_FAILURE; /* * Use SUNW convenience function to initialize the cryptoki * library, and open a session with a slot that supports * the mechanism we plan on using. */ mechanism.mechanism = CKM_RC4; mechanism.pParameter = NULL; mechanism.ulParameterLen = 0; rv = SUNW_C_GetMechSession(mechanism.mechanism, &hSession); if (rv != CKR_OK) { return (SMBAUTH_FAILURE); } rv = SUNW_C_KeyToObject(hSession, mechanism.mechanism, Key, KeyLen, &hKey); if (rv != CKR_OK) goto exit_session; /* Initialize the encryption operation in the session */ rv = C_EncryptInit(hSession, &mechanism, hKey); if (rv != CKR_OK) goto exit_encrypt; ciphertext_len = ResultLen; rv = C_EncryptUpdate(hSession, (CK_BYTE_PTR)Data, DataLen, (CK_BYTE_PTR)Result, &ciphertext_len); if (rv == CKR_OK) error = 0; exit_encrypt: (void) C_DestroyObject(hSession, hKey); exit_session: (void) C_CloseSession(hSession); return (error); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Buffer manipulation routines. These routines can be used to format * data within a data buffer without worrying about overrunning the * buffer. * * A ctxbuf_t structure is used to track the current location within * the buffer. The ctxbuf_init() must be called first to initialize the * context structure. ctxbuf_printf() can then be called to fill the buffer. * ctxbuf_printf will discard any data that would overrun the buffer and * the buffer will always be null terminated. */ #include #include #include /* * smb_ctxbuf_init * * Initialize the buffer context structure. * This must be called before any of the other * buffer routines can be used. * * Returns -1 if invalid parameters, 0 otherwise */ int smb_ctxbuf_init(smb_ctxbuf_t *ctx, unsigned char *buf, size_t buflen) { if (ctx == 0 || buf == 0 || buflen == 0) return (-1); buf[0] = '\0'; ctx->basep = buf; ctx->curp = buf; ctx->endp = &buf[buflen]; return (0); } /* * smb_ctxbuf_len * * Return the amount of data stored in the buffer, * excluding the terminating null character. Similar * to strlen() * * Returns 0 if the ctx is invalid. */ int smb_ctxbuf_len(smb_ctxbuf_t *ctx) { if (ctx == 0 || ctx->basep == 0 || ctx->curp == 0 || ctx->endp == 0) return (0); else /*LINTED E_PTRDIFF_OVERFLOW*/ return (ctx->curp - ctx->basep); } /* * smb_ctxbuf_printf * * Move formatted output (based on fmt string) to the buffer * identified in ctxbuf. Any output characters beyond the buffer * are discarded and a null character is written at the end of the * characters actually written. * * Returns * Always return the number of bytes actually written (excluding the * terminating null). */ int smb_ctxbuf_printf(smb_ctxbuf_t *ctx, const char *fmt, ...) { int n; va_list args; if (ctx == 0 || ctx->basep == 0 || ctx->curp == 0 || ctx->endp == 0) return (-1); va_start(args, fmt); /*LINTED E_PTRDIFF_OVERFLOW*/ n = vsnprintf((char *)ctx->curp, ctx->endp-ctx->curp, fmt, args); ctx->curp += n; va_end(args); /* * return the number of bytes moved into the buffer. */ return (n); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ /* * This file defines the domain environment values and the domain * database interface. The database is a single linked list of * structures containing domain type, name and SID information. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_DOMAINS_FILE "domains" #define SMB_DCACHE_UPDATE_WAIT 45 /* seconds */ /* * Domain cache states */ #define SMB_DCACHE_STATE_NONE 0 #define SMB_DCACHE_STATE_READY 1 #define SMB_DCACHE_STATE_UPDATING 2 #define SMB_DCACHE_STATE_DESTROYING 3 /* * Cache lock modes */ #define SMB_DCACHE_RDLOCK 0 #define SMB_DCACHE_WRLOCK 1 typedef struct smb_domain_cache { list_t dc_cache; rwlock_t dc_cache_lck; uint32_t dc_state; uint32_t dc_nops; mutex_t dc_mtx; cond_t dc_cv; /* domain controller information */ cond_t dc_dci_cv; boolean_t dc_dci_valid; smb_dcinfo_t dc_dci; } smb_domain_cache_t; static smb_domain_cache_t smb_dcache; static uint32_t smb_domain_add(smb_domain_type_t, smb_domain_t *); static uint32_t smb_domain_add_local(void); static uint32_t smb_domain_add_primary(uint32_t); static void smb_domain_unlink(void); static void smb_dcache_create(void); static void smb_dcache_destroy(void); static uint32_t smb_dcache_lock(int); static void smb_dcache_unlock(void); static void smb_dcache_remove(smb_domain_t *); static uint32_t smb_dcache_add(smb_domain_t *); static boolean_t smb_dcache_getdc(smb_dcinfo_t *, boolean_t); static void smb_dcache_setdc(const smb_dcinfo_t *); static boolean_t smb_dcache_wait(void); static uint32_t smb_dcache_updating(void); static void smb_dcache_ready(void); /* * domain cache one time initialization. This function should * only be called during service startup. * * Returns 0 on success and an error code on failure. */ int smb_domain_init(uint32_t secmode) { smb_domain_t di; int rc; smb_dcache_create(); if ((rc = smb_domain_add_local()) != 0) return (rc); bzero(&di, sizeof (di)); smb_domain_set_basic_info(NT_BUILTIN_DOMAIN_SIDSTR, "BUILTIN", "", &di); (void) smb_domain_add(SMB_DOMAIN_BUILTIN, &di); return (smb_domain_add_primary(secmode)); } /* * Destroys the cache upon service termination */ void smb_domain_fini(void) { smb_dcache_destroy(); smb_domain_unlink(); } /* * Add a domain structure to domain cache. There is no checking * for duplicates. */ static uint32_t smb_domain_add(smb_domain_type_t type, smb_domain_t *di) { uint32_t res; if (di == NULL || di->di_sid[0] == '\0') return (SMB_DOMAIN_INVALID_ARG); if ((res = smb_dcache_lock(SMB_DCACHE_WRLOCK)) == SMB_DOMAIN_SUCCESS) { di->di_type = type; res = smb_dcache_add(di); smb_dcache_unlock(); } return (res); } /* * Lookup a domain by its name. The passed name is the NETBIOS or fully * qualified DNS name or non-qualified DNS name. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t smb_domain_lookup_name(char *name, smb_domain_t *di) { boolean_t found = B_FALSE; smb_domain_t *dcnode; char *p; bzero(di, sizeof (smb_domain_t)); if (name == NULL || *name == '\0') return (B_FALSE); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { found = (smb_strcasecmp(dcnode->di_nbname, name, 0) == 0) || (smb_strcasecmp(dcnode->di_fqname, name, 0) == 0); if (found) { if (di) *di = *dcnode; break; } if ((p = strchr(dcnode->di_fqname, '.')) != NULL) { *p = '\0'; found = (smb_strcasecmp(dcnode->di_fqname, name, 0) == 0); *p = '.'; if (found) { if (di) *di = *dcnode; break; } } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Lookup a domain by its SID. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t smb_domain_lookup_sid(smb_sid_t *sid, smb_domain_t *di) { boolean_t found = B_FALSE; smb_domain_t *dcnode; char sidstr[SMB_SID_STRSZ]; bzero(di, sizeof (smb_domain_t)); if (sid == NULL) return (B_FALSE); smb_sid_tostr(sid, sidstr); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { found = (strcmp(dcnode->di_sid, sidstr) == 0); if (found) { if (di) *di = *dcnode; break; } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Lookup a domain by its type. * * If the requested domain is found and given 'di' pointer is not NULL * it'll be filled with the domain information and B_TRUE is returned. * If the caller only needs to check a domain existence it can pass * NULL for 'di' and just check the return value. * * If the domain is not in the cache B_FALSE is returned. */ boolean_t smb_domain_lookup_type(smb_domain_type_t type, smb_domain_t *di) { boolean_t found = B_FALSE; smb_domain_t *dcnode; bzero(di, sizeof (smb_domain_t)); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return (B_FALSE); dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { if (dcnode->di_type == type) { found = B_TRUE; if (di) *di = *dcnode; break; } dcnode = list_next(&smb_dcache.dc_cache, dcnode); } smb_dcache_unlock(); return (found); } /* * Returns primary domain information plus the name of * the selected domain controller. * * Returns TRUE on success. */ boolean_t smb_domain_getinfo(smb_domainex_t *dxi) { boolean_t rv; /* Note: this waits for the dcache lock. */ rv = smb_domain_lookup_type(SMB_DOMAIN_PRIMARY, &dxi->d_primary); if (!rv) { syslog(LOG_ERR, "smb_domain_getinfo: no primary domain"); return (B_FALSE); } /* * The 2nd arg TRUE means this will wait for DC info. * * Note that we do NOT hold the dcache rwlock here * (not even as reader) because we already have what we * need from the dcache (our primary domain) and we don't * want to interfere with the DC locator which will take * the dcache lock as writer to update the domain info. */ rv = smb_dcache_getdc(&dxi->d_dci, B_TRUE); if (!rv) { syslog(LOG_ERR, "smb_domain_getinfo: no DC info"); return (B_FALSE); } return (B_TRUE); } /* * Get the name of the current DC (if any) * Does NOT block. */ void smb_domain_current_dc(smb_dcinfo_t *dci) { (void) smb_dcache_getdc(dci, B_FALSE); } /* * Transfer the cache to updating state. * In this state any request for reading the cache would * be blocked until the update is finished. */ uint32_t smb_domain_start_update(void) { return (smb_dcache_updating()); } /* * Transfer the cache from updating to ready state. */ void smb_domain_end_update(void) { smb_dcache_ready(); } /* * Mark the current domain controller (DC) info invalid * until the DC locator calls smb_domain_update(). */ void smb_domain_bad_dc(void) { (void) mutex_lock(&smb_dcache.dc_mtx); smb_dcache.dc_dci_valid = B_FALSE; (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Updates the cache with given information for the primary * domain, possible trusted domains and the selected domain * controller. * * Before adding the new entries existing entries of type * primary and trusted will be removed from cache. */ void smb_domain_update(smb_domainex_t *dxi) { smb_domain_t *dcnode; int i; if (smb_dcache_lock(SMB_DCACHE_WRLOCK) != SMB_DOMAIN_SUCCESS) return; dcnode = list_head(&smb_dcache.dc_cache); while (dcnode) { if ((dcnode->di_type == SMB_DOMAIN_PRIMARY) || (dcnode->di_type == SMB_DOMAIN_TRUSTED)) { smb_dcache_remove(dcnode); dcnode = list_head(&smb_dcache.dc_cache); } else { dcnode = list_next(&smb_dcache.dc_cache, dcnode); } } dxi->d_primary.di_type = SMB_DOMAIN_PRIMARY; if (smb_dcache_add(&dxi->d_primary) == SMB_DOMAIN_SUCCESS) { for (i = 0, dcnode = dxi->d_trusted.td_domains; i < dxi->d_trusted.td_num; i++, dcnode++) { dcnode->di_type = SMB_DOMAIN_TRUSTED; (void) smb_dcache_add(dcnode); } smb_dcache_setdc(&dxi->d_dci); } smb_dcache_unlock(); } /* * Write the list of domains to /var/run/smb/domains. */ void smb_domain_save(void) { char fname[MAXPATHLEN]; char tag; smb_domain_t *domain; FILE *fp; struct passwd *pwd; struct group *grp; uid_t uid; gid_t gid; (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); if ((fp = fopen(fname, "w")) == NULL) return; pwd = getpwnam("root"); grp = getgrnam("sys"); uid = (pwd == NULL) ? 0 : pwd->pw_uid; gid = (grp == NULL) ? 3 : grp->gr_gid; (void) lockf(fileno(fp), F_LOCK, 0); (void) fchmod(fileno(fp), 0600); (void) fchown(fileno(fp), uid, gid); if (smb_dcache_lock(SMB_DCACHE_RDLOCK) != SMB_DOMAIN_SUCCESS) return; domain = list_head(&smb_dcache.dc_cache); while (domain) { switch (domain->di_type) { case SMB_DOMAIN_PRIMARY: tag = '*'; break; case SMB_DOMAIN_TRUSTED: case SMB_DOMAIN_UNTRUSTED: tag = '-'; break; case SMB_DOMAIN_LOCAL: tag = '.'; break; default: domain = list_next(&smb_dcache.dc_cache, domain); continue; } (void) fprintf(fp, "[%c] [%s] [%s]\n", tag, domain->di_nbname, domain->di_sid); domain = list_next(&smb_dcache.dc_cache, domain); } smb_dcache_unlock(); (void) lockf(fileno(fp), F_ULOCK, 0); (void) fclose(fp); } /* * List the domains in /var/run/smb/domains. */ void smb_domain_show(void) { char buf[MAXPATHLEN]; char *p; FILE *fp; (void) snprintf(buf, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); if ((fp = fopen(buf, "r")) != NULL) { (void) lockf(fileno(fp), F_LOCK, 0); while (fgets(buf, MAXPATHLEN, fp) != NULL) { if ((p = strchr(buf, '\n')) != NULL) *p = '\0'; (void) printf("%s\n", buf); } (void) lockf(fileno(fp), F_ULOCK, 0); (void) fclose(fp); } } void smb_domain_set_basic_info(char *sid, char *nb_domain, char *fq_domain, smb_domain_t *di) { if (sid == NULL || nb_domain == NULL || fq_domain == NULL || di == NULL) return; (void) strlcpy(di->di_sid, sid, SMB_SID_STRSZ); (void) strlcpy(di->di_nbname, nb_domain, NETBIOS_NAME_SZ); (void) smb_strupr(di->di_nbname); (void) strlcpy(di->di_fqname, fq_domain, MAXHOSTNAMELEN); di->di_binsid = NULL; } void smb_domain_set_dns_info(char *sid, char *nb_domain, char *fq_domain, char *forest, char *guid, smb_domain_t *di) { if (di == NULL || forest == NULL || guid == NULL) return; /* Caller zeros out *di before this. */ smb_domain_set_basic_info(sid, nb_domain, fq_domain, di); (void) strlcpy(di->di_u.di_dns.ddi_forest, forest, MAXHOSTNAMELEN); (void) strlcpy(di->di_u.di_dns.ddi_guid, guid, UUID_PRINTABLE_STRING_LENGTH); } void smb_domain_set_trust_info(char *sid, char *nb_domain, char *fq_domain, uint32_t trust_dir, uint32_t trust_type, uint32_t trust_attrs, smb_domain_t *di) { smb_domain_trust_t *ti; if (di == NULL) return; /* Caller zeros out *di before this. */ di->di_type = SMB_DOMAIN_TRUSTED; ti = &di->di_u.di_trust; smb_domain_set_basic_info(sid, nb_domain, fq_domain, di); ti->dti_trust_direction = trust_dir; ti->dti_trust_type = trust_type; ti->dti_trust_attrs = trust_attrs; } /* * Remove the /var/run/smb/domains file. */ static void smb_domain_unlink(void) { char fname[MAXPATHLEN]; (void) snprintf(fname, MAXPATHLEN, "%s/%s", SMB_VARRUN_DIR, SMB_DOMAINS_FILE); (void) unlink(fname); } /* * Add an entry for the local domain to the domain cache */ static uint32_t smb_domain_add_local(void) { char *lsidstr; char hostname[NETBIOS_NAME_SZ]; char fq_name[MAXHOSTNAMELEN]; smb_domain_t di; if ((lsidstr = smb_config_get_localsid()) == NULL) return (SMB_DOMAIN_NOMACHINE_SID); if (smb_getnetbiosname(hostname, NETBIOS_NAME_SZ) != 0) { free(lsidstr); return (SMB_DOMAIN_NOMACHINE_SID); } bzero(&di, sizeof (di)); *fq_name = '\0'; (void) smb_getfqhostname(fq_name, MAXHOSTNAMELEN); smb_domain_set_basic_info(lsidstr, hostname, fq_name, &di); (void) smb_domain_add(SMB_DOMAIN_LOCAL, &di); free(lsidstr); return (SMB_DOMAIN_SUCCESS); } /* * Add an entry for the primary domain to the domain cache */ static uint32_t smb_domain_add_primary(uint32_t secmode) { char sidstr[SMB_SID_STRSZ]; char fq_name[MAXHOSTNAMELEN]; char nb_name[NETBIOS_NAME_SZ]; smb_domain_t di; int rc; if (secmode != SMB_SECMODE_DOMAIN) return (SMB_DOMAIN_SUCCESS); rc = smb_config_getstr(SMB_CI_DOMAIN_SID, sidstr, sizeof (sidstr)); if (rc != SMBD_SMF_OK) return (SMB_DOMAIN_NODOMAIN_SID); rc = smb_config_getstr(SMB_CI_DOMAIN_NAME, nb_name, NETBIOS_NAME_SZ); if ((rc != SMBD_SMF_OK) || (*nb_name == '\0')) return (SMB_DOMAIN_NODOMAIN_NAME); bzero(&di, sizeof (di)); (void) smb_getfqdomainname(fq_name, MAXHOSTNAMELEN); smb_domain_set_basic_info(sidstr, nb_name, fq_name, &di); (void) smb_domain_add(SMB_DOMAIN_PRIMARY, &di); return (SMB_DOMAIN_SUCCESS); } /* * Initialize the domain cache. * This function does not populate the cache. */ static void smb_dcache_create(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if (smb_dcache.dc_state != SMB_DCACHE_STATE_NONE) { (void) mutex_unlock(&smb_dcache.dc_mtx); return; } list_create(&smb_dcache.dc_cache, sizeof (smb_domain_t), offsetof(smb_domain_t, di_lnd)); smb_dcache.dc_nops = 0; bzero(&smb_dcache.dc_dci, sizeof (smb_dcache.dc_dci)); smb_dcache.dc_dci_valid = B_FALSE; smb_dcache.dc_state = SMB_DCACHE_STATE_READY; (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Removes and frees all the cache entries */ static void smb_dcache_flush(void) { smb_domain_t *di; (void) rw_wrlock(&smb_dcache.dc_cache_lck); while ((di = list_head(&smb_dcache.dc_cache)) != NULL) smb_dcache_remove(di); (void) rw_unlock(&smb_dcache.dc_cache_lck); } /* * Destroys the cache. */ static void smb_dcache_destroy(void) { (void) mutex_lock(&smb_dcache.dc_mtx); if ((smb_dcache.dc_state == SMB_DCACHE_STATE_READY) || (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING)) { smb_dcache.dc_state = SMB_DCACHE_STATE_DESTROYING; while (smb_dcache.dc_nops > 0) (void) cond_wait(&smb_dcache.dc_cv, &smb_dcache.dc_mtx); smb_dcache_flush(); list_destroy(&smb_dcache.dc_cache); smb_dcache.dc_state = SMB_DCACHE_STATE_NONE; } (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Lock the cache with the specified mode. * If the cache is in updating state and a read lock is * requested, the lock won't be granted until either the * update is finished or SMB_DCACHE_UPDATE_WAIT has passed. * * Whenever a lock is granted, the number of inflight cache * operations is incremented. */ static uint32_t smb_dcache_lock(int mode) { (void) mutex_lock(&smb_dcache.dc_mtx); switch (smb_dcache.dc_state) { case SMB_DCACHE_STATE_NONE: case SMB_DCACHE_STATE_DESTROYING: default: (void) mutex_unlock(&smb_dcache.dc_mtx); return (SMB_DOMAIN_INTERNAL_ERR); case SMB_DCACHE_STATE_UPDATING: if (mode == SMB_DCACHE_RDLOCK) { /* * Read operations should wait until the update * is completed. */ if (!smb_dcache_wait()) { (void) mutex_unlock(&smb_dcache.dc_mtx); return (SMB_DOMAIN_INTERNAL_ERR); } } /* FALLTHROUGH */ case SMB_DCACHE_STATE_READY: smb_dcache.dc_nops++; break; } (void) mutex_unlock(&smb_dcache.dc_mtx); /* * Lock has to be taken outside the mutex otherwise * there could be a deadlock */ if (mode == SMB_DCACHE_RDLOCK) (void) rw_rdlock(&smb_dcache.dc_cache_lck); else (void) rw_wrlock(&smb_dcache.dc_cache_lck); return (SMB_DOMAIN_SUCCESS); } /* * Decrement the number of inflight operations and then unlock. */ static void smb_dcache_unlock(void) { (void) mutex_lock(&smb_dcache.dc_mtx); assert(smb_dcache.dc_nops > 0); smb_dcache.dc_nops--; (void) cond_broadcast(&smb_dcache.dc_cv); (void) mutex_unlock(&smb_dcache.dc_mtx); (void) rw_unlock(&smb_dcache.dc_cache_lck); } static uint32_t smb_dcache_add(smb_domain_t *di) { smb_domain_t *dcnode; assert(di->di_type != 0); if ((dcnode = malloc(sizeof (smb_domain_t))) == NULL) return (SMB_DOMAIN_NO_MEMORY); *dcnode = *di; dcnode->di_binsid = smb_sid_fromstr(dcnode->di_sid); if (dcnode->di_binsid == NULL) { free(dcnode); return (SMB_DOMAIN_NO_MEMORY); } list_insert_tail(&smb_dcache.dc_cache, dcnode); return (SMB_DOMAIN_SUCCESS); } static void smb_dcache_remove(smb_domain_t *di) { list_remove(&smb_dcache.dc_cache, di); smb_sid_free(di->di_binsid); free(di); } static void smb_dcache_setdc(const smb_dcinfo_t *dci) { (void) mutex_lock(&smb_dcache.dc_mtx); smb_dcache.dc_dci = *dci; /* struct assignment! */ smb_dcache.dc_dci_valid = B_TRUE; (void) cond_broadcast(&smb_dcache.dc_dci_cv); (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * Get information about our domain controller. If the wait arg * is true, wait for the DC locator to finish before copying. * Returns TRUE on success (have DC info). */ static boolean_t smb_dcache_getdc(smb_dcinfo_t *dci, boolean_t wait) { timestruc_t to; boolean_t rv; int err; to.tv_sec = time(NULL) + SMB_DCACHE_UPDATE_WAIT; to.tv_nsec = 0; (void) mutex_lock(&smb_dcache.dc_mtx); while (wait && !smb_dcache.dc_dci_valid) { err = cond_timedwait(&smb_dcache.dc_dci_cv, &smb_dcache.dc_mtx, &to); if (err == ETIME) break; } *dci = smb_dcache.dc_dci; /* struct assignment! */ rv = smb_dcache.dc_dci_valid; (void) mutex_unlock(&smb_dcache.dc_mtx); return (rv); } /* * Waits for SMB_DCACHE_UPDATE_WAIT seconds if cache is in * UPDATING state. Upon wake up returns true if cache is * ready to be used, otherwise it returns false. */ static boolean_t smb_dcache_wait(void) { timestruc_t to; int err; assert(MUTEX_HELD(&smb_dcache.dc_mtx)); to.tv_sec = time(NULL) + SMB_DCACHE_UPDATE_WAIT; to.tv_nsec = 0; while (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING) { err = cond_timedwait(&smb_dcache.dc_cv, &smb_dcache.dc_mtx, &to); if (err == ETIME) break; } return (smb_dcache.dc_state == SMB_DCACHE_STATE_READY); } /* * Transfers the cache into UPDATING state, this will ensure * any read access to the cache will be stalled until the * update is finished. This is to avoid providing incomplete, * inconsistent or stale information. * * If another thread is already updating the cache, other * callers will wait until cache is no longer in UPDATING * state. The return code is decided based on the new * state of the cache. */ static uint32_t smb_dcache_updating(void) { uint32_t rc; (void) mutex_lock(&smb_dcache.dc_mtx); switch (smb_dcache.dc_state) { case SMB_DCACHE_STATE_READY: smb_dcache.dc_state = SMB_DCACHE_STATE_UPDATING; rc = SMB_DOMAIN_SUCCESS; break; case SMB_DCACHE_STATE_UPDATING: while (smb_dcache.dc_state == SMB_DCACHE_STATE_UPDATING) (void) cond_wait(&smb_dcache.dc_cv, &smb_dcache.dc_mtx); if (smb_dcache.dc_state == SMB_DCACHE_STATE_READY) { smb_dcache.dc_state = SMB_DCACHE_STATE_UPDATING; rc = SMB_DOMAIN_SUCCESS; } else { rc = SMB_DOMAIN_NO_CACHE; } break; case SMB_DCACHE_STATE_NONE: case SMB_DCACHE_STATE_DESTROYING: rc = SMB_DOMAIN_NO_CACHE; break; default: rc = SMB_DOMAIN_SUCCESS; break; } (void) mutex_unlock(&smb_dcache.dc_mtx); return (rc); } /* * Transfers the cache from UPDATING to READY state. * * Nothing will happen if the cache is no longer available * or it is being destroyed. */ static void smb_dcache_ready(void) { (void) mutex_lock(&smb_dcache.dc_mtx); switch (smb_dcache.dc_state) { case SMB_DCACHE_STATE_UPDATING: smb_dcache.dc_state = SMB_DCACHE_STATE_READY; (void) cond_broadcast(&smb_dcache.dc_cv); break; case SMB_DCACHE_STATE_NONE: case SMB_DCACHE_STATE_DESTROYING: break; default: assert(0); } (void) mutex_unlock(&smb_dcache.dc_mtx); } /* * 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. */ #include #include #include #include #include #include #include #include /* * Generic XDR encoder. * * Returns a malloc'd, encoded buffer upon success. * Otherwise, returns NULL. */ char * smb_common_encode(void *data, xdrproc_t proc, size_t *rsize) { XDR xdrs; char *buf; size_t len; if (proc == NULL || data == NULL || rsize == NULL) { syslog(LOG_ERR, "smb_common_encode: invalid parameter"); return (NULL); } len = xdr_sizeof(proc, data); if ((buf = malloc(len)) == NULL) { syslog(LOG_ERR, "smb_common_encode: %m"); *rsize = 0; return (NULL); } xdrmem_create(&xdrs, buf, len, XDR_ENCODE); *rsize = len; if (!proc(&xdrs, data)) { syslog(LOG_DEBUG, "smb_common_encode: encode error"); free(buf); buf = NULL; *rsize = 0; } xdr_destroy(&xdrs); return (buf); } /* * Generic XDR decoder. Ensure that data is non-null and bzero'd. */ int smb_common_decode(char *buf, size_t len, xdrproc_t proc, void *data) { XDR xdrs; int rc = 0; if (data == NULL) return (-1); xdrmem_create(&xdrs, buf, len, XDR_DECODE); if (!proc(&xdrs, data)) rc = -1; xdr_destroy(&xdrs); return (rc); } char * smb_string_encode(char *s, size_t *rsize) { smb_string_t obj; XDR xdrs; char *buf = NULL; size_t len; if ((obj.buf = s) == NULL) { syslog(LOG_DEBUG, "smb_string_encode: invalid param"); goto smb_string_encode_failed; } len = xdr_sizeof(smb_string_xdr, &obj); if ((buf = calloc(len, 1)) == NULL) { syslog(LOG_DEBUG, "smb_string_encode: %m"); goto smb_string_encode_failed; } xdrmem_create(&xdrs, buf, len, XDR_ENCODE); if (!smb_string_xdr(&xdrs, &obj)) { syslog(LOG_DEBUG, "smb_string_encode: encode failed"); xdr_destroy(&xdrs); free(buf); goto smb_string_encode_failed; } xdr_destroy(&xdrs); if (rsize) *rsize = len; return (buf); smb_string_encode_failed: if (rsize) *rsize = 0; return (NULL); } int smb_string_decode(smb_string_t *obj, char *buf, size_t buflen) { XDR xdrs; int rc = 0; xdrmem_create(&xdrs, (const caddr_t)buf, buflen, XDR_DECODE); bzero(obj, sizeof (smb_string_t)); if (!smb_string_xdr(&xdrs, obj)) rc = -1; xdr_destroy(&xdrs); return (rc); } /* * Encode an lsa_account_t into a buffer. */ int lsa_account_encode(lsa_account_t *acct, uint8_t *buf, uint32_t buflen) { XDR xdrs; int rc = 0; xdrmem_create(&xdrs, (const caddr_t)buf, buflen, XDR_ENCODE); if (!lsa_account_xdr(&xdrs, acct)) rc = -1; xdr_destroy(&xdrs); return (rc); } /* * Decode an XDR buffer into an lsa_account_t. */ int lsa_account_decode(lsa_account_t *acct, uint8_t *buf, uint32_t buflen) { XDR xdrs; int rc = 0; xdrmem_create(&xdrs, (const caddr_t)buf, buflen, XDR_DECODE); bzero(acct, sizeof (lsa_account_t)); if (!lsa_account_xdr(&xdrs, acct)) rc = -1; xdr_destroy(&xdrs); return (rc); } bool_t lsa_account_xdr(XDR *xdrs, lsa_account_t *objp) { if (!xdr_uint16_t(xdrs, &objp->a_sidtype)) return (FALSE); if (!xdr_uint32_t(xdrs, &objp->a_status)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->a_domain, MAXNAMELEN, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->a_name, MAXNAMELEN, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->a_sid, SMB_SID_STRSZ, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); return (TRUE); } /* * 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 2019 Nexenta Systems, Inc. All rights reserved. * Copyright 2022 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static int smb_door_call(uint32_t, void *, xdrproc_t, void *, xdrproc_t); static int smb_door_call_private(int, smb_doorarg_t *); static int smb_door_encode(smb_doorarg_t *, uint32_t); static int smb_door_decode(smb_doorarg_t *); static void smb_door_sethdr(smb_doorhdr_t *, uint32_t, uint32_t); static boolean_t smb_door_chkhdr(smb_doorarg_t *, smb_doorhdr_t *); static void smb_door_free(door_arg_t *arg); static int smb_lookup_name_int(const char *name, sid_type_t sidtype, lsa_account_t *acct, int); static int smb_lookup_sid_int(const char *sid, lsa_account_t *acct, int); /* * Given a SID, make a door call to get the associated name. * * Returns 0 if the door call is successful, otherwise -1. * * If 0 is returned, the lookup result will be available in a_status. * NT_STATUS_SUCCESS The SID was mapped to a name. * NT_STATUS_NONE_MAPPED The SID could not be mapped to a name. */ int smb_lookup_sid(const char *sid, lsa_account_t *acct) { return (smb_lookup_sid_int(sid, acct, SMB_DR_LOOKUP_SID)); } /* * Variant of smb_lookup_sid to do a "local-only" lookup. */ int smb_lookup_lsid(const char *sid, lsa_account_t *acct) { return (smb_lookup_sid_int(sid, acct, SMB_DR_LOOKUP_LSID)); } static int smb_lookup_sid_int(const char *sid, lsa_account_t *acct, int dop) { int rc; assert((sid != NULL) && (acct != NULL)); bzero(acct, sizeof (lsa_account_t)); (void) strlcpy(acct->a_sid, sid, SMB_SID_STRSZ); rc = smb_door_call(dop, acct, lsa_account_xdr, acct, lsa_account_xdr); if (rc != 0) syslog(LOG_DEBUG, "smb_lookup_sid: %m"); return (rc); } /* * Given a name, make a door call to get the associated SID. * * Returns 0 if the door call is successful, otherwise -1. * * If 0 is returned, the lookup result will be available in a_status. * NT_STATUS_SUCCESS The name was mapped to a SID. * NT_STATUS_NONE_MAPPED The name could not be mapped to a SID. */ int smb_lookup_name(const char *name, sid_type_t sidtype, lsa_account_t *acct) { return (smb_lookup_name_int(name, sidtype, acct, SMB_DR_LOOKUP_NAME)); } int smb_lookup_lname(const char *name, sid_type_t sidtype, lsa_account_t *acct) { return (smb_lookup_name_int(name, sidtype, acct, SMB_DR_LOOKUP_LNAME)); } static int smb_lookup_name_int(const char *name, sid_type_t sidtype, lsa_account_t *acct, int dop) { char tmp[MAXNAMELEN]; char *dp = NULL; char *np = NULL; int rc; assert((name != NULL) && (acct != NULL)); (void) strlcpy(tmp, name, MAXNAMELEN); smb_name_parse(tmp, &np, &dp); bzero(acct, sizeof (lsa_account_t)); acct->a_sidtype = sidtype; if (dp != NULL && np != NULL) { (void) strlcpy(acct->a_domain, dp, MAXNAMELEN); (void) strlcpy(acct->a_name, np, MAXNAMELEN); } else { (void) strlcpy(acct->a_name, name, MAXNAMELEN); } rc = smb_door_call(dop, acct, lsa_account_xdr, acct, lsa_account_xdr); if (rc != 0) syslog(LOG_DEBUG, "smb_lookup_name: %m"); return (rc); } int smb_join(smb_joininfo_t *jdi, smb_joinres_t *jres) { int rc; rc = smb_door_call(SMB_DR_JOIN, jdi, smb_joininfo_xdr, jres, smb_joinres_xdr); if (rc != 0) { /* * This usually means the SMB service is not running. */ syslog(LOG_DEBUG, "smb_join: %m"); jres->status = NT_STATUS_SERVER_DISABLED; return (rc); } return (0); } /* * Get information about the Domain Controller in the joined resource domain. * * Returns NT status codes. */ uint32_t smb_get_dcinfo(char *namebuf, uint32_t namebuflen, smb_inaddr_t *ipaddr) { smb_string_t dcname; struct hostent *h; int rc; assert((namebuf != NULL) && (namebuflen != 0)); *namebuf = '\0'; bzero(&dcname, sizeof (smb_string_t)); rc = smb_door_call(SMB_DR_GET_DCINFO, NULL, NULL, &dcname, smb_string_xdr); if (rc != 0) { syslog(LOG_DEBUG, "smb_get_dcinfo: %m"); if (dcname.buf) xdr_free(smb_string_xdr, (char *)&dcname); return (NT_STATUS_INTERNAL_ERROR); } if (dcname.buf) { (void) strlcpy(namebuf, dcname.buf, namebuflen); if ((h = smb_gethostbyname(dcname.buf, &rc)) == NULL) { bzero(ipaddr, sizeof (smb_inaddr_t)); } else { (void) memcpy(ipaddr, h->h_addr, h->h_length); ipaddr->a_family = h->h_addrtype; freehostent(h); } xdr_free(smb_string_xdr, (char *)&dcname); } return (NT_STATUS_SUCCESS); } bool_t smb_joininfo_xdr(XDR *xdrs, smb_joininfo_t *objp) { if (!xdr_uint32_t(xdrs, &objp->mode)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->domain_name, MAXHOSTNAMELEN, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->container_name, MAXHOSTNAMELEN, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->domain_username, SMB_USERNAME_MAXLEN + 1, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->domain_passwd, SMB_PASSWD_MAXLEN + 1, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); return (TRUE); } bool_t smb_joinres_xdr(XDR *xdrs, smb_joinres_t *objp) { if (!xdr_uint32_t(xdrs, &objp->status)) return (FALSE); if (!xdr_int(xdrs, &objp->join_err)) return (FALSE); if (!xdr_vector(xdrs, (char *)objp->dc_name, MAXHOSTNAMELEN, sizeof (char), (xdrproc_t)xdr_char)) return (FALSE); return (TRUE); } /* * Parameters: * fqdn (input) - fully-qualified domain name * buf (output) - fully-qualified hostname of the AD server found * by this function. * buflen (input) - length of the 'buf' * * Return: * B_TRUE if an AD server is found. Otherwise, returns B_FALSE; * * The buffer passed in should be big enough to hold a fully-qualified * hostname (MAXHOSTNAMELEN); otherwise, a truncated string will be * returned. On error, an empty string will be returned. */ boolean_t smb_find_ads_server(char *fqdn, char *buf, int buflen) { smb_string_t server; smb_string_t domain; boolean_t found = B_FALSE; int rc; if (fqdn == NULL || buf == NULL) { if (buf) *buf = '\0'; return (B_FALSE); } bzero(&server, sizeof (smb_string_t)); *buf = '\0'; domain.buf = fqdn; rc = smb_door_call(SMB_DR_ADS_FIND_HOST, &domain, smb_string_xdr, &server, smb_string_xdr); if (rc != 0) syslog(LOG_DEBUG, "smb_find_ads_server: %m"); if (server.buf != NULL) { if (*server.buf != '\0') { (void) strlcpy(buf, server.buf, buflen); found = B_TRUE; } xdr_free(smb_string_xdr, (char *)&server); } return (found); } int smb_notify_dc_changed(void) { int rc; rc = smb_door_call(SMB_DR_NOTIFY_DC_CHANGED, NULL, NULL, NULL, NULL); if (rc != 0) { rc = errno; if (rc == 0) rc = EPERM; syslog(LOG_DEBUG, "smb_notify_dc_changed: rc=%d", rc); return (rc); } return (0); } /* * After a successful door call the local door_arg->data_ptr is assigned * to the caller's arg->rbuf so that arg has references to both input and * response buffers, which is required by smb_door_free. * * On success, the object referenced by rsp_data will have been populated * by passing rbuf through the rsp_xdr function. */ static int smb_door_call(uint32_t cmd, void *req_data, xdrproc_t req_xdr, void *rsp_data, xdrproc_t rsp_xdr) { smb_doorarg_t da; int fd; int rc; char *door_name; bzero(&da, sizeof (smb_doorarg_t)); da.da_opcode = cmd; da.da_opname = smb_doorhdr_opname(cmd); da.da_req_xdr = req_xdr; da.da_rsp_xdr = rsp_xdr; da.da_req_data = req_data; da.da_rsp_data = rsp_data; if ((req_data == NULL && req_xdr != NULL) || (rsp_data == NULL && rsp_xdr != NULL)) { errno = EINVAL; syslog(LOG_DEBUG, "smb_door_call[%s]: %m", da.da_opname); return (-1); } door_name = getenv("SMBD_DOOR_NAME"); if (door_name == NULL) door_name = SMBD_DOOR_NAME; if ((fd = open(door_name, O_RDONLY)) < 0) { syslog(LOG_DEBUG, "smb_door_call[%s]: %m", da.da_opname); return (-1); } if (smb_door_encode(&da, cmd) != 0) { syslog(LOG_DEBUG, "smb_door_call[%s]: %m", da.da_opname); (void) close(fd); return (-1); } if (smb_door_call_private(fd, &da) != 0) { syslog(LOG_DEBUG, "smb_door_call[%s]: %m", da.da_opname); smb_door_free(&da.da_arg); (void) close(fd); return (-1); } if ((rc = smb_door_decode(&da)) != 0) syslog(LOG_DEBUG, "smb_door_call[%s]: %m", da.da_opname); smb_door_free(&da.da_arg); (void) close(fd); return (rc); } /* * We use a copy of the door arg because doorfs may change data_ptr * and we want to detect that when freeing the door buffers. After * this call, response data must be referenced via rbuf and rsize. */ static int smb_door_call_private(int fd, smb_doorarg_t *da) { door_arg_t door_arg; int rc; int i; bcopy(&da->da_arg, &door_arg, sizeof (door_arg_t)); for (i = 0; i < SMB_DOOR_CALL_RETRIES; ++i) { errno = 0; if ((rc = door_call(fd, &door_arg)) == 0) break; if (errno != EAGAIN && errno != EINTR) return (-1); } if (rc != 0 || door_arg.data_size == 0 || door_arg.rsize == 0) { if (errno == 0) errno = EIO; return (-1); } da->da_arg.rbuf = door_arg.data_ptr; da->da_arg.rsize = door_arg.rsize; return (rc); } static int smb_door_encode(smb_doorarg_t *da, uint32_t cmd) { XDR xdrs; char *buf; uint32_t buflen; buflen = xdr_sizeof(smb_doorhdr_xdr, &da->da_hdr); if (da->da_req_xdr != NULL) buflen += xdr_sizeof(da->da_req_xdr, da->da_req_data); smb_door_sethdr(&da->da_hdr, cmd, buflen); if ((buf = malloc(buflen)) == NULL) return (-1); xdrmem_create(&xdrs, buf, buflen, XDR_ENCODE); if (!smb_doorhdr_xdr(&xdrs, &da->da_hdr)) { errno = EPROTO; free(buf); xdr_destroy(&xdrs); return (-1); } if (da->da_req_xdr != NULL) { if (!da->da_req_xdr(&xdrs, da->da_req_data)) { errno = EPROTO; free(buf); xdr_destroy(&xdrs); return (-1); } } da->da_arg.data_ptr = buf; da->da_arg.data_size = buflen; da->da_arg.desc_ptr = NULL; da->da_arg.desc_num = 0; da->da_arg.rbuf = buf; da->da_arg.rsize = buflen; xdr_destroy(&xdrs); return (0); } /* * Decode the response in rbuf and rsize. */ static int smb_door_decode(smb_doorarg_t *da) { XDR xdrs; smb_doorhdr_t hdr; char *rbuf = da->da_arg.rbuf; uint32_t rsize = da->da_arg.rsize; if (rbuf == NULL || rsize == 0) { errno = EINVAL; return (-1); } xdrmem_create(&xdrs, rbuf, rsize, XDR_DECODE); if (!smb_doorhdr_xdr(&xdrs, &hdr)) { errno = EPROTO; xdr_destroy(&xdrs); return (-1); } if (!smb_door_chkhdr(da, &hdr)) { errno = EPROTO; xdr_destroy(&xdrs); return (-1); } if (da->da_rsp_xdr != NULL) { if (!da->da_rsp_xdr(&xdrs, da->da_rsp_data)) { errno = EPROTO; xdr_destroy(&xdrs); return (-1); } } xdr_destroy(&xdrs); return (0); } static void smb_door_sethdr(smb_doorhdr_t *hdr, uint32_t cmd, uint32_t datalen) { bzero(hdr, sizeof (smb_doorhdr_t)); hdr->dh_magic = SMB_DOOR_HDR_MAGIC; hdr->dh_flags = SMB_DF_USERSPACE; hdr->dh_op = cmd; hdr->dh_txid = smb_get_txid(); hdr->dh_datalen = datalen; hdr->dh_door_rc = SMB_DOP_NOT_CALLED; } static boolean_t smb_door_chkhdr(smb_doorarg_t *da, smb_doorhdr_t *hdr) { if ((hdr->dh_magic != SMB_DOOR_HDR_MAGIC) || (hdr->dh_op != da->da_hdr.dh_op) || (hdr->dh_txid != da->da_hdr.dh_txid)) { syslog(LOG_DEBUG, "smb_door_chkhdr[%s]: invalid header", da->da_opname); return (B_FALSE); } if (hdr->dh_door_rc != SMB_DOP_SUCCESS) { syslog(LOG_DEBUG, "smb_door_chkhdr[%s]: call status=%d", da->da_opname, hdr->dh_door_rc); return (B_FALSE); } return (B_TRUE); } /* * Free resources allocated for a door call. If the result buffer provided * by the client is too small, doorfs will have allocated a new buffer, * which must be unmapped here. * * This function must be called to free both the argument and result door * buffers regardless of the status of the door call. */ static void smb_door_free(door_arg_t *arg) { if (arg->rbuf && (arg->rbuf != arg->data_ptr)) (void) munmap(arg->rbuf, arg->rsize); free(arg->data_ptr); } /* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Generic hash table library. The hash table is an array of pointers * to items. Hash collisions are handled using linked lists from the * table entries. A handle is associated with each table, which is used * to maintain the hash table. * * +------+ +-------+ +----+ +----+ * |handle|---> |index 0|--->|item|--->|item|---> * | ... | +-------+ +----+ +----+ * | ... | |index 1|---> * +------+ +-------+ +----+ +----+ +----+ * |index 2|--->|item|--->|item|--->|item|---> * +-------+ +----+ +----+ +----+ * | ... |---> * +-------+ * | ... |---> * +-------+ * |index n|---> * +-------+ * */ #include #include #include static size_t ht_default_hash(HT_HANDLE *handle, const char *key); /* * ht_is_power2 * * Inline function to determine if a value is a power of two. This * function is used by the library to validate the table size when * a new table is created. * * Returns 1 if value given is power of two, otherwise returns 0. */ static size_t ht_is_power2(size_t value) { return (((value & (value - 1)) == 0)? 1 : 0); } /* * ht_create_table * * Create a hash table. The table size must be a positive integer and * must be a power of two. The key size must be a positive integer. * For null terminated keys, the key size does not need to include the * null terminating character. The type of key is indicated by the * flags (see hash_table.h). * * The handle and the table are are malloc'd using a single call, to * avoid two allocations. The table is located immediately after the * handle. * * On success a pointer to an opaque handle is returned. Otherwise a * null pointer is returned. */ HT_HANDLE * ht_create_table(size_t table_size, size_t key_size, size_t flags) { HT_HANDLE *ht; size_t msize; size_t i; if ((table_size == 0) || (key_size == 0)) return (NULL); if (ht_is_power2(table_size) == 0) return (NULL); msize = sizeof (HT_HANDLE) + (sizeof (HT_TABLE_ENTRY) * table_size); if ((ht = (HT_HANDLE *)malloc(msize)) == 0) return (NULL); /*LINTED E_BAD_PTR_CAST_ALIGN*/ ht->ht_table = (HT_TABLE_ENTRY *)((char *)ht + sizeof (HT_HANDLE)); ht->ht_table_size = table_size; ht->ht_table_mask = table_size - 1; ht->ht_key_size = key_size; ht->ht_total_items = 0; ht->ht_flags = flags; ht->ht_hash = ht_default_hash; ht->ht_callback = 0; ht->ht_sequence = random(); ht->ht_cmp = ((flags & HTHF_FIXED_KEY) == 0) ? (HT_CMP)strncmp : (HT_CMP)memcmp; for (i = 0; i < table_size; i++) bzero(&ht->ht_table[i], sizeof (HT_TABLE_ENTRY)); return (ht); } /* * ht_destroy_table * * Destroy a hash table. All entries in the table are removed, which * may invoke the callback if it's installed, and the memory is freed. */ void ht_destroy_table(HT_HANDLE *handle) { HT_ITEM *item; HT_ITERATOR iterator; if (handle == 0) return; /* To remove marked entries */ (void) ht_clean_table(handle); while ((item = ht_findfirst(handle, &iterator)) != 0) (void) ht_remove_item(handle, item->hi_key); free(handle); } /* * ht_get_total_items * * Return the total number of items in the table. Returns -1 if the * handle is invalid. */ size_t ht_get_total_items(HT_HANDLE *handle) { if (handle == 0) return ((size_t)-1); return (handle->ht_total_items); } /* * ht_default_hash * * Default hash function to compute the table index (hash value) based * on the specified key. This will identify the location for the * corresponding item in the hash table. The handle and key pointers * should be validated before this function is called. * * Returns the table index location for the item. */ static size_t ht_default_hash(HT_HANDLE *handle, const char *key) { unsigned int hash_ndx = 0; size_t rval; if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) { while (*key) { hash_ndx += *key; ++key; } } else { int key_len = handle->ht_key_size; while (key_len--) { hash_ndx += *key; ++key; } } rval = (hash_ndx * HASH_MESH_VALUE) & handle->ht_table_mask; return (rval); } /* * ht_set_cmpfn * * Replace the current compare function. As the this is function * for comparing items' key, it should not be called while there are * items in the table. */ void ht_set_cmpfn(HT_HANDLE *handle, HT_CMP cmpfn) { if (handle) handle->ht_cmp = cmpfn; } /* * ht_add_item * * Adds an item to a hash table. The hash table is identified by the * handle and the key is used to generate a hashed index. The data * item can be null; it is never dereferenced. We don't check for * duplicates. If duplicate keys are added to the table, the last * item added will be to the front of the duplicate list. * * The table sequence number may be modified here. * * If the item is successfully inserted, a pointer to the item object * is returned. Otherwise a null pointer is returned. */ HT_ITEM * ht_add_item(HT_HANDLE *handle, const char *key, const void *data) { size_t h_index, key_len; size_t msize; HT_ITEM *item; if (handle == 0 || key == 0) return (NULL); if (handle->ht_flags & HTHF_FIXED_KEY) { key_len = handle->ht_key_size; } else { key_len = strlen(key); if (key_len > handle->ht_key_size) return (NULL); /* Include the null terminator */ ++key_len; } msize = key_len + sizeof (HT_ITEM); if ((item = malloc(msize)) == 0) return (NULL); item->hi_key = (char *)item + sizeof (HT_ITEM); (void) memcpy(item->hi_key, key, key_len); item->hi_data = (void *)data; item->hi_flags = 0; h_index = handle->ht_hash(handle, key); /* * Add to the front of the list. */ item->hi_next = handle->ht_table[h_index].he_head; handle->ht_table[h_index].he_head = item; handle->ht_table[h_index].he_count++; handle->ht_total_items++; handle->ht_sequence++; return (item); } /* * ht_replace_item * * Replace an item in a hash table. The item associated with key is removed * using ht_remove_item and a new item is added using ht_add_item. We rely * on parameter validation in ht_remove_item and ht_add_item. * * The table sequence number may be modified here. */ HT_ITEM * ht_replace_item(HT_HANDLE *handle, const char *key, const void *data) { (void) ht_remove_item(handle, key); return (ht_add_item(handle, key, data)); } /* * ht_remove_item * * Remove an item from a hash table. If there are duplicate keys, then the * first key found will be deleted. Note that the data pointer is never * dereferenced. If a callback is installed, it will be invoked and the * return value will be null. Otherwise, the data pointer supplied by the * application will be returned. If there is an error, a null pointer will * be returned. * * The table sequence number may be modified here. */ void * ht_remove_item(HT_HANDLE *handle, const char *key) { size_t h_index; HT_ITEM *cur, *prev; int key_len; void *data = 0; if (handle == 0 || key == 0) return (NULL); if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) key_len = strlen(key) + 1; else key_len = handle->ht_key_size; h_index = handle->ht_hash(handle, key); cur = handle->ht_table[h_index].he_head; prev = 0; while (cur) { if (!(cur->hi_flags & HTIF_MARKED_DELETED) && (handle->ht_cmp(cur->hi_key, key, key_len) == 0)) { /* found key */ if (prev == 0) handle->ht_table[h_index].he_head = cur->hi_next; else prev->hi_next = cur->hi_next; if (handle->ht_callback) handle->ht_callback(cur); else data = cur->hi_data; /* * Since the key and the item were allocated as * a single chunk, we only need one free here. */ free(cur); handle->ht_table[h_index].he_count--; handle->ht_total_items--; handle->ht_sequence++; break; } prev = cur; cur = cur->hi_next; } return (data); } /* * ht_find_item * * Find an item in a hash table. If there are duplicate keys then the * first item found (which will be the last one added) will be returned. * * Returns a pointer to an item. Otherwise returns a null pointer to * indicate an error or that the key didn't match anything in the table. */ HT_ITEM * ht_find_item(HT_HANDLE *handle, const char *key) { size_t h_index; HT_ITEM *cur; int key_len; if (handle == 0 || key == 0) return (NULL); if ((handle->ht_flags & HTHF_FIXED_KEY) == 0) key_len = strlen(key) + 1; else key_len = handle->ht_key_size; h_index = handle->ht_hash(handle, key); cur = handle->ht_table[h_index].he_head; while (cur) { if (!(cur->hi_flags & HTIF_MARKED_DELETED) && (handle->ht_cmp(cur->hi_key, key, key_len) == 0)) return (cur); cur = cur->hi_next; } return (NULL); } /* * ht_register_callback * * Register an application callback function that can be used to process * an item when it is removed from the table, i.e. free any memory * allocated for that data item. * * The previous callback function pointer, which may be null, before * registering the new one. This provides the caller with the option to * restore a previous callback as required. */ HT_CALLBACK ht_register_callback(HT_HANDLE *handle, HT_CALLBACK callback) { HT_CALLBACK old_callback; if (handle == 0) return (NULL); old_callback = handle->ht_callback; handle->ht_callback = callback; return (old_callback); } /* * ht_clean_table * * This function removes all the items that are marked for deletion. Note * that this will invoke the callback, if one has been installed. If this * call is used, the callback mechanism is the only way for an application * to free the item data if it was dynamically allocated. * * The table sequence number may be modified here. * * Returns 0 if the handle is valid; otherwise returns -1. */ size_t ht_clean_table(HT_HANDLE *handle) { size_t i; HT_ITEM *cur, *prev; if (handle == 0) return ((size_t)-1); for (i = 0; i < handle->ht_table_size; i++) { cur = handle->ht_table[i].he_head; prev = 0; while (cur) { if (cur->hi_flags & HTIF_MARKED_DELETED) { /* * We have a marked item: remove it. */ if (prev == 0) handle->ht_table[i].he_head = cur->hi_next; else prev->hi_next = cur->hi_next; if (handle->ht_callback) handle->ht_callback(cur); /* * Since the key and the item were allocated as * a single chunk, we only need one free here. */ free(cur); handle->ht_table[i].he_count--; handle->ht_sequence++; if (prev == 0) cur = handle->ht_table[i].he_head; else cur = prev->hi_next; continue; } prev = cur; cur = cur->hi_next; } } return (0); } /* * ht_mark_delete * * This function marks an item for deletion, which may be useful when * using findfirst/findnext to avoid modifying the table during the * table scan. Marked items can be removed later using ht_clean_table. */ void ht_mark_delete(HT_HANDLE *handle, HT_ITEM *item) { if (handle && item) { item->hi_flags |= HTIF_MARKED_DELETED; handle->ht_total_items--; } } /* * ht_clear_delete * * This function clear an item from marked for deletion list. */ void ht_clear_delete(HT_HANDLE *handle, HT_ITEM *item) { if (handle && item) { item->hi_flags &= ~HTIF_MARKED_DELETED; handle->ht_total_items++; } } /* * ht_bucket_search * * Returns first item which is not marked as deleted * in the specified bucket by 'head' */ static HT_ITEM * ht_bucket_search(HT_ITEM *head) { HT_ITEM *item = head; while ((item != 0) && (item->hi_flags & HTIF_MARKED_DELETED)) item = item->hi_next; return (item); } /* * ht_findfirst * * This function is used to begin an iteration through the hash table. * The iterator is initialized and the first item in the table (as * determined by the hash algorithm) is returned. The current sequence * number is stored in the iterator to determine whether or not the * the table has changed between calls. If the table is empty, a null * pointer is returned. */ HT_ITEM * ht_findfirst(HT_HANDLE *handle, HT_ITERATOR *iterator) { HT_ITEM *item; size_t h_index; if (handle == 0 || iterator == 0 || handle->ht_total_items == 0) return (NULL); (void) memset(iterator, 0, sizeof (HT_ITERATOR)); iterator->hti_handle = handle; iterator->hti_sequence = handle->ht_sequence; for (h_index = 0; h_index < handle->ht_table_size; ++h_index) { item = ht_bucket_search(handle->ht_table[h_index].he_head); if (item != 0) { iterator->hti_index = h_index; iterator->hti_item = item; return (item); } } return (NULL); } /* * ht_findnext * * Find the next item in the table for the given iterator. Iterators must * be initialized by ht_findfirst, which will also return the first item * in the table. If an item is available, a pointer to it is returned. * Otherwise a null pointer is returned. A null pointer may indicate: * * - an invalid iterator (i.e. ht_findfirst has not been called) * - the table has changed since the previous findfirst/findnext * - the entire table has been traversed * * The caller can use ht_get_total_items to determine whether or not all * of the items in the table have been visited. */ HT_ITEM * ht_findnext(HT_ITERATOR *iterator) { HT_HANDLE *handle; HT_ITEM *item; size_t total; size_t index; if (iterator == 0 || iterator->hti_handle == 0 || iterator->hti_sequence == 0) { /* Invalid iterator */ return (NULL); } handle = iterator->hti_handle; if (iterator->hti_item == 0 || iterator->hti_sequence != handle->ht_sequence) { /* * No more items or the table has changed * since the last call. */ return (NULL); } /* * Check for another item in the current bucket. */ item = ht_bucket_search(iterator->hti_item->hi_next); if (item != 0) { iterator->hti_item = item; return (item); } /* * Nothing else in the current bucket. Look for another * bucket with something in it and return the head item. */ total = handle->ht_table_size; for (index = iterator->hti_index + 1; index < total; ++index) { item = ht_bucket_search(handle->ht_table[index].he_head); if (item != 0) { iterator->hti_index = index; iterator->hti_item = item; return (item); } } iterator->hti_index = 0; iterator->hti_item = 0; iterator->hti_sequence = 0; return (NULL); } /* * 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 2018 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 RackTop Systems, Inc. */ /* * SMB server interface to idmap * (smb_idmap_get..., smb_idmap_batch_...) * * There are three implementations of this interface. * This is the libsmb version of these routines. See also: * $SRC/uts/common/fs/smbsrv/smb_idmap.c * $SRC/lib/smbsrv/libfksmbsrv/common/fksmb_idmap.c * * There are enough differences (relative to the code size) * that it's more trouble than it's worth to merge them. * * This one differs from the others in that it: * calls idmap interfaces (libidmap) * domain SIDs returned are allocated */ #include #include #include static int smb_idmap_batch_binsid(smb_idmap_batch_t *sib); /* * Report an idmap error. */ void smb_idmap_check(const char *s, idmap_stat stat) { if (stat != IDMAP_SUCCESS) { if (s == NULL) s = "smb_idmap_check"; syslog(LOG_ERR, "%s: %s", s, idmap_stat2string(stat)); } } /* * smb_idmap_getsid * * Tries to get a mapping for the given uid/gid * Allocates ->sim_domsid */ idmap_stat smb_idmap_getsid(uid_t id, int idtype, smb_sid_t **sid) { smb_idmap_batch_t sib; idmap_stat stat; stat = smb_idmap_batch_create(&sib, 1, SMB_IDMAP_ID2SID); if (stat != IDMAP_SUCCESS) return (stat); stat = smb_idmap_batch_getsid(sib.sib_idmaph, &sib.sib_maps[0], id, idtype); if (stat != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (stat); } /* Leave error reporting to the caller. */ stat = smb_idmap_batch_getmappings(&sib, NULL); if (stat != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (stat); } *sid = smb_sid_dup(sib.sib_maps[0].sim_sid); smb_idmap_batch_destroy(&sib); return (IDMAP_SUCCESS); } /* * smb_idmap_getid * * Tries to get a mapping for the given SID */ idmap_stat smb_idmap_getid(smb_sid_t *sid, uid_t *id, int *id_type) { smb_idmap_batch_t sib; smb_idmap_t *sim; idmap_stat stat; stat = smb_idmap_batch_create(&sib, 1, SMB_IDMAP_SID2ID); if (stat != IDMAP_SUCCESS) return (stat); sim = &sib.sib_maps[0]; sim->sim_id = id; stat = smb_idmap_batch_getid(sib.sib_idmaph, sim, sid, *id_type); if (stat != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (stat); } /* Leave error reporting to the caller. */ stat = smb_idmap_batch_getmappings(&sib, NULL); if (stat != IDMAP_SUCCESS) { smb_idmap_batch_destroy(&sib); return (stat); } *id_type = sim->sim_idtype; smb_idmap_batch_destroy(&sib); return (IDMAP_SUCCESS); } /* * smb_idmap_batch_create * * Creates and initializes the context for batch ID mapping. */ idmap_stat smb_idmap_batch_create(smb_idmap_batch_t *sib, uint16_t nmap, int flags) { idmap_stat stat; if (!sib) return (IDMAP_ERR_ARG); bzero(sib, sizeof (smb_idmap_batch_t)); stat = idmap_get_create(&sib->sib_idmaph); if (stat != IDMAP_SUCCESS) { smb_idmap_check("idmap_get_create", stat); return (stat); } sib->sib_flags = flags; sib->sib_nmap = nmap; sib->sib_size = nmap * sizeof (smb_idmap_t); sib->sib_maps = malloc(sib->sib_size); if (!sib->sib_maps) return (IDMAP_ERR_MEMORY); bzero(sib->sib_maps, sib->sib_size); return (IDMAP_SUCCESS); } /* * smb_idmap_batch_destroy * * Frees the batch ID mapping context. */ void smb_idmap_batch_destroy(smb_idmap_batch_t *sib) { int i; if (sib == NULL) return; if (sib->sib_idmaph) { idmap_get_destroy(sib->sib_idmaph); sib->sib_idmaph = NULL; } if (sib->sib_maps == NULL) return; if (sib->sib_flags & SMB_IDMAP_ID2SID) { /* * SIDs are allocated only when mapping * UID/GID to SIDs */ for (i = 0; i < sib->sib_nmap; i++) { smb_sid_free(sib->sib_maps[i].sim_sid); free(sib->sib_maps[i].sim_domsid); } } else if (sib->sib_flags & SMB_IDMAP_SID2ID) { /* * SID prefixes are allocated only when mapping * SIDs to UID/GID */ for (i = 0; i < sib->sib_nmap; i++) { free(sib->sib_maps[i].sim_domsid); } } if (sib->sib_size && sib->sib_maps) { free(sib->sib_maps); sib->sib_maps = NULL; } } /* * smb_idmap_batch_getid * * Queue a request to map the given SID to a UID or GID. * * sim->sim_id should point to variable that's supposed to * hold the returned UID/GID. This needs to be setup by caller * of this function. * If requested ID type is known, it's passed as 'idtype', * if it's unknown it'll be returned in sim->sim_idtype. */ idmap_stat smb_idmap_batch_getid(idmap_get_handle_t *idmaph, smb_idmap_t *sim, smb_sid_t *sid, int idtype) { char sidstr[SMB_SID_STRSZ]; idmap_stat stat; int flag = 0; if (idmaph == NULL || sim == NULL || sid == NULL) return (IDMAP_ERR_ARG); smb_sid_tostr(sid, sidstr); if (smb_sid_splitstr(sidstr, &sim->sim_rid) != 0) return (IDMAP_ERR_SID); /* Note: Free sim_domsid in smb_idmap_batch_destroy */ sim->sim_domsid = strdup(sidstr); sim->sim_idtype = idtype; switch (idtype) { case SMB_IDMAP_USER: stat = idmap_get_uidbysid(idmaph, sim->sim_domsid, sim->sim_rid, flag, sim->sim_id, &sim->sim_stat); smb_idmap_check("idmap_get_uidbysid", stat); break; case SMB_IDMAP_GROUP: stat = idmap_get_gidbysid(idmaph, sim->sim_domsid, sim->sim_rid, flag, sim->sim_id, &sim->sim_stat); smb_idmap_check("idmap_get_gidbysid", stat); break; case SMB_IDMAP_UNKNOWN: stat = idmap_get_pidbysid(idmaph, sim->sim_domsid, sim->sim_rid, flag, sim->sim_id, &sim->sim_idtype, &sim->sim_stat); smb_idmap_check("idmap_get_pidbysid", stat); break; default: stat = IDMAP_ERR_ARG; break; } return (stat); } /* * smb_idmap_batch_getsid * * Queue a request to map the given UID/GID to a SID. * * sim->sim_domsid and sim->sim_rid will contain the mapping * result upon successful process of the batched request. * Stash the type for error reporting (caller saves the ID). * * NB: sim_domsid allocated by strdup, here or in libidmap */ idmap_stat smb_idmap_batch_getsid(idmap_get_handle_t *idmaph, smb_idmap_t *sim, uid_t id, int idtype) { idmap_stat stat; int flag = 0; if (!idmaph || !sim) return (IDMAP_ERR_ARG); sim->sim_idtype = idtype; switch (idtype) { case SMB_IDMAP_USER: stat = idmap_get_sidbyuid(idmaph, id, flag, &sim->sim_domsid, &sim->sim_rid, &sim->sim_stat); smb_idmap_check("idmap_get_sidbyuid", stat); break; case SMB_IDMAP_GROUP: stat = idmap_get_sidbygid(idmaph, id, flag, &sim->sim_domsid, &sim->sim_rid, &sim->sim_stat); smb_idmap_check("idmap_get_sidbygid", stat); break; case SMB_IDMAP_OWNERAT: /* Current Owner S-1-5-32-766 */ sim->sim_domsid = strdup(NT_BUILTIN_DOMAIN_SIDSTR); sim->sim_rid = SECURITY_CURRENT_OWNER_RID; sim->sim_stat = IDMAP_SUCCESS; stat = IDMAP_SUCCESS; break; case SMB_IDMAP_GROUPAT: /* Current Group S-1-5-32-767 */ sim->sim_domsid = strdup(NT_BUILTIN_DOMAIN_SIDSTR); sim->sim_rid = SECURITY_CURRENT_GROUP_RID; sim->sim_stat = IDMAP_SUCCESS; stat = IDMAP_SUCCESS; break; case SMB_IDMAP_EVERYONE: /* Everyone S-1-1-0 */ sim->sim_domsid = strdup(NT_WORLD_AUTH_SIDSTR); sim->sim_rid = 0; sim->sim_stat = IDMAP_SUCCESS; stat = IDMAP_SUCCESS; break; default: return (IDMAP_ERR_ARG); } return (stat); } /* * smb_idmap_batch_getmappings * * trigger ID mapping service to get the mappings for queued * requests. * * Checks the result of all the queued requests. */ idmap_stat smb_idmap_batch_getmappings(smb_idmap_batch_t *sib, smb_idmap_batch_errcb_t errcb) { idmap_stat stat = IDMAP_SUCCESS; smb_idmap_t *sim; int i; if ((stat = idmap_get_mappings(sib->sib_idmaph)) != IDMAP_SUCCESS) { smb_idmap_check("idmap_get_mappings", stat); return (stat); } /* * Check the status for all the queued requests */ for (i = 0, sim = sib->sib_maps; i < sib->sib_nmap; i++, sim++) { if (sim->sim_stat != IDMAP_SUCCESS) { sib->sib_nerr++; if (errcb != NULL) errcb(sib, sim); if ((sib->sib_flags & SMB_IDMAP_SKIP_ERRS) == 0) { return (sim->sim_stat); } } } if (smb_idmap_batch_binsid(sib) != 0) stat = IDMAP_ERR_OTHER; return (stat); } /* * smb_idmap_batch_binsid * * Convert sidrids to binary sids * * Returns 0 if successful and non-zero upon failure. */ static int smb_idmap_batch_binsid(smb_idmap_batch_t *sib) { smb_sid_t *sid; smb_idmap_t *sim; int i; if (sib->sib_flags & SMB_IDMAP_SID2ID) /* This operation is not required */ return (0); sim = sib->sib_maps; for (i = 0; i < sib->sib_nmap; sim++, i++) { if (sim->sim_domsid == NULL) return (-1); sid = smb_sid_fromstr(sim->sim_domsid); if (sid == NULL) return (-1); sim->sim_sid = smb_sid_splice(sid, sim->sim_rid); smb_sid_free(sid); } return (0); } /* * 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 2020 Tintri by DDN, Inc. All rights reserved. * Copyright 2020-2023 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mutex_t seqnum_mtx; /* * IPC connection information that may be passed to the SMB Redirector. */ typedef struct { char user[SMB_USERNAME_MAXLEN]; uint8_t passwd[SMBAUTH_HASH_SZ]; } smb_ipc_t; static smb_ipc_t ipc_info; static smb_ipc_t ipc_orig_info; static rwlock_t smb_ipc_lock; /* * These three parameters are all related: * skc_initial_credits * skc_maximum_credits * skc_maxworkers (max worker threads) * They must be in non-decreasing order. Get the values in order: * maxworkers, maximum_credits, initial_credits * enforcing maximum values and relations as we go. Then in the * opposite order check minimum values and relations. * * smb_config_getnum puts a zero in the &citem if it fails getting * the parameter value. When fetch parameters for which zero is OK, * the return code is intentionally ignored. */ void smb_load_kconfig(smb_kmod_cfg_t *kcfg) { struct utsname uts; int64_t citem; int rc; bzero(kcfg, sizeof (smb_kmod_cfg_t)); /* * skc_maxworkers (max. no. of taskq worker threads) */ rc = smb_config_getnum(SMB_CI_MAX_WORKERS, &citem); if (rc != SMBD_SMF_OK) citem = SMB_PI_MAX_WORKERS_DEF; if (citem > SMB_PI_MAX_WORKERS_MAX) citem = SMB_PI_MAX_WORKERS_MAX; kcfg->skc_maxworkers = (uint32_t)citem; /* * The largest number of credits we let a single client have. * It never makes sense for this to be > max_workers */ rc = smb_config_getnum(SMB_CI_MAXIMUM_CREDITS, &citem); if (rc != SMBD_SMF_OK) citem = SMB_PI_MAXIMUM_CREDITS_DEF; if (citem > SMB_PI_MAXIMUM_CREDITS_MAX) citem = SMB_PI_MAXIMUM_CREDITS_MAX; kcfg->skc_maximum_credits = (uint16_t)citem; if (kcfg->skc_maximum_credits > kcfg->skc_maxworkers) kcfg->skc_maximum_credits = (uint16_t)kcfg->skc_maxworkers; /* * The number of credits we give a client initially. * Should be enough for a "light" workload, as the * client will request additional credits when the * workload increases. Must be <= maximum_credits. */ rc = smb_config_getnum(SMB_CI_INITIAL_CREDITS, &citem); if (rc != SMBD_SMF_OK) citem = SMB_PI_INITIAL_CREDITS_DEF; if (citem > SMB_PI_INITIAL_CREDITS_MAX) citem = SMB_PI_INITIAL_CREDITS_MAX; kcfg->skc_initial_credits = (uint16_t)citem; if (kcfg->skc_initial_credits > kcfg->skc_maximum_credits) kcfg->skc_initial_credits = kcfg->skc_maximum_credits; /* * Now enforce minimums, smaller to larger. */ if (kcfg->skc_initial_credits < SMB_PI_INITIAL_CREDITS_MIN) kcfg->skc_initial_credits = SMB_PI_INITIAL_CREDITS_MIN; if (kcfg->skc_maximum_credits < SMB_PI_MAXIMUM_CREDITS_MIN) kcfg->skc_maximum_credits = SMB_PI_MAXIMUM_CREDITS_MIN; if (kcfg->skc_maximum_credits < kcfg->skc_initial_credits) kcfg->skc_maximum_credits = kcfg->skc_initial_credits; if (kcfg->skc_maxworkers < SMB_PI_MAX_WORKERS_MIN) kcfg->skc_maxworkers = SMB_PI_MAX_WORKERS_MIN; if (kcfg->skc_maxworkers < kcfg->skc_maximum_credits) kcfg->skc_maxworkers = kcfg->skc_maximum_credits; (void) smb_config_getnum(SMB_CI_KEEPALIVE, &citem); kcfg->skc_keepalive = (uint32_t)citem; if ((kcfg->skc_keepalive != 0) && (kcfg->skc_keepalive < SMB_PI_KEEP_ALIVE_MIN)) kcfg->skc_keepalive = SMB_PI_KEEP_ALIVE_MIN; (void) smb_config_getnum(SMB_CI_MAX_CONNECTIONS, &citem); kcfg->skc_maxconnections = (uint32_t)citem; (void) smb_config_getnum(SMB_CI_MAX_OPENS, &citem); kcfg->skc_max_opens = (uint32_t)citem; kcfg->skc_restrict_anon = smb_config_getbool(SMB_CI_RESTRICT_ANON); kcfg->skc_signing_enable = smb_config_getbool(SMB_CI_SIGNING_ENABLE); kcfg->skc_signing_required = smb_config_getbool(SMB_CI_SIGNING_REQD); kcfg->skc_netbios_enable = smb_config_getbool(SMB_CI_NETBIOS_ENABLE); kcfg->skc_ipv6_enable = smb_config_getbool(SMB_CI_IPV6_ENABLE); kcfg->skc_print_enable = smb_config_getbool(SMB_CI_PRINT_ENABLE); kcfg->skc_oplock_enable = smb_config_getbool(SMB_CI_OPLOCK_ENABLE); kcfg->skc_sync_enable = smb_config_getbool(SMB_CI_SYNC_ENABLE); kcfg->skc_traverse_mounts = smb_config_getbool(SMB_CI_TRAVERSE_MOUNTS); kcfg->skc_short_names = smb_config_getbool(SMB_CI_SHORT_NAMES); kcfg->skc_max_protocol = smb_config_get_max_protocol(); kcfg->skc_min_protocol = smb_config_get_min_protocol(); kcfg->skc_secmode = smb_config_get_secmode(); kcfg->skc_encrypt = smb_config_get_require(SMB_CI_ENCRYPT); kcfg->skc_encrypt_ciphers = smb_config_get_encrypt_ciphers(); kcfg->skc_sign_algs = smb_config_get_signing_algs(); (void) smb_getdomainname(kcfg->skc_nbdomain, sizeof (kcfg->skc_nbdomain)); (void) smb_getfqdomainname(kcfg->skc_fqdn, sizeof (kcfg->skc_fqdn)); (void) smb_getnetbiosname(kcfg->skc_hostname, sizeof (kcfg->skc_hostname)); (void) smb_config_getstr(SMB_CI_SYS_CMNT, kcfg->skc_system_comment, sizeof (kcfg->skc_system_comment)); smb_config_get_version(&kcfg->skc_version); kcfg->skc_execflags = smb_config_get_execinfo(NULL, NULL, 0); if (smb_config_get_localuuid(kcfg->skc_machine_uuid) < 0) { syslog(LOG_ERR, "smb_load_kconfig: no machine_uuid"); uuid_generate_time(kcfg->skc_machine_uuid); } /* skc_negtok, skc_negtok_len: see smbd_authsvc.c */ (void) uname(&uts); (void) snprintf(kcfg->skc_native_os, sizeof (kcfg->skc_native_os), "%s %s %s", uts.sysname, uts.release, uts.version); (void) strlcpy(kcfg->skc_native_lm, "Native SMB service", sizeof (kcfg->skc_native_lm)); } /* * Get the current system NetBIOS name. The hostname is truncated at * the first `.` or 15 bytes, whichever occurs first, and converted * to uppercase (by smb_gethostname). Text that appears after the * first '.' is considered to be part of the NetBIOS scope. * * Returns 0 on success, otherwise -1 to indicate an error. */ int smb_getnetbiosname(char *buf, size_t buflen) { if (smb_gethostname(buf, buflen, SMB_CASE_UPPER) != 0) return (-1); if (buflen >= NETBIOS_NAME_SZ) buf[NETBIOS_NAME_SZ - 1] = '\0'; return (0); } /* * Get the SAM account of the current system. * Returns 0 on success, otherwise, -1 to indicate an error. */ int smb_getsamaccount(char *buf, size_t buflen) { if (smb_getnetbiosname(buf, buflen - 1) != 0) return (-1); (void) strlcat(buf, "$", buflen); return (0); } /* * Get the current system node name. The returned name is guaranteed * to be null-terminated (gethostname may not null terminate the name). * If the hostname has been fully-qualified for some reason, the domain * part will be removed. The returned hostname is converted to the * specified case (lower, upper, or preserved). * * If gethostname fails, the returned buffer will contain an empty * string. */ int smb_gethostname(char *buf, size_t buflen, smb_caseconv_t which) { char *p; if (buf == NULL || buflen == 0) return (-1); if (gethostname(buf, buflen) != 0) { *buf = '\0'; return (-1); } buf[buflen - 1] = '\0'; if ((p = strchr(buf, '.')) != NULL) *p = '\0'; switch (which) { case SMB_CASE_LOWER: (void) smb_strlwr(buf); break; case SMB_CASE_UPPER: (void) smb_strupr(buf); break; case SMB_CASE_PRESERVE: default: break; } return (0); } /* * Obtain the fully-qualified name for this machine in lower case. If * the hostname is fully-qualified, accept it. Otherwise, try to find an * appropriate domain name to append to the hostname. */ int smb_getfqhostname(char *buf, size_t buflen) { char hostname[MAXHOSTNAMELEN]; char domain[MAXHOSTNAMELEN]; hostname[0] = '\0'; domain[0] = '\0'; if (smb_gethostname(hostname, MAXHOSTNAMELEN, SMB_CASE_LOWER) != 0) return (-1); if (smb_getfqdomainname(domain, MAXHOSTNAMELEN) != 0) return (-1); if (hostname[0] == '\0') return (-1); if (domain[0] == '\0') { (void) strlcpy(buf, hostname, buflen); return (0); } (void) snprintf(buf, buflen, "%s.%s", hostname, domain); return (0); } /* * smb_getdomainname * * Returns NETBIOS name of the domain if the system is in domain * mode. Or returns workgroup name if the system is in workgroup * mode. */ int smb_getdomainname(char *buf, size_t buflen) { int rc; if (buf == NULL || buflen == 0) return (-1); *buf = '\0'; rc = smb_config_getstr(SMB_CI_DOMAIN_NAME, buf, buflen); if ((rc != SMBD_SMF_OK) || (*buf == '\0')) return (-1); return (0); } /* * smb_getfqdomainname * * In the system is in domain mode, the dns_domain property value * is returned. Otherwise, it returns the local domain obtained via * resolver. * * Returns 0 upon success. Otherwise, returns -1. */ int smb_getfqdomainname(char *buf, size_t buflen) { struct __res_state res_state; int rc; if (buf == NULL || buflen == 0) return (-1); *buf = '\0'; if (smb_config_get_secmode() == SMB_SECMODE_DOMAIN) { rc = smb_config_getstr(SMB_CI_DOMAIN_FQDN, buf, buflen); if ((rc != SMBD_SMF_OK) || (*buf == '\0')) return (-1); } else { bzero(&res_state, sizeof (struct __res_state)); if (res_ninit(&res_state)) return (-1); if (*res_state.defdname == '\0') { res_ndestroy(&res_state); return (-1); } (void) strlcpy(buf, res_state.defdname, buflen); res_ndestroy(&res_state); rc = 0; } return (rc); } /* * smb_set_machine_passwd * * This function should be used when setting the machine password property. * The associated sequence number is incremented. */ static int smb_set_machine_passwd(char *passwd) { int64_t num; int rc = -1; if (smb_config_set(SMB_CI_MACHINE_PASSWD, passwd) != SMBD_SMF_OK) return (-1); (void) mutex_lock(&seqnum_mtx); (void) smb_config_getnum(SMB_CI_KPASSWD_SEQNUM, &num); if (smb_config_setnum(SMB_CI_KPASSWD_SEQNUM, ++num) == SMBD_SMF_OK) rc = 0; (void) mutex_unlock(&seqnum_mtx); return (rc); } static int smb_get_machine_passwd(uint8_t *buf, size_t buflen) { char pwd[SMB_PASSWD_MAXLEN + 1]; int rc; if (buflen < SMBAUTH_HASH_SZ) return (-1); rc = smb_config_getstr(SMB_CI_MACHINE_PASSWD, pwd, sizeof (pwd)); if ((rc != SMBD_SMF_OK) || *pwd == '\0') return (-1); if (smb_auth_ntlm_hash(pwd, buf) != 0) return (-1); return (rc); } /* * Set up IPC connection credentials. */ void smb_ipc_init(void) { int rc; (void) rw_wrlock(&smb_ipc_lock); bzero(&ipc_info, sizeof (smb_ipc_t)); bzero(&ipc_orig_info, sizeof (smb_ipc_t)); (void) smb_getsamaccount(ipc_info.user, SMB_USERNAME_MAXLEN); rc = smb_get_machine_passwd(ipc_info.passwd, SMBAUTH_HASH_SZ); if (rc != 0) *ipc_info.passwd = 0; (void) rw_unlock(&smb_ipc_lock); } /* * Set the IPC username and password hash in memory. If the domain * join succeeds, the credentials will be committed for use with * authenticated IPC. Otherwise, they should be rolled back. */ void smb_ipc_set(char *plain_user, uint8_t *passwd_hash) { (void) rw_wrlock(&smb_ipc_lock); (void) strlcpy(ipc_info.user, plain_user, sizeof (ipc_info.user)); (void) memcpy(ipc_info.passwd, passwd_hash, SMBAUTH_HASH_SZ); (void) rw_unlock(&smb_ipc_lock); } /* * Save the host credentials to be used for authenticated IPC. * The credentials are also saved to the original IPC info as * rollback data in case the join domain process fails later. */ void smb_ipc_commit(void) { (void) rw_wrlock(&smb_ipc_lock); (void) smb_getsamaccount(ipc_info.user, SMB_USERNAME_MAXLEN); (void) smb_get_machine_passwd(ipc_info.passwd, SMBAUTH_HASH_SZ); (void) memcpy(&ipc_orig_info, &ipc_info, sizeof (smb_ipc_t)); (void) rw_unlock(&smb_ipc_lock); } /* * Restore the original credentials */ void smb_ipc_rollback(void) { (void) rw_wrlock(&smb_ipc_lock); (void) strlcpy(ipc_info.user, ipc_orig_info.user, sizeof (ipc_info.user)); (void) memcpy(ipc_info.passwd, ipc_orig_info.passwd, sizeof (ipc_info.passwd)); (void) rw_unlock(&smb_ipc_lock); } void smb_ipc_get_user(char *buf, size_t buflen) { (void) rw_rdlock(&smb_ipc_lock); (void) strlcpy(buf, ipc_info.user, buflen); (void) rw_unlock(&smb_ipc_lock); } void smb_ipc_get_passwd(uint8_t *buf, size_t buflen) { if (buflen < SMBAUTH_HASH_SZ) return; (void) rw_rdlock(&smb_ipc_lock); (void) memcpy(buf, ipc_info.passwd, SMBAUTH_HASH_SZ); (void) rw_unlock(&smb_ipc_lock); } /* * smb_match_netlogon_seqnum * * A sequence number is associated with each machine password property * update and the netlogon credential chain setup. If the * sequence numbers don't match, a NETLOGON credential chain * establishment is required. * * Returns 0 if kpasswd_seqnum equals to netlogon_seqnum. Otherwise, * returns -1. */ boolean_t smb_match_netlogon_seqnum(void) { int64_t setpasswd_seqnum; int64_t netlogon_seqnum; (void) mutex_lock(&seqnum_mtx); (void) smb_config_getnum(SMB_CI_KPASSWD_SEQNUM, &setpasswd_seqnum); (void) smb_config_getnum(SMB_CI_NETLOGON_SEQNUM, &netlogon_seqnum); (void) mutex_unlock(&seqnum_mtx); return (setpasswd_seqnum == netlogon_seqnum); } /* * smb_setdomainprops * * This function should be called after joining an AD to * set all the domain related SMF properties. * * The kpasswd_domain property is the AD domain to which the system * is joined via kclient. If this function is invoked by the SMB * daemon, fqdn should be set to NULL. */ int smb_setdomainprops(char *fqdn, char *server, char *passwd) { if (server == NULL || passwd == NULL) return (-1); if ((*server == '\0') || (*passwd == '\0')) return (-1); if (fqdn && (smb_config_set(SMB_CI_KPASSWD_DOMAIN, fqdn) != 0)) return (-1); if (smb_config_set(SMB_CI_KPASSWD_SRV, server) != 0) return (-1); if (smb_set_machine_passwd(passwd) != 0) { syslog(LOG_ERR, "smb_setdomainprops: failed to set" " machine account password"); return (-1); } /* * If we successfully create a trust account, we mark * ourselves as a domain member in the environment so * that we use the SAMLOGON version of the NETLOGON * PDC location protocol. */ (void) smb_config_setbool(SMB_CI_DOMAIN_MEMB, B_TRUE); return (0); } /* * smb_update_netlogon_seqnum * * This function should only be called upon a successful netlogon * credential chain establishment to set the sequence number of the * netlogon to match with that of the kpasswd. */ void smb_update_netlogon_seqnum(void) { int64_t num; (void) mutex_lock(&seqnum_mtx); (void) smb_config_getnum(SMB_CI_KPASSWD_SEQNUM, &num); (void) smb_config_setnum(SMB_CI_NETLOGON_SEQNUM, num); (void) mutex_unlock(&seqnum_mtx); } /* * Temporary fbt for dtrace until user space sdt enabled. */ void smb_tracef(const char *fmt, ...) { va_list ap; char buf[128]; va_start(ap, fmt); (void) vsnprintf(buf, 128, fmt, ap); va_end(ap); smb_trace(buf); } /* * Temporary fbt for dtrace until user space sdt enabled. * * This function is designed to be used with dtrace, i.e. see: * usr/src/cmd/smbsrv/dtrace/smbd-all.d * * Outside of dtrace, the messages passed to this function usually * lack sufficient context to be useful, so we don't log them. */ void smb_trace(const char *s __unused) { } /* * smb_tonetbiosname * * Creates a NetBIOS name based on the given name and suffix. * NetBIOS name is 15 capital characters, padded with space if needed * and the 16th byte is the suffix. */ void smb_tonetbiosname(char *name, char *nb_name, char suffix) { char tmp_name[NETBIOS_NAME_SZ]; smb_wchar_t wtmp_name[NETBIOS_NAME_SZ]; int len; size_t rc; len = 0; rc = smb_mbstowcs(wtmp_name, (const char *)name, NETBIOS_NAME_SZ); if (rc != (size_t)-1) { wtmp_name[NETBIOS_NAME_SZ - 1] = 0; rc = ucstooem(tmp_name, wtmp_name, NETBIOS_NAME_SZ, OEM_CPG_850); if (rc > 0) len = strlen(tmp_name); } (void) memset(nb_name, ' ', NETBIOS_NAME_SZ - 1); if (len) { (void) smb_strupr(tmp_name); (void) memcpy(nb_name, tmp_name, len); } nb_name[NETBIOS_NAME_SZ - 1] = suffix; } int smb_get_nameservers(smb_inaddr_t *ips, int sz) { union res_sockaddr_union set[MAXNS]; int i, cnt; struct __res_state res_state; char ipstr[INET6_ADDRSTRLEN]; if (ips == NULL) return (0); bzero(&res_state, sizeof (struct __res_state)); if (res_ninit(&res_state) < 0) return (0); cnt = res_getservers(&res_state, set, MAXNS); for (i = 0; i < cnt; i++) { if (i >= sz) break; ips[i].a_family = AF_INET; bcopy(&set[i].sin.sin_addr, &ips[i].a_ipv4, NS_INADDRSZ); if (inet_ntop(AF_INET, &ips[i].a_ipv4, ipstr, INET_ADDRSTRLEN)) { syslog(LOG_DEBUG, "Found %s name server\n", ipstr); continue; } ips[i].a_family = AF_INET6; bcopy(&set[i].sin.sin_addr, &ips[i].a_ipv6, NS_IN6ADDRSZ); if (inet_ntop(AF_INET6, &ips[i].a_ipv6, ipstr, INET6_ADDRSTRLEN)) { syslog(LOG_DEBUG, "Found %s name server\n", ipstr); } } res_ndestroy(&res_state); return (i); } /* * smb_gethostbyname * * Looks up a host by the given name. The host entry can come * from any of the sources for hosts specified in the * /etc/nsswitch.conf and the NetBIOS cache. * * XXX Invokes nbt_name_resolve API once the NBTD is integrated * to look in the NetBIOS cache if getipnodebyname fails. * * Caller should invoke freehostent to free the returned hostent. */ struct hostent * smb_gethostbyname(const char *name, int *err_num) { struct hostent *h; h = getipnodebyname(name, AF_INET, 0, err_num); if ((h == NULL) || h->h_length != INADDRSZ) h = getipnodebyname(name, AF_INET6, AI_DEFAULT, err_num); return (h); } /* * smb_gethostbyaddr * * Looks up a host by the given IP address. The host entry can come * from any of the sources for hosts specified in the * /etc/nsswitch.conf and the NetBIOS cache. * * XXX Invokes nbt API to resolve name by IP once the NBTD is integrated * to look in the NetBIOS cache if getipnodebyaddr fails. * * Caller should invoke freehostent to free the returned hostent. */ struct hostent * smb_gethostbyaddr(const char *addr, int len, int type, int *err_num) { struct hostent *h; h = getipnodebyaddr(addr, len, type, err_num); return (h); } uint32_t smb_get_netlogon_flags(void) { int64_t val; if (smb_config_getnum(SMB_CI_NETLOGON_FLAGS, &val) != SMBD_SMF_OK) return (SMB_PI_NETLOGON_FLAGS_DEFAULT); /* These are flags, and we only use the lowest 32 bits */ return ((uint32_t)val); } /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2017 Joyent, Inc. * Copyright 2020-2023 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMBDRV_DEVICE_PATH "/dev/smbsrv" int smb_kmod_ioctl(int, smb_ioc_header_t *, uint32_t); int smbdrv_fd = -1; /* * Open the smbsrv driver. * The smb daemon (smbd) opens the "control" device (/dev/smbsrv) * and other consumers open the library device (/dev/smbsrv1) */ int smb_kmod_bind(boolean_t smbd) { int fd; if (smbdrv_fd != -1) (void) close(smbdrv_fd); if (smbd) { fd = open(SMBDRV_DEVICE_PATH, 0); } else { fd = open(SMBDRV_DEVICE_PATH "1", 0); } if (fd < 0) return (errno); smbdrv_fd = fd; return (0); } boolean_t smb_kmod_isbound(void) { return ((smbdrv_fd == -1) ? B_FALSE : B_TRUE); } /* See also: smbsrv smb_server_store_cfg */ int smb_kmod_setcfg(smb_kmod_cfg_t *cfg) { smb_ioc_cfg_t ioc = {0}; ioc.maxworkers = cfg->skc_maxworkers; ioc.maxconnections = cfg->skc_maxconnections; ioc.keepalive = cfg->skc_keepalive; ioc.restrict_anon = cfg->skc_restrict_anon; ioc.signing_enable = cfg->skc_signing_enable; ioc.signing_required = cfg->skc_signing_required; ioc.oplock_enable = cfg->skc_oplock_enable; ioc.sync_enable = cfg->skc_sync_enable; ioc.secmode = cfg->skc_secmode; ioc.netbios_enable = cfg->skc_netbios_enable; ioc.ipv6_enable = cfg->skc_ipv6_enable; ioc.print_enable = cfg->skc_print_enable; ioc.traverse_mounts = cfg->skc_traverse_mounts; ioc.short_names = cfg->skc_short_names; ioc.max_protocol = cfg->skc_max_protocol; ioc.min_protocol = cfg->skc_min_protocol; ioc.exec_flags = cfg->skc_execflags; ioc.negtok_len = cfg->skc_negtok_len; ioc.max_opens = cfg->skc_max_opens; ioc.version = cfg->skc_version; ioc.initial_credits = cfg->skc_initial_credits; ioc.maximum_credits = cfg->skc_maximum_credits; ioc.encrypt = cfg->skc_encrypt; ioc.encrypt_ciphers = cfg->skc_encrypt_ciphers; ioc.sign_algs = cfg->skc_sign_algs; (void) memcpy(ioc.machine_uuid, cfg->skc_machine_uuid, sizeof (uuid_t)); (void) memcpy(ioc.negtok, cfg->skc_negtok, sizeof (ioc.negtok)); (void) memcpy(ioc.native_os, cfg->skc_native_os, sizeof (ioc.native_os)); (void) memcpy(ioc.native_lm, cfg->skc_native_lm, sizeof (ioc.native_lm)); (void) strlcpy(ioc.nbdomain, cfg->skc_nbdomain, sizeof (ioc.nbdomain)); (void) strlcpy(ioc.fqdn, cfg->skc_fqdn, sizeof (ioc.fqdn)); (void) strlcpy(ioc.hostname, cfg->skc_hostname, sizeof (ioc.hostname)); (void) strlcpy(ioc.system_comment, cfg->skc_system_comment, sizeof (ioc.system_comment)); return (smb_kmod_ioctl(SMB_IOC_CONFIG, &ioc.hdr, sizeof (ioc))); } int smb_kmod_setgmtoff(int32_t gmtoff) { smb_ioc_gmt_t ioc; ioc.offset = gmtoff; return (smb_kmod_ioctl(SMB_IOC_GMTOFF, &ioc.hdr, sizeof (ioc))); } int smb_kmod_start(int opipe, int lmshr, int udoor) { smb_ioc_start_t ioc; ioc.opipe = opipe; ioc.lmshrd = lmshr; ioc.udoor = udoor; return (smb_kmod_ioctl(SMB_IOC_START, &ioc.hdr, sizeof (ioc))); } void smb_kmod_stop(void) { smb_ioc_header_t ioc; (void) smb_kmod_ioctl(SMB_IOC_STOP, &ioc, sizeof (ioc)); } int smb_kmod_event_notify(uint32_t txid) { smb_ioc_event_t ioc; ioc.txid = txid; return (smb_kmod_ioctl(SMB_IOC_EVENT, &ioc.hdr, sizeof (ioc))); } int smb_kmod_share(nvlist_t *shrlist) { smb_ioc_share_t *ioc; uint32_t ioclen; char *shrbuf = NULL; size_t bufsz; int rc = ENOMEM; if ((rc = nvlist_pack(shrlist, &shrbuf, &bufsz, NV_ENCODE_XDR, 0)) != 0) return (rc); ioclen = sizeof (smb_ioc_share_t) + bufsz; if ((ioc = malloc(ioclen)) != NULL) { ioc->shrlen = bufsz; bcopy(shrbuf, ioc->shr, bufsz); rc = smb_kmod_ioctl(SMB_IOC_SHARE, &ioc->hdr, ioclen); free(ioc); } free(shrbuf); return (rc); } int smb_kmod_unshare(nvlist_t *shrlist) { smb_ioc_share_t *ioc; uint32_t ioclen; char *shrbuf = NULL; size_t bufsz; int rc = ENOMEM; if ((rc = nvlist_pack(shrlist, &shrbuf, &bufsz, NV_ENCODE_XDR, 0)) != 0) return (rc); ioclen = sizeof (smb_ioc_share_t) + bufsz; if ((ioc = malloc(ioclen)) != NULL) { ioc->shrlen = bufsz; bcopy(shrbuf, ioc->shr, bufsz); rc = smb_kmod_ioctl(SMB_IOC_UNSHARE, &ioc->hdr, ioclen); free(ioc); } free(shrbuf); return (rc); } int smb_kmod_shareinfo(char *shrname, boolean_t *shortnames) { smb_ioc_shareinfo_t ioc; int rc; bzero(&ioc, sizeof (ioc)); (void) strlcpy(ioc.shrname, shrname, MAXNAMELEN); rc = smb_kmod_ioctl(SMB_IOC_SHAREINFO, &ioc.hdr, sizeof (ioc)); if (rc == 0) *shortnames = ioc.shortnames; else *shortnames = B_TRUE; return (rc); } /* * Does the client have any access in this share? */ int smb_kmod_shareaccess(smb_netuserinfo_t *ui, smb_share_t *si) { smb_ioc_shareaccess_t ioc; int rc; bzero(&ioc, sizeof (ioc)); ioc.session_id = ui->ui_session_id; ioc.user_id = ui->ui_user_id; (void) strlcpy(ioc.shrname, si->shr_name, MAXNAMELEN); rc = smb_kmod_ioctl(SMB_IOC_SHAREACCESS, &ioc.hdr, sizeof (ioc)); return (rc); } int smb_kmod_get_open_num(smb_opennum_t *opennum) { smb_ioc_opennum_t ioc; int rc; bzero(&ioc, sizeof (ioc)); ioc.qualtype = opennum->qualtype; (void) strlcpy(ioc.qualifier, opennum->qualifier, MAXNAMELEN); rc = smb_kmod_ioctl(SMB_IOC_NUMOPEN, &ioc.hdr, sizeof (ioc)); if (rc == 0) { opennum->open_users = ioc.open_users; opennum->open_trees = ioc.open_trees; opennum->open_files = ioc.open_files; } return (rc); } int smb_kmod_get_spool_doc(uint32_t *spool_num, char *username, char *path, smb_inaddr_t *ipaddr) { smb_ioc_spooldoc_t ioc; int rc; bzero(&ioc, sizeof (ioc)); rc = smb_kmod_ioctl(SMB_IOC_SPOOLDOC, &ioc.hdr, sizeof (ioc)); if (rc == 0) { *spool_num = ioc.spool_num; (void) strlcpy(username, ioc.username, MAXNAMELEN); (void) strlcpy(path, ioc.path, MAXPATHLEN); *ipaddr = ioc.ipaddr; } return (rc); } /* * Initialization for an smb_kmod_enum request. If this call succeeds, * smb_kmod_enum_fini() must be called later to deallocate resources. */ smb_netsvc_t * smb_kmod_enum_init(smb_svcenum_t *request) { smb_netsvc_t *ns; smb_svcenum_t *svcenum; smb_ioc_svcenum_t *ioc; uint32_t ioclen; if ((ns = calloc(1, sizeof (smb_netsvc_t))) == NULL) return (NULL); ioclen = sizeof (smb_ioc_svcenum_t) + SMB_IOC_DATA_SIZE; if ((ioc = malloc(ioclen)) == NULL) { free(ns); return (NULL); } bzero(ioc, ioclen); svcenum = &ioc->svcenum; svcenum->se_type = request->se_type; svcenum->se_level = request->se_level; svcenum->se_bavail = SMB_IOC_DATA_SIZE; svcenum->se_nlimit = request->se_nlimit; svcenum->se_nskip = request->se_nskip; svcenum->se_buflen = SMB_IOC_DATA_SIZE; list_create(&ns->ns_list, sizeof (smb_netsvcitem_t), offsetof(smb_netsvcitem_t, nsi_lnd)); ns->ns_ioc = ioc; ns->ns_ioclen = ioclen; return (ns); } /* * Cleanup resources allocated via smb_kmod_enum_init and smb_kmod_enum. */ void smb_kmod_enum_fini(smb_netsvc_t *ns) { list_t *lst; smb_netsvcitem_t *item; smb_netuserinfo_t *user; smb_netconnectinfo_t *tree; smb_netfileinfo_t *ofile; uint32_t se_type; if (ns == NULL) return; lst = &ns->ns_list; se_type = ns->ns_ioc->svcenum.se_type; while ((item = list_head(lst)) != NULL) { list_remove(lst, item); switch (se_type) { case SMB_SVCENUM_TYPE_USER: user = &item->nsi_un.nsi_user; free(user->ui_domain); free(user->ui_account); free(user->ui_workstation); break; case SMB_SVCENUM_TYPE_TREE: tree = &item->nsi_un.nsi_tree; free(tree->ci_username); free(tree->ci_share); break; case SMB_SVCENUM_TYPE_FILE: ofile = &item->nsi_un.nsi_ofile; free(ofile->fi_path); free(ofile->fi_username); break; default: break; } } list_destroy(&ns->ns_list); free(ns->ns_items); free(ns->ns_ioc); free(ns); } /* * Enumerate users, connections or files. */ int smb_kmod_enum(smb_netsvc_t *ns) { smb_ioc_svcenum_t *ioc; uint32_t ioclen; smb_svcenum_t *svcenum; smb_netsvcitem_t *items; smb_netuserinfo_t *user; smb_netconnectinfo_t *tree; smb_netfileinfo_t *ofile; uint8_t *data; uint32_t len; uint32_t se_type; uint_t nbytes; int i; int rc; ioc = ns->ns_ioc; ioclen = ns->ns_ioclen; rc = smb_kmod_ioctl(SMB_IOC_SVCENUM, &ioc->hdr, ioclen); if (rc != 0) return (rc); svcenum = &ioc->svcenum; items = calloc(svcenum->se_nitems, sizeof (smb_netsvcitem_t)); if (items == NULL) return (ENOMEM); ns->ns_items = items; se_type = ns->ns_ioc->svcenum.se_type; data = svcenum->se_buf; len = svcenum->se_bused; for (i = 0; i < svcenum->se_nitems; ++i) { switch (se_type) { case SMB_SVCENUM_TYPE_USER: user = &items->nsi_un.nsi_user; rc = smb_netuserinfo_decode(user, data, len, &nbytes); break; case SMB_SVCENUM_TYPE_TREE: tree = &items->nsi_un.nsi_tree; rc = smb_netconnectinfo_decode(tree, data, len, &nbytes); break; case SMB_SVCENUM_TYPE_FILE: ofile = &items->nsi_un.nsi_ofile; rc = smb_netfileinfo_decode(ofile, data, len, &nbytes); break; default: rc = -1; break; } if (rc != 0) return (EINVAL); list_insert_tail(&ns->ns_list, items); ++items; data += nbytes; len -= nbytes; } return (0); } /* * A NULL pointer is a wildcard indicator, which we pass on * as an empty string (by virtue of the bzero). */ int smb_kmod_session_close(const char *client, const char *username) { smb_ioc_session_t ioc; int rc; bzero(&ioc, sizeof (ioc)); if (client != NULL) (void) strlcpy(ioc.client, client, MAXNAMELEN); if (username != NULL) (void) strlcpy(ioc.username, username, MAXNAMELEN); rc = smb_kmod_ioctl(SMB_IOC_SESSION_CLOSE, &ioc.hdr, sizeof (ioc)); return (rc); } int smb_kmod_file_close(uint32_t uniqid) { smb_ioc_fileid_t ioc; int rc; bzero(&ioc, sizeof (ioc)); ioc.uniqid = uniqid; rc = smb_kmod_ioctl(SMB_IOC_FILE_CLOSE, &ioc.hdr, sizeof (ioc)); return (rc); } void smb_kmod_unbind(void) { if (smbdrv_fd != -1) { (void) close(smbdrv_fd); smbdrv_fd = -1; } } /* * Note: The user-space smbd-d provides it own version of this function * which directly calls the "kernel" module code (in user space). */ int smb_kmod_ioctl(int cmd, smb_ioc_header_t *ioc, uint32_t len) { int rc = EINVAL; ioc->version = SMB_IOC_VERSION; ioc->cmd = cmd; ioc->len = len; ioc->crc = 0; ioc->crc = smb_crc_gen((uint8_t *)ioc, sizeof (smb_ioc_header_t)); if (smbdrv_fd != -1) { if (ioctl(smbdrv_fd, cmd, ioc) < 0) rc = errno; else rc = 0; } return (rc); } /* * 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013 RackTop Systems. * Copyright 2019 Nexenta by DDN, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Local domain SID (aka machine SID) is not stored in the domain table * therefore the index is 0 */ #define SMB_LGRP_LOCAL_IDX 0 #define SMB_LGRP_BUILTIN_IDX 1 #define SMB_LGRP_DB_NAME "/var/smb/smbgroup.db" #define SMB_LGRP_DB_TIMEOUT 3000 /* in millisecond */ #define SMB_LGRP_DB_VERMAJOR 1 #define SMB_LGRP_DB_VERMINOR 0 #define SMB_LGRP_DB_MAGIC 0x4C475250 /* LGRP */ #define SMB_LGRP_DB_ORD 1 /* open read-only */ #define SMB_LGRP_DB_ORW 2 /* open read/write */ #define SMB_LGRP_DB_ADDMEMBER 1 #define SMB_LGRP_DB_DELMEMBER 2 /* * members column of the groups table is an array of * member structure smb_lgmid_t defined below. * * privs column of the groups table is an array of bytes * where each byte is the id of an enable privilege */ #define SMB_LGRP_DB_SQL \ "CREATE TABLE db_info (" \ " ver_major INTEGER," \ " ver_minor INTEGER," \ " magic INTEGER" \ ");" \ "" \ "CREATE TABLE domains (" \ " dom_idx INTEGER PRIMARY KEY," \ " dom_sid TEXT UNIQUE," \ " dom_cnt INTEGER" \ ");" \ "" \ "CREATE UNIQUE INDEX domsid_idx ON domains (dom_sid);" \ "" \ "CREATE TABLE groups (" \ " name TEXT PRIMARY KEY," \ " sid_idx INTEGER," \ " sid_rid INTEGER," \ " sid_type INTEGER," \ " sid_attrs INTEGER," \ " comment TEXT," \ " n_privs INTEGER," \ " privs BLOB," \ " n_members INTEGER," \ " members BLOB" \ ");" \ "" \ "CREATE INDEX grprid_idx ON groups (sid_rid);" /* * Number of groups table columns */ #define SMB_LGRP_GTBL_NCOL 10 #define SMB_LGRP_GTBL_NAME 0 #define SMB_LGRP_GTBL_SIDIDX 1 #define SMB_LGRP_GTBL_SIDRID 2 #define SMB_LGRP_GTBL_SIDTYP 3 #define SMB_LGRP_GTBL_SIDATR 4 #define SMB_LGRP_GTBL_CMNT 5 #define SMB_LGRP_GTBL_NPRIVS 6 #define SMB_LGRP_GTBL_PRIVS 7 #define SMB_LGRP_GTBL_NMEMBS 8 #define SMB_LGRP_GTBL_MEMBS 9 #define SMB_LGRP_INFO_NONE 0x00 #define SMB_LGRP_INFO_NAME 0x01 #define SMB_LGRP_INFO_CMNT 0x02 #define SMB_LGRP_INFO_SID 0x04 #define SMB_LGRP_INFO_PRIV 0x08 #define SMB_LGRP_INFO_MEMB 0x10 #define SMB_LGRP_INFO_ALL 0x1F #define SMB_LGRP_PGRP_GRPTMP "/etc/gtmp" #define SMB_LGRP_PGRP_GRPBUFSIZ 5120 #define SMB_LGRP_PGRP_GROUP "/etc/group" #define SMB_LGRP_PGRP_MAXGLEN 9 /* max length of group name */ #define SMB_LGRP_PGRP_DEFRID 1000 /* lowest cifs created gid */ #define SMB_LGRP_PGRP_NOTUNIQUE 0 #define SMB_LGRP_PGRP_RESERVED 1 #define SMB_LGRP_PGRP_UNIQUE 2 #define SMB_LGRP_PGRP_TOOBIG 3 #define SMB_LGRP_PGRP_INVALID 4 #define NULL_MSGCHK(msg) ((msg) ? (msg) : "NULL") /* Member ID */ typedef struct smb_lgmid { uint32_t m_idx; uint32_t m_rid; uint16_t m_type; } smb_lgmid_t; /* Buffer size to hold hex form of the above (>24). */ #define SMB_LGRP_MID_HEXSZ 32 /* Size of idx,rid parts of above, in hex form. */ #define SMB_LGRP_IDXRID_LEN 16 /* Member list */ typedef struct smb_lgmlist { uint32_t m_cnt; char *m_ids; } smb_lgmlist_t; /* Privilege ID */ typedef uint8_t smb_lgpid_t; /* Privilege list */ typedef struct smb_lgplist { uint32_t p_cnt; smb_lgpid_t *p_ids; } smb_lgplist_t; static struct { int errnum; char *errmsg; } errtab[] = { { SMB_LGRP_SUCCESS, "success" }, { SMB_LGRP_INVALID_ARG, "invalid argument" }, { SMB_LGRP_INVALID_MEMBER, "invalid member type" }, { SMB_LGRP_INVALID_NAME, "invalid name" }, { SMB_LGRP_NOT_FOUND, "group not found" }, { SMB_LGRP_EXISTS, "group exists" }, { SMB_LGRP_NO_SID, "cannot obtain a SID" }, { SMB_LGRP_NO_LOCAL_SID, "cannot get the machine SID" }, { SMB_LGRP_SID_NOTLOCAL, "local account has non-local SID" }, { SMB_LGRP_WKSID, "operation not permitted on well-known account" }, { SMB_LGRP_NO_MEMORY, "not enough memory" }, { SMB_LGRP_DB_ERROR, "database operation error" }, { SMB_LGRP_DBINIT_ERROR, "database initialization error" }, { SMB_LGRP_INTERNAL_ERROR, "internal error" }, { SMB_LGRP_MEMBER_IN_GROUP, "member already in group" }, { SMB_LGRP_MEMBER_NOT_IN_GROUP, "not a member" }, { SMB_LGRP_NO_SUCH_PRIV, "no such privilege" }, { SMB_LGRP_NO_SUCH_DOMAIN, "no such domain SID" }, { SMB_LGRP_PRIV_HELD, "privilege already held" }, { SMB_LGRP_PRIV_NOT_HELD, "privilege not held" }, { SMB_LGRP_BAD_DATA, "bad data" }, { SMB_LGRP_NO_MORE, "no more groups" }, { SMB_LGRP_DBOPEN_FAILED, "database open failed" }, { SMB_LGRP_DBEXEC_FAILED, "database operation failed" }, { SMB_LGRP_DBINIT_FAILED, "database initialization failed" }, { SMB_LGRP_DOMLKP_FAILED, "domain SID lookup failed" }, { SMB_LGRP_DOMINS_FAILED, "domain SID insert failed" }, { SMB_LGRP_INSERT_FAILED, "group insert failed" }, { SMB_LGRP_DELETE_FAILED, "group delete failed" }, { SMB_LGRP_UPDATE_FAILED, "group update failed" }, { SMB_LGRP_LOOKUP_FAILED, "group lookup failed" }, { SMB_LGRP_OFFLINE, "local group service is offline" }, { SMB_LGRP_POSIXCREATE_FAILED, "posix group create failed" } }; /* * Serialization for the local group API. */ typedef struct { mutex_t lg_mutex; cond_t lg_cv; boolean_t lg_online; uint32_t lg_refcnt; smb_sid_t *lg_machine_sid; } smb_localgrp_t; static smb_localgrp_t smb_localgrp; static boolean_t smb_lgrp_enter(void); static void smb_lgrp_exit(void); static int smb_lgrp_db_init(void); static sqlite *smb_lgrp_db_open(int); static void smb_lgrp_db_close(sqlite *); static int smb_lgrp_db_setinfo(sqlite *); static boolean_t smb_lgrp_gtbl_exists(sqlite *, char *); static int smb_lgrp_gtbl_lookup(sqlite *, int, smb_group_t *, int, ...); static int smb_lgrp_gtbl_insert(sqlite *, smb_group_t *); static int smb_lgrp_gtbl_update(sqlite *, char *, smb_group_t *, int); static int smb_lgrp_gtbl_delete(sqlite *, char *); static int smb_lgrp_gtbl_update_mlist(sqlite *, char *, smb_gsid_t *, int); static int smb_lgrp_gtbl_update_plist(sqlite *, char *, uint8_t, boolean_t); static int smb_lgrp_gtbl_count(sqlite *, int, int *); static int smb_lgrp_dtbl_insert(sqlite *, char *, uint32_t *); static int smb_lgrp_dtbl_getidx(sqlite *, smb_sid_t *, uint16_t, uint32_t *, uint32_t *); static int smb_lgrp_dtbl_getsid(sqlite *, uint32_t, smb_sid_t **); static int smb_lgrp_mlist_add(smb_lgmlist_t *, smb_lgmid_t *, smb_lgmlist_t *); static int smb_lgrp_mlist_del(smb_lgmlist_t *, smb_lgmid_t *, smb_lgmlist_t *); static int smb_lgrp_plist_add(smb_lgplist_t *, smb_lgpid_t, smb_lgplist_t *); static int smb_lgrp_plist_del(smb_lgplist_t *, smb_lgpid_t, smb_lgplist_t *); static void smb_lgrp_encode_privset(smb_group_t *, smb_lgplist_t *); static int smb_lgrp_decode(smb_group_t *, char **, int, sqlite *); static int smb_lgrp_decode_privset(smb_group_t *, char *, char *); static int smb_lgrp_decode_members(smb_group_t *, char *, char *, sqlite *); static void smb_lgrp_set_default_privs(smb_group_t *); static boolean_t smb_lgrp_normalize_name(char *); static boolean_t smb_lgrp_chkmember(uint16_t); static int smb_lgrp_getsid(int, uint32_t *, uint16_t, sqlite *, smb_sid_t **); static int smb_lgrp_getgid(uint32_t rid, gid_t *gid); static boolean_t smb_lgrp_exists(char *); static int smb_lgrp_pgrp_add(char *); /* * smb_lgrp_add * * Create a local group with the given name and comment. * This new group doesn't have any members and no enabled * privileges. * * No well-known accounts can be added other than Administators, * Backup Operators and Power Users. These built-in groups * won't have any members when created but a set of default * privileges will be enabled for them. */ int smb_lgrp_add(char *gname, char *cmnt) { smb_wka_t *wka; struct group *pxgrp; smb_group_t grp; smb_sid_t *sid = NULL; sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt && (strlen(cmnt) > SMB_LGRP_COMMENT_MAX)) return (SMB_LGRP_INVALID_ARG); bzero(&grp, sizeof (grp)); grp.sg_name = smb_strlwr(gname); grp.sg_cmnt = cmnt; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); wka = smb_wka_lookup_name(gname); if (wka == NULL) { if ((pxgrp = getgrnam(gname)) == NULL) { if (smb_lgrp_pgrp_add(gname) != 0) { smb_lgrp_exit(); return (SMB_LGRP_POSIXCREATE_FAILED); } if ((pxgrp = getgrnam(gname)) == NULL) { smb_lgrp_exit(); return (SMB_LGRP_NOT_FOUND); } } /* * Make sure a local SID can be obtained */ if (smb_idmap_getsid(pxgrp->gr_gid, SMB_IDMAP_GROUP, &sid) != IDMAP_SUCCESS) { smb_lgrp_exit(); return (SMB_LGRP_NO_SID); } if (!smb_sid_indomain(smb_localgrp.lg_machine_sid, sid)) { free(sid); smb_lgrp_exit(); return (SMB_LGRP_SID_NOTLOCAL); } free(sid); grp.sg_id.gs_type = SidTypeAlias; grp.sg_domain = SMB_DOMAIN_LOCAL; grp.sg_rid = pxgrp->gr_gid; } else { if ((wka->wka_flags & SMB_WKAFLG_LGRP_ENABLE) == 0) { /* cannot add well-known accounts */ smb_lgrp_exit(); return (SMB_LGRP_WKSID); } grp.sg_id.gs_type = wka->wka_type; if ((sid = smb_sid_fromstr(wka->wka_sid)) == NULL) { smb_lgrp_exit(); return (SMB_LGRP_NO_MEMORY); } (void) smb_sid_getrid(sid, &grp.sg_rid); free(sid); grp.sg_domain = SMB_DOMAIN_BUILTIN; grp.sg_privs = smb_privset_new(); smb_lgrp_set_default_privs(&grp); } if (smb_lgrp_exists(grp.sg_name)) { smb_lgrp_exit(); return (SMB_LGRP_EXISTS); } grp.sg_attr = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_insert(db, &grp); smb_lgrp_db_close(db); smb_privset_free(grp.sg_privs); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_rename * * Renames the given group */ int smb_lgrp_rename(char *gname, char *new_gname) { smb_group_t grp; sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (smb_strcasecmp(gname, new_gname, 0) == 0) return (SMB_LGRP_SUCCESS); /* Cannot rename well-known groups */ if (smb_wka_lookup_name(gname) != NULL) return (SMB_LGRP_WKSID); /* Cannot rename to a well-known groups */ if (smb_wka_lookup_name(new_gname) != NULL) return (SMB_LGRP_WKSID); grp.sg_name = new_gname; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); if (getgrnam(new_gname) == NULL) { if (smb_lgrp_pgrp_add(new_gname) != 0) { smb_lgrp_exit(); return (SMB_LGRP_POSIXCREATE_FAILED); } if (getgrnam(new_gname) == NULL) { smb_lgrp_exit(); return (SMB_LGRP_NOT_FOUND); } } db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update(db, gname, &grp, SMB_LGRP_GTBL_NAME); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_delete * * Deletes the specified local group. */ int smb_lgrp_delete(char *gname) { sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); /* Cannot remove a built-in group */ if (smb_wka_lookup_name(gname) != NULL) return (SMB_LGRP_WKSID); if (!smb_lgrp_exists(gname)) return (SMB_LGRP_NOT_FOUND); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_delete(db, gname); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_setcmnt * * Sets the description for the given group */ int smb_lgrp_setcmnt(char *gname, char *cmnt) { smb_group_t grp; sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt && (strlen(cmnt) > SMB_LGRP_COMMENT_MAX)) return (SMB_LGRP_INVALID_ARG); grp.sg_cmnt = cmnt; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update(db, gname, &grp, SMB_LGRP_GTBL_CMNT); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_getcmnt * * Obtain the description of the specified group */ int smb_lgrp_getcmnt(char *gname, char **cmnt) { smb_group_t grp; sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (cmnt == NULL) return (SMB_LGRP_INVALID_ARG); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, &grp, SMB_LGRP_INFO_CMNT, gname); smb_lgrp_db_close(db); smb_lgrp_exit(); if (rc == SMB_LGRP_SUCCESS) { *cmnt = grp.sg_cmnt; grp.sg_cmnt = NULL; smb_lgrp_free(&grp); } return (rc); } /* * smb_lgrp_setpriv * * Enable/disable the specified privilge for the group */ int smb_lgrp_setpriv(char *gname, uint8_t priv_lid, boolean_t enable) { sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if ((priv_lid < SE_MIN_LUID) || (priv_lid > SE_MAX_LUID)) return (SMB_LGRP_NO_SUCH_PRIV); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_plist(db, gname, priv_lid, enable); smb_lgrp_db_close(db); smb_lgrp_exit(); if (enable) { if (rc == SMB_LGRP_PRIV_HELD) rc = SMB_LGRP_SUCCESS; } else { if (rc == SMB_LGRP_PRIV_NOT_HELD) rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_getpriv * * Obtain the status of the specified privilge for the group */ int smb_lgrp_getpriv(char *gname, uint8_t priv_lid, boolean_t *enable) { sqlite *db; smb_group_t grp; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if ((priv_lid < SE_MIN_LUID) || (priv_lid > SE_MAX_LUID)) return (SMB_LGRP_NO_SUCH_PRIV); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, &grp, SMB_LGRP_INFO_PRIV, gname); smb_lgrp_db_close(db); smb_lgrp_exit(); if (rc == SMB_LGRP_SUCCESS) { *enable = (smb_privset_query(grp.sg_privs, priv_lid) == 1); smb_lgrp_free(&grp); } return (rc); } /* * smb_lgrp_add_member * * Add the given account to the specified group as its member. */ int smb_lgrp_add_member(char *gname, smb_sid_t *msid, uint16_t sid_type) { sqlite *db; smb_gsid_t mid; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (!smb_sid_isvalid(msid)) return (SMB_LGRP_INVALID_ARG); if (!smb_lgrp_chkmember(sid_type)) return (SMB_LGRP_INVALID_MEMBER); mid.gs_sid = msid; mid.gs_type = sid_type; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_mlist(db, gname, &mid, SMB_LGRP_DB_ADDMEMBER); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_del_member * * Delete the specified member from the given group. */ int smb_lgrp_del_member(char *gname, smb_sid_t *msid, uint16_t sid_type) { sqlite *db; smb_gsid_t mid; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (!smb_sid_isvalid(msid)) return (SMB_LGRP_INVALID_ARG); mid.gs_sid = msid; mid.gs_type = sid_type; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORW); rc = smb_lgrp_gtbl_update_mlist(db, gname, &mid, SMB_LGRP_DB_DELMEMBER); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_getbyname * * Retrieves the information of the group specified by * the given name. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ int smb_lgrp_getbyname(char *gname, smb_group_t *grp) { sqlite *db; int rc; if (!smb_lgrp_normalize_name(gname)) return (SMB_LGRP_INVALID_NAME); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_NAME, grp, SMB_LGRP_INFO_ALL, gname); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_getbyrid * * Retrieves the information of the group specified by * the given RID and domain type. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). * * If grp is NULL no information would be returned. The * return value of SMB_LGRP_SUCCESS will indicate that a * group with the given information exists. */ int smb_lgrp_getbyrid(uint32_t rid, smb_domain_type_t domtype, smb_group_t *grp) { smb_group_t tmpgrp; sqlite *db; int infolvl = SMB_LGRP_INFO_ALL; int rc; if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); if (grp == NULL) { grp = &tmpgrp; infolvl = SMB_LGRP_INFO_NONE; } db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_lookup(db, SMB_LGRP_GTBL_SIDRID, grp, infolvl, rid, domtype); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_numbydomain * * Returns the number of groups in the given domain in the * arg 'count' */ int smb_lgrp_numbydomain(smb_domain_type_t dom_type, int *count) { sqlite *db; int dom_idx; int rc; switch (dom_type) { case SMB_DOMAIN_LOCAL: dom_idx = SMB_LGRP_LOCAL_IDX; break; case SMB_DOMAIN_BUILTIN: dom_idx = SMB_LGRP_BUILTIN_IDX; break; default: *count = 0; return (SMB_LGRP_INVALID_ARG); } if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); rc = smb_lgrp_gtbl_count(db, dom_idx, count); smb_lgrp_db_close(db); smb_lgrp_exit(); return (rc); } /* * smb_lgrp_free * * Frees the allocated memory for the fields of the given * group structure. Note that this function doesn't free * the group itself. */ void smb_lgrp_free(smb_group_t *grp) { int i; if (grp == NULL) return; free(grp->sg_name); free(grp->sg_cmnt); smb_sid_free(grp->sg_id.gs_sid); smb_privset_free(grp->sg_privs); for (i = 0; i < grp->sg_nmembers; i++) smb_sid_free(grp->sg_members[i].gs_sid); free(grp->sg_members); } /* * smb_lgrp_iteropen * * Initializes the given group iterator by opening * the group database and creating a virtual machine * for iteration. */ int smb_lgrp_iteropen(smb_giter_t *iter) { char *sql; char *errmsg = NULL; int rc = SMB_LGRP_SUCCESS; assert(iter); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); bzero(iter, sizeof (smb_giter_t)); sql = sqlite_mprintf("SELECT * FROM groups"); if (sql == NULL) { smb_lgrp_exit(); return (SMB_LGRP_NO_MEMORY); } iter->sgi_db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); if (iter->sgi_db == NULL) { sqlite_freemem(sql); smb_lgrp_exit(); return (SMB_LGRP_DBOPEN_FAILED); } rc = sqlite_compile(iter->sgi_db, sql, NULL, &iter->sgi_vm, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to create a VM (%s)", NULL_MSGCHK(errmsg)); rc = SMB_LGRP_DB_ERROR; } smb_lgrp_exit(); return (rc); } /* * smb_lgrp_iterclose * * Closes the given group iterator. */ void smb_lgrp_iterclose(smb_giter_t *iter) { char *errmsg = NULL; int rc; assert(iter); if (!smb_lgrp_enter()) return; rc = sqlite_finalize(iter->sgi_vm, &errmsg); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to destroy a VM (%s)", NULL_MSGCHK(errmsg)); } smb_lgrp_db_close(iter->sgi_db); smb_lgrp_exit(); } /* * Returns B_TRUE if there has been an error during * iteration. */ boolean_t smb_lgrp_itererror(smb_giter_t *iter) { return (iter->sgi_nerr != 0); } /* * smb_lgrp_iterate * * Iterate through group database * Group information is returned in provided group structure. * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ int smb_lgrp_iterate(smb_giter_t *iter, smb_group_t *grp) { const char **values; int ncol; int rc; int i; if (iter->sgi_vm == NULL || iter->sgi_db == NULL) return (SMB_LGRP_INVALID_ARG); if (!smb_lgrp_enter()) return (SMB_LGRP_OFFLINE); for (;;) { bzero(grp, sizeof (smb_group_t)); rc = sqlite_step(iter->sgi_vm, &ncol, &values, NULL); if (rc == SQLITE_DONE) { smb_lgrp_exit(); return (SMB_LGRP_NO_MORE); } if (rc != SQLITE_ROW) { smb_lgrp_exit(); return (SMB_LGRP_DBEXEC_FAILED); } if (ncol != SMB_LGRP_GTBL_NCOL) { smb_lgrp_exit(); return (SMB_LGRP_DB_ERROR); } for (i = 0; i < ncol; i++) { if (values[i] == NULL) { smb_lgrp_exit(); return (SMB_LGRP_DB_ERROR); } } rc = smb_lgrp_decode(grp, (char **)values, SMB_LGRP_INFO_ALL, iter->sgi_db); if (rc == SMB_LGRP_SUCCESS) break; iter->sgi_nerr++; syslog(LOG_ERR, "smb_lgrp_iterate: %s", smb_lgrp_strerror(rc)); } smb_lgrp_exit(); return (rc); } /* * smb_lgrp_is_member * * Check to see if the specified account is a member of * the given group. */ boolean_t smb_lgrp_is_member(smb_group_t *grp, smb_sid_t *sid) { int i; if (grp == NULL || grp->sg_members == NULL || sid == NULL) return (B_FALSE); for (i = 0; i < grp->sg_nmembers; i++) { if (smb_sid_cmp(grp->sg_members[i].gs_sid, sid)) return (B_TRUE); } return (B_FALSE); } /* * smb_lgrp_strerror * * Returns a text for the given group error code. */ char * smb_lgrp_strerror(int errnum) { int i; int nerr = (sizeof (errtab) / sizeof (errtab[0])); for (i = 0; i < nerr; ++i) { if (errnum == errtab[i].errnum) return (errtab[i].errmsg); } return ("unknown local group error"); } /* * smb_lgrp_err_to_ntstatus * * This routine maps Local group operation errors to NT Status error codes. */ uint32_t smb_lgrp_err_to_ntstatus(uint32_t lgrp_err) { int i; static struct err_map { uint32_t lgrp_err; uint32_t nt_status; } err_map[] = { { SMB_LGRP_SUCCESS, NT_STATUS_SUCCESS }, { SMB_LGRP_INVALID_ARG, NT_STATUS_INVALID_PARAMETER }, { SMB_LGRP_INVALID_MEMBER, NT_STATUS_INVALID_MEMBER }, { SMB_LGRP_INVALID_NAME, NT_STATUS_INVALID_PARAMETER }, { SMB_LGRP_NOT_FOUND, NT_STATUS_NO_SUCH_ALIAS }, { SMB_LGRP_EXISTS, NT_STATUS_ALIAS_EXISTS }, { SMB_LGRP_NO_SID, NT_STATUS_INVALID_SID }, { SMB_LGRP_NO_LOCAL_SID, NT_STATUS_INVALID_SID }, { SMB_LGRP_SID_NOTLOCAL, NT_STATUS_INVALID_SID }, { SMB_LGRP_WKSID, NT_STATUS_INVALID_SID }, { SMB_LGRP_NO_MEMORY, NT_STATUS_NO_MEMORY }, { SMB_LGRP_DB_ERROR, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DBINIT_ERROR, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_INTERNAL_ERROR, NT_STATUS_INTERNAL_ERROR }, { SMB_LGRP_MEMBER_IN_GROUP, NT_STATUS_MEMBER_IN_ALIAS }, { SMB_LGRP_MEMBER_NOT_IN_GROUP, NT_STATUS_MEMBER_NOT_IN_ALIAS }, { SMB_LGRP_NO_SUCH_PRIV, NT_STATUS_NO_SUCH_PRIVILEGE }, { SMB_LGRP_NO_SUCH_DOMAIN, NT_STATUS_NO_SUCH_DOMAIN }, { SMB_LGRP_PRIV_HELD, NT_STATUS_SUCCESS }, { SMB_LGRP_PRIV_NOT_HELD, NT_STATUS_PRIVILEGE_NOT_HELD }, { SMB_LGRP_BAD_DATA, NT_STATUS_DATA_ERROR }, { SMB_LGRP_NO_MORE, NT_STATUS_NO_MORE_ENTRIES }, { SMB_LGRP_DBOPEN_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DBEXEC_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DBINIT_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DOMLKP_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DOMINS_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_INSERT_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_DELETE_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_UPDATE_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_LOOKUP_FAILED, NT_STATUS_INTERNAL_DB_ERROR }, { SMB_LGRP_NOT_SUPPORTED, NT_STATUS_NOT_SUPPORTED }, { SMB_LGRP_OFFLINE, NT_STATUS_INTERNAL_ERROR }, { SMB_LGRP_POSIXCREATE_FAILED, NT_STATUS_UNSUCCESSFUL } }; for (i = 0; i < sizeof (err_map)/sizeof (err_map[0]); ++i) { if (err_map[i].lgrp_err == lgrp_err) return (err_map[i].nt_status); } return (NT_STATUS_INTERNAL_ERROR); } /* * smb_lgrp_chkmember * * Determines valid account types for being member of * a local group. We really have no business trying to * keep track of the "type" of SIDs in a group, so just * validate that the SID type is a known enum value. */ static boolean_t smb_lgrp_chkmember(uint16_t sid_type) { switch (sid_type) { case SidTypeNull: case SidTypeUser: case SidTypeGroup: case SidTypeAlias: case SidTypeWellKnownGroup: case SidTypeDeletedAccount: case SidTypeInvalid: case SidTypeUnknown: return (B_TRUE); } return (B_FALSE); } /* * smb_lgrp_start * * Initializes the library private global variables. * Create the database, if it doesn't exist, and add * the predefined builtin groups. */ int smb_lgrp_start(void) { static char *builtin[] = { "Administrators", "Backup Operators", "Power Users" }; smb_wka_t *wka; char *localsid; int i, rc; int ngrp = sizeof (builtin) / sizeof (builtin[0]); (void) mutex_lock(&smb_localgrp.lg_mutex); if ((localsid = smb_config_get_localsid()) == NULL) { (void) mutex_unlock(&smb_localgrp.lg_mutex); return (SMB_LGRP_NO_LOCAL_SID); } smb_localgrp.lg_machine_sid = smb_sid_fromstr(localsid); free(localsid); if (!smb_sid_isvalid(smb_localgrp.lg_machine_sid)) { free(smb_localgrp.lg_machine_sid); smb_localgrp.lg_machine_sid = NULL; (void) mutex_unlock(&smb_localgrp.lg_mutex); return (SMB_LGRP_NO_LOCAL_SID); } rc = smb_lgrp_db_init(); if (rc != SMB_LGRP_SUCCESS) { free(smb_localgrp.lg_machine_sid); smb_localgrp.lg_machine_sid = NULL; (void) mutex_unlock(&smb_localgrp.lg_mutex); return (rc); } smb_localgrp.lg_online = B_TRUE; (void) mutex_unlock(&smb_localgrp.lg_mutex); for (i = 0; i < ngrp; i++) { char *tname; if ((wka = smb_wka_lookup_name(builtin[i])) == NULL) continue; if ((tname = strdup(wka->wka_name)) == NULL) return (SMB_LGRP_NO_MEMORY); if (!smb_lgrp_exists(tname)) { rc = smb_lgrp_add(tname, wka->wka_desc); if (rc != SMB_LGRP_SUCCESS) { syslog(LOG_DEBUG, "failed to add %s", tname); } } free(tname); } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_stop * * Unintialize the library global private variables. */ void smb_lgrp_stop(void) { (void) mutex_lock(&smb_localgrp.lg_mutex); if (!smb_localgrp.lg_online) return; smb_localgrp.lg_online = B_FALSE; while (smb_localgrp.lg_refcnt > 0) (void) cond_wait(&smb_localgrp.lg_cv, &smb_localgrp.lg_mutex); free(smb_localgrp.lg_machine_sid); smb_localgrp.lg_machine_sid = NULL; (void) mutex_unlock(&smb_localgrp.lg_mutex); } static boolean_t smb_lgrp_enter(void) { boolean_t status; (void) mutex_lock(&smb_localgrp.lg_mutex); status = smb_localgrp.lg_online; if (smb_localgrp.lg_online) ++smb_localgrp.lg_refcnt; (void) mutex_unlock(&smb_localgrp.lg_mutex); return (status); } static void smb_lgrp_exit(void) { (void) mutex_lock(&smb_localgrp.lg_mutex); assert(smb_localgrp.lg_refcnt > 0); if ((--smb_localgrp.lg_refcnt) == 0) (void) cond_signal(&smb_localgrp.lg_cv); (void) mutex_unlock(&smb_localgrp.lg_mutex); } /* * smb_lgrp_db_open * * Opens group database with the given mode. */ static sqlite * smb_lgrp_db_open(int mode) { sqlite *db; char *errmsg = NULL; db = sqlite_open(SMB_LGRP_DB_NAME, mode, &errmsg); if (db == NULL) { syslog(LOG_ERR, "failed to open group database (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); } return (db); } /* * smb_lgrp_db_close * * Closes the given database handle */ static void smb_lgrp_db_close(sqlite *db) { if (db) { sqlite_close(db); } } /* * smb_lgrp_db_init * * Creates the group database based on the defined SQL statement. * It also initializes db_info and domain tables. */ static int smb_lgrp_db_init(void) { int dbrc = SQLITE_OK; int rc = SMB_LGRP_SUCCESS; sqlite *db = NULL; char *errmsg = NULL; db = sqlite_open(SMB_LGRP_DB_NAME, 0600, &errmsg); if (db == NULL) { syslog(LOG_ERR, "failed to create group database (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DBOPEN_FAILED); } sqlite_busy_timeout(db, SMB_LGRP_DB_TIMEOUT); dbrc = sqlite_exec(db, "BEGIN TRANSACTION;", NULL, NULL, &errmsg); if (dbrc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to begin database transaction (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); sqlite_close(db); return (SMB_LGRP_DBEXEC_FAILED); } switch (sqlite_exec(db, SMB_LGRP_DB_SQL, NULL, NULL, &errmsg)) { case SQLITE_ERROR: /* * This is the normal situation: CREATE probably failed because * tables already exist. It may indicate an error in SQL as well * but we cannot tell. */ sqlite_freemem(errmsg); dbrc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); rc = SMB_LGRP_SUCCESS; break; case SQLITE_OK: dbrc = sqlite_exec(db, "COMMIT TRANSACTION", NULL, NULL, &errmsg); if (dbrc != SQLITE_OK) break; rc = smb_lgrp_dtbl_insert(db, NT_BUILTIN_DOMAIN_SIDSTR, NULL); if (rc == SMB_LGRP_SUCCESS) rc = smb_lgrp_db_setinfo(db); if (rc != SMB_LGRP_SUCCESS) { (void) sqlite_close(db); (void) unlink(SMB_LGRP_DB_NAME); return (rc); } break; default: syslog(LOG_ERR, "failed to initialize group database (%s)", errmsg); sqlite_freemem(errmsg); dbrc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); rc = SMB_LGRP_DBINIT_FAILED; break; } if (dbrc != SQLITE_OK) { /* this is bad - database may be left in a locked state */ syslog(LOG_DEBUG, "failed to close a transaction (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); } (void) sqlite_close(db); return (rc); } /* * smb_lgrp_gtbl_lookup * * This is a flexible lookup function for the group database. * The key type can be specified by the 'key' arg and the actual key * values can be passed after the 'infolvl' arg. 'infolvl' arg specifies * what information items for the specified group is needed. * * Note that the function assumes the given key is unique and only * specifies one or 0 group. The keys that are supported now are * the group name and the group SID * * Note that this function doesn't allocate the group * structure itself only the fields, so the given grp * pointer has to point to a group structure. * Caller must free the allocated memories for the fields * by calling smb_lgrp_free(). */ static int smb_lgrp_gtbl_lookup(sqlite *db, int key, smb_group_t *grp, int infolvl, ...) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc, dom_idx; smb_group_t grpkey; va_list ap; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); bzero(grp, sizeof (smb_group_t)); va_start(ap, infolvl); switch (key) { case SMB_LGRP_GTBL_NAME: grpkey.sg_name = va_arg(ap, char *); sql = sqlite_mprintf("SELECT * FROM groups WHERE name = '%s'", grpkey.sg_name); break; case SMB_LGRP_GTBL_SIDRID: grpkey.sg_rid = va_arg(ap, uint32_t); grpkey.sg_domain = va_arg(ap, smb_domain_type_t); if (grpkey.sg_domain == SMB_DOMAIN_LOCAL) { dom_idx = SMB_LGRP_LOCAL_IDX; /* need to map the given rid to a gid */ rc = smb_lgrp_getgid(grpkey.sg_rid, (gid_t *)&grpkey.sg_rid); if (rc != SMB_LGRP_SUCCESS) { va_end(ap); return (rc); } } else { dom_idx = SMB_LGRP_BUILTIN_IDX; } sql = sqlite_mprintf("SELECT * FROM groups " "WHERE (sid_idx = %d) AND (sid_rid = %u)", dom_idx, grpkey.sg_rid); break; default: va_end(ap); return (SMB_LGRP_INVALID_ARG); } va_end(ap); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != SMB_LGRP_GTBL_NCOL) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } rc = smb_lgrp_decode(grp, &result[SMB_LGRP_GTBL_NCOL], infolvl, db); sqlite_free_table(result); return (rc); } /* * smb_lgrp_gtbl_exists * * Checks to see if the given group exists or not. */ static boolean_t smb_lgrp_gtbl_exists(sqlite *db, char *gname) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; if (db == NULL) return (B_FALSE); sql = sqlite_mprintf("SELECT name FROM groups WHERE name = '%s'", gname); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (B_FALSE); } sqlite_free_table(result); return (nrow != 0); } /* * smb_lgrp_gtbl_count * * Counts the number of groups in the domain specified by * 'dom_idx' */ static int smb_lgrp_gtbl_count(sqlite *db, int dom_idx, int *count) { char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; *count = 0; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT sid_idx FROM groups WHERE sid_idx = %d", dom_idx); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to count (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } sqlite_free_table(result); if (ncol > 1) return (SMB_LGRP_DB_ERROR); *count = nrow; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_gtbl_insert * * Insert a record for the given group in the group database. * * NOTE: this function assumes that this group has no members * at this time. */ static int smb_lgrp_gtbl_insert(sqlite *db, smb_group_t *grp) { smb_lgpid_t privs[SE_MAX_LUID + 1]; smb_lgplist_t plist; char *errmsg = NULL; char *sql; int dom_idx; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); dom_idx = (grp->sg_domain == SMB_DOMAIN_LOCAL) ? SMB_LGRP_LOCAL_IDX : SMB_LGRP_BUILTIN_IDX; plist.p_cnt = SE_MAX_LUID; plist.p_ids = privs; smb_lgrp_encode_privset(grp, &plist); sql = sqlite_mprintf("INSERT INTO groups " "(name, sid_idx, sid_rid, sid_type, sid_attrs, comment, " "n_privs, privs, n_members, members) " "VALUES('%s', %u, %u, %u, %u, '%q', %u, '%q', %u, '%q')", grp->sg_name, dom_idx, grp->sg_rid, grp->sg_id.gs_type, grp->sg_attr, (grp->sg_cmnt) ? grp->sg_cmnt : "", plist.p_cnt, (char *)plist.p_ids, 0, ""); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert %s (%s)", grp->sg_name, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_INSERT_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_delete * * Removes the specified group from the database */ static int smb_lgrp_gtbl_delete(sqlite *db, char *gname) { char *errmsg = NULL; char *sql; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("DELETE FROM groups WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to delete %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_DELETE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update * * Updates the specified group information, the supported items * are group name and comment */ static int smb_lgrp_gtbl_update(sqlite *db, char *gname, smb_group_t *grp, int col_id) { char *errmsg = NULL; char *sql; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); /* UPDATE doesn't fail if gname doesn't exist */ if (!smb_lgrp_gtbl_exists(db, gname)) return (SMB_LGRP_NOT_FOUND); switch (col_id) { case SMB_LGRP_GTBL_NAME: if (smb_lgrp_gtbl_exists(db, grp->sg_name)) return (SMB_LGRP_EXISTS); sql = sqlite_mprintf("UPDATE groups SET name = '%s' " "WHERE name = '%s'", grp->sg_name, gname); break; case SMB_LGRP_GTBL_CMNT: sql = sqlite_mprintf("UPDATE groups SET comment = '%q' " "WHERE name = '%s'", grp->sg_cmnt, gname); break; default: return (SMB_LGRP_INVALID_ARG); } if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update_mlist * * Adds/removes the specified member from the member list of the * given group */ static int smb_lgrp_gtbl_update_mlist(sqlite *db, char *gname, smb_gsid_t *member, int flags) { smb_lgmlist_t new_members; smb_lgmlist_t members; smb_lgmid_t mid; char *errmsg = NULL; char *sql; char **result; int nrow, ncol; int rc; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT n_members, members FROM groups " "WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != 2) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } bzero(&mid, sizeof (mid)); mid.m_type = member->gs_type; rc = smb_lgrp_dtbl_getidx(db, member->gs_sid, mid.m_type, &mid.m_idx, &mid.m_rid); if (rc != SMB_LGRP_SUCCESS) { sqlite_free_table(result); return (rc); } members.m_cnt = atoi(result[2]); members.m_ids = result[3]; switch (flags) { case SMB_LGRP_DB_ADDMEMBER: rc = smb_lgrp_mlist_add(&members, &mid, &new_members); break; case SMB_LGRP_DB_DELMEMBER: rc = smb_lgrp_mlist_del(&members, &mid, &new_members); break; default: rc = SMB_LGRP_INVALID_ARG; } sqlite_free_table(result); if (rc != SMB_LGRP_SUCCESS) return (rc); sql = sqlite_mprintf("UPDATE groups SET n_members = %u, members = '%s'" " WHERE name = '%s'", new_members.m_cnt, new_members.m_ids, gname); free(new_members.m_ids); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_gtbl_update_plist * * Adds/removes the specified privilege from the privilege list of the * given group */ static int smb_lgrp_gtbl_update_plist(sqlite *db, char *gname, uint8_t priv_id, boolean_t enable) { char *sql; char *errmsg = NULL; char **result; int nrow, ncol; int rc; smb_lgplist_t privs; smb_lgplist_t new_privs; if (db == NULL) return (SMB_LGRP_DBOPEN_FAILED); sql = sqlite_mprintf("SELECT n_privs, privs FROM groups " "WHERE name = '%s'", gname); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_LOOKUP_FAILED); } if (nrow == 0) { /* group not found */ sqlite_free_table(result); return (SMB_LGRP_NOT_FOUND); } if (nrow != 1 || ncol != 2) { sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } privs.p_cnt = atoi(result[2]); privs.p_ids = (smb_lgpid_t *)result[3]; if (enable) rc = smb_lgrp_plist_add(&privs, priv_id, &new_privs); else rc = smb_lgrp_plist_del(&privs, priv_id, &new_privs); sqlite_free_table(result); if (rc != SMB_LGRP_SUCCESS) return (rc); sql = sqlite_mprintf("UPDATE groups SET n_privs = %u, privs = '%q'" " WHERE name = '%s'", new_privs.p_cnt, (char *)new_privs.p_ids, gname); free(new_privs.p_ids); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to update %s (%s)", gname, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_UPDATE_FAILED; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_dtbl_insert * * Inserts the specified domain SID in the dmain table. * Upon successful insert the index will be returned in * 'dom_idx' arg. */ static int smb_lgrp_dtbl_insert(sqlite *db, char *dom_sid, uint32_t *dom_idx) { char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("INSERT INTO domains (dom_sid, dom_cnt)" " VALUES('%s', 1);", dom_sid); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert domain SID (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMINS_FAILED); } if (dom_idx) *dom_idx = sqlite_last_insert_rowid(db); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_dtbl_getidx * * Searches the domain table for the domain SID of the * given member SID. If it finds the domain SID it'll * return the index and the RID, otherwise it'll insert * it in the domain table as a new SID. */ static int smb_lgrp_dtbl_getidx(sqlite *db, smb_sid_t *sid, uint16_t sid_type, uint32_t *dom_idx, uint32_t *rid) { char sidstr[SMB_SID_STRSZ]; smb_sid_t *dom_sid; char **result; int nrow, ncol; char *errmsg = NULL; char *sql; int rc; if (smb_sid_indomain(smb_localgrp.lg_machine_sid, sid)) { /* This is a local SID */ int id_type = (sid_type == SidTypeUser) ? SMB_IDMAP_USER : SMB_IDMAP_GROUP; *dom_idx = SMB_LGRP_LOCAL_IDX; if (smb_idmap_getid(sid, rid, &id_type) != IDMAP_SUCCESS) return (SMB_LGRP_INTERNAL_ERROR); return (SMB_LGRP_SUCCESS); } if ((dom_sid = smb_sid_split(sid, rid)) == NULL) return (SMB_LGRP_NO_MEMORY); smb_sid_tostr(dom_sid, sidstr); free(dom_sid); sql = sqlite_mprintf("SELECT dom_idx FROM domains WHERE dom_sid = '%s'", sidstr); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup domain SID (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMLKP_FAILED); } switch (nrow) { case 0: /* new domain SID; insert it into the domains table */ sqlite_free_table(result); return (smb_lgrp_dtbl_insert(db, sidstr, dom_idx)); case 1: *dom_idx = atoi(result[1]); sqlite_free_table(result); return (SMB_LGRP_SUCCESS); } sqlite_free_table(result); return (SMB_LGRP_DB_ERROR); } /* * smb_lgrp_dtbl_getsid * * Searchs the domain table for the given domain index. * Converts the found domain SID to binary format and * returns it in the 'sid' arg. * * Caller must free the returned SID by calling free(). */ static int smb_lgrp_dtbl_getsid(sqlite *db, uint32_t dom_idx, smb_sid_t **sid) { char **result; int nrow, ncol; char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("SELECT dom_sid FROM domains WHERE dom_idx = %u", dom_idx); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to lookup domain index (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_LGRP_DOMLKP_FAILED); } switch (nrow) { case 0: rc = SMB_LGRP_NO_SUCH_DOMAIN; break; case 1: *sid = smb_sid_fromstr(result[1]); rc = (*sid == NULL) ? SMB_LGRP_INTERNAL_ERROR : SMB_LGRP_SUCCESS; break; default: rc = SMB_LGRP_DB_ERROR; break; } sqlite_free_table(result); return (rc); } /* * smb_lgrp_db_setinfo * * Initializes the db_info table upon database creation. */ static int smb_lgrp_db_setinfo(sqlite *db) { char *errmsg = NULL; char *sql; int rc; sql = sqlite_mprintf("INSERT INTO db_info (ver_major, ver_minor," " magic) VALUES (%d, %d, %u)", SMB_LGRP_DB_VERMAJOR, SMB_LGRP_DB_VERMINOR, SMB_LGRP_DB_MAGIC); if (sql == NULL) return (SMB_LGRP_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_DEBUG, "failed to insert database information (%s)", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = SMB_LGRP_DBINIT_ERROR; } else { rc = SMB_LGRP_SUCCESS; } return (rc); } /* * smb_lgrp_mlist_add * * Adds the given member (newm) to the input member list (in_members) * if it's not already there. The result list will be returned in * out_members. The caller must free the allocated memory for * out_members by calling free(). * * in_members and out_members are hex strings. */ static int smb_lgrp_mlist_add(smb_lgmlist_t *in_members, smb_lgmid_t *newm, smb_lgmlist_t *out_members) { char mid_hex[SMB_LGRP_MID_HEXSZ]; char *in_list; char *out_list; int in_size; int out_size; int mid_hexsz; int i; out_members->m_cnt = 0; out_members->m_ids = NULL; bzero(mid_hex, sizeof (mid_hex)); mid_hexsz = bintohex((const char *)newm, sizeof (smb_lgmid_t), mid_hex, sizeof (mid_hex)); /* * Check to see if this is already a group member */ in_list = in_members->m_ids; for (i = 0; i < in_members->m_cnt; i++) { if (strncmp(in_list, mid_hex, mid_hexsz) == 0) return (SMB_LGRP_MEMBER_IN_GROUP); in_list += mid_hexsz; } in_size = (in_members->m_ids) ? strlen(in_members->m_ids) : 0; out_size = in_size + sizeof (mid_hex) + 1; out_list = malloc(out_size); if (out_list == NULL) return (SMB_LGRP_NO_MEMORY); bzero(out_list, out_size); if (in_members->m_ids) (void) strlcpy(out_list, in_members->m_ids, out_size); (void) strcat(out_list, mid_hex); out_members->m_cnt = in_members->m_cnt + 1; out_members->m_ids = out_list; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_mlist_del * * Removes the given member (msid) from the input member list * (in_members) if it's already there. The result list will b * returned in out_members. The caller must free the allocated * memory for out_members by calling free(). * * in_members and out_members are hex strings. * * Note that we ignore the SID "type" when matching because * we always want to delete when the SID part matches. * The "type" part can be fiction. */ static int smb_lgrp_mlist_del(smb_lgmlist_t *in_members, smb_lgmid_t *mid, smb_lgmlist_t *out_members) { char mid_hex[SMB_LGRP_MID_HEXSZ]; char *in_list; char *out_list; int in_size; int out_size; int mid_hexsz; int out_cnt; int i; out_members->m_cnt = 0; out_members->m_ids = NULL; if ((in_members == NULL) || (in_members->m_cnt == 0)) return (SMB_LGRP_MEMBER_NOT_IN_GROUP); in_size = strlen(in_members->m_ids); out_size = in_size + sizeof (mid_hex) + 1; out_list = malloc(out_size); if (out_list == NULL) return (SMB_LGRP_NO_MEMORY); *out_list = '\0'; bzero(mid_hex, sizeof (mid_hex)); mid_hexsz = bintohex((const char *)mid, sizeof (smb_lgmid_t), mid_hex, sizeof (mid_hex)); in_list = in_members->m_ids; for (i = 0, out_cnt = 0; i < in_members->m_cnt; i++) { /* Keep only those NOT matching in IDX,RID */ if (strncmp(in_list, mid_hex, SMB_LGRP_IDXRID_LEN)) { (void) strncat(out_list, in_list, mid_hexsz); out_cnt++; } in_list += mid_hexsz; } if (out_cnt == in_members->m_cnt) { free(out_list); return (SMB_LGRP_MEMBER_NOT_IN_GROUP); } out_members->m_cnt = out_cnt; out_members->m_ids = out_list; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_plist_add * * Adds the given privilege to the input list (in_privs) * if it's not already there. The result list is returned * in out_privs. The caller must free the allocated memory * for out_privs by calling free(). */ static int smb_lgrp_plist_add(smb_lgplist_t *in_privs, smb_lgpid_t priv_id, smb_lgplist_t *out_privs) { int i, size; smb_lgpid_t *pbuf; out_privs->p_cnt = 0; out_privs->p_ids = NULL; for (i = 0; i < in_privs->p_cnt; i++) { if (in_privs->p_ids[i] == priv_id) return (SMB_LGRP_PRIV_HELD); } size = (in_privs->p_cnt + 1) * sizeof (smb_lgpid_t) + 1; pbuf = malloc(size); if (pbuf == NULL) return (SMB_LGRP_NO_MEMORY); bzero(pbuf, size); bcopy(in_privs->p_ids, pbuf, in_privs->p_cnt * sizeof (smb_lgpid_t)); pbuf[in_privs->p_cnt] = priv_id; out_privs->p_cnt = in_privs->p_cnt + 1; out_privs->p_ids = pbuf; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_plist_del * * Removes the given privilege from the input list (in_privs) * if it's already there. The result list is returned * in out_privs. The caller must free the allocated memory * for out_privs by calling free(). */ static int smb_lgrp_plist_del(smb_lgplist_t *in_privs, smb_lgpid_t priv_id, smb_lgplist_t *out_privs) { int i, size; out_privs->p_cnt = 0; out_privs->p_ids = NULL; if ((in_privs == NULL) || (in_privs->p_cnt == 0)) return (SMB_LGRP_PRIV_NOT_HELD); size = (in_privs->p_cnt - 1) * sizeof (smb_lgpid_t) + 1; out_privs->p_ids = malloc(size); if (out_privs->p_ids == NULL) return (SMB_LGRP_NO_MEMORY); bzero(out_privs->p_ids, size); for (i = 0; i < in_privs->p_cnt; i++) { if (in_privs->p_ids[i] != priv_id) out_privs->p_ids[out_privs->p_cnt++] = in_privs->p_ids[i]; } if (out_privs->p_cnt == in_privs->p_cnt) { free(out_privs->p_ids); out_privs->p_cnt = 0; out_privs->p_ids = NULL; return (SMB_LGRP_PRIV_NOT_HELD); } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_encode_privset * * Encodes given privilege set into a buffer to be stored in the group * database. Each entry of the encoded buffer contains the privilege ID * of an enable privilege. The returned buffer is null-terminated. */ static void smb_lgrp_encode_privset(smb_group_t *grp, smb_lgplist_t *plist) { smb_privset_t *privs; uint32_t pcnt = plist->p_cnt; int i; bzero(plist->p_ids, sizeof (smb_lgpid_t) * plist->p_cnt); plist->p_cnt = 0; privs = grp->sg_privs; if ((privs == NULL) || (privs->priv_cnt == 0)) return; if (pcnt < privs->priv_cnt) { assert(0); } for (i = 0; i < privs->priv_cnt; i++) { if (privs->priv[i].attrs == SE_PRIVILEGE_ENABLED) { plist->p_ids[plist->p_cnt++] = (uint8_t)privs->priv[i].luid.lo_part; } } } /* * smb_lgrp_decode_privset * * Decodes the privilege information read from group table * (nprivs, privs) into a binray format specified by the * privilege field of smb_group_t */ static int smb_lgrp_decode_privset(smb_group_t *grp, char *nprivs, char *privs) { smb_lgplist_t plist; int i; plist.p_cnt = atoi(nprivs); if (strlen(privs) != plist.p_cnt) return (SMB_LGRP_BAD_DATA); plist.p_ids = (smb_lgpid_t *)privs; grp->sg_privs = smb_privset_new(); if (grp->sg_privs == NULL) return (SMB_LGRP_NO_MEMORY); for (i = 0; i < plist.p_cnt; i++) smb_privset_enable(grp->sg_privs, plist.p_ids[i]); return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_decode_members * * Decodes the members information read from group table * (nmembers, members) into a binary format specified by the * member fields of smb_group_t */ static int smb_lgrp_decode_members(smb_group_t *grp, char *nmembers, char *members, sqlite *db) { smb_lgmid_t *m_id; smb_lgmid_t *m_ids; smb_gsid_t *m_sid; smb_gsid_t *m_sids; int m_num; int mids_size; int i, rc; grp->sg_nmembers = 0; grp->sg_members = NULL; m_num = atoi(nmembers); mids_size = m_num * sizeof (smb_lgmid_t); if ((m_ids = malloc(mids_size)) == NULL) return (SMB_LGRP_NO_MEMORY); m_sids = calloc(m_num, sizeof (smb_gsid_t)); if (m_sids == NULL) { free(m_ids); return (SMB_LGRP_NO_MEMORY); } (void) hextobin(members, strlen(members), (char *)m_ids, mids_size); m_id = m_ids; m_sid = m_sids; for (i = 0; i < m_num; i++, m_id++, m_sid++) { rc = smb_lgrp_getsid(m_id->m_idx, &m_id->m_rid, m_id->m_type, db, &m_sid->gs_sid); if (rc != SMB_LGRP_SUCCESS) { free(m_ids); for (m_sid = m_sids; m_sid->gs_sid != NULL; m_sid++) smb_sid_free(m_sid->gs_sid); free(m_sids); return (rc); } m_sid->gs_type = m_id->m_type; } free(m_ids); grp->sg_nmembers = m_num; grp->sg_members = m_sids; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_decode * * Fills out the fields of the given group (grp) based in the * string information read from the group table. infolvl determines * which fields are requested and need to be decoded. * * Allocated memories must be freed by calling smb_lgrp_free() * upon successful return. */ static int smb_lgrp_decode(smb_group_t *grp, char **values, int infolvl, sqlite *db) { uint32_t sid_idx; int rc; if (infolvl == SMB_LGRP_INFO_NONE) return (SMB_LGRP_SUCCESS); if (infolvl & SMB_LGRP_INFO_NAME) { grp->sg_name = strdup(values[SMB_LGRP_GTBL_NAME]); if (grp->sg_name == NULL) return (SMB_LGRP_NO_MEMORY); } if (infolvl & SMB_LGRP_INFO_CMNT) { grp->sg_cmnt = strdup(values[SMB_LGRP_GTBL_CMNT]); if (grp->sg_cmnt == NULL) { smb_lgrp_free(grp); return (SMB_LGRP_NO_MEMORY); } } if (infolvl & SMB_LGRP_INFO_SID) { sid_idx = atoi(values[SMB_LGRP_GTBL_SIDIDX]); grp->sg_rid = atoi(values[SMB_LGRP_GTBL_SIDRID]); grp->sg_attr = atoi(values[SMB_LGRP_GTBL_SIDATR]); grp->sg_id.gs_type = atoi(values[SMB_LGRP_GTBL_SIDTYP]); rc = smb_lgrp_getsid(sid_idx, &grp->sg_rid, grp->sg_id.gs_type, db, &grp->sg_id.gs_sid); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (rc); } grp->sg_domain = (sid_idx == SMB_LGRP_LOCAL_IDX) ? SMB_DOMAIN_LOCAL : SMB_DOMAIN_BUILTIN; } if (infolvl & SMB_LGRP_INFO_PRIV) { rc = smb_lgrp_decode_privset(grp, values[SMB_LGRP_GTBL_NPRIVS], values[SMB_LGRP_GTBL_PRIVS]); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (rc); } } if (infolvl & SMB_LGRP_INFO_MEMB) { rc = smb_lgrp_decode_members(grp, values[SMB_LGRP_GTBL_NMEMBS], values[SMB_LGRP_GTBL_MEMBS], db); if (rc != SMB_LGRP_SUCCESS) { smb_lgrp_free(grp); return (rc); } } return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_normalize_name * * Trim whitespace, validate the group name and convert it to lowercase. */ static boolean_t smb_lgrp_normalize_name(char *name) { (void) trim_whitespace(name); if (smb_name_validate_account(name) != ERROR_SUCCESS) return (B_FALSE); (void) smb_strlwr(name); return (B_TRUE); } /* * smb_lgrp_set_default_privs * * set default privileges for Administrators and Backup Operators */ static void smb_lgrp_set_default_privs(smb_group_t *grp) { if (smb_strcasecmp(grp->sg_name, "Administrators", 0) == 0) { smb_privset_enable(grp->sg_privs, SE_TAKE_OWNERSHIP_LUID); smb_privset_enable(grp->sg_privs, SE_BACKUP_LUID); smb_privset_enable(grp->sg_privs, SE_RESTORE_LUID); return; } if (smb_strcasecmp(grp->sg_name, "Backup Operators", 0) == 0) { smb_privset_enable(grp->sg_privs, SE_BACKUP_LUID); smb_privset_enable(grp->sg_privs, SE_RESTORE_LUID); return; } } /* * smb_lgrp_getsid * * Returns a SID based on the provided information * If dom_idx is 0, it means 'rid' contains a UID/GID and the * returned SID will be a local SID. If dom_idx is not 0 then * the domain SID will be fetched from the domain table. */ static int smb_lgrp_getsid(int dom_idx, uint32_t *rid, uint16_t sid_type, sqlite *db, smb_sid_t **sid) { smb_sid_t *dom_sid = NULL; smb_sid_t *res_sid = NULL; idmap_stat stat; int id_type; int rc; *sid = NULL; if (dom_idx == SMB_LGRP_LOCAL_IDX) { id_type = (sid_type == SidTypeUser) ? SMB_IDMAP_USER : SMB_IDMAP_GROUP; stat = smb_idmap_getsid(*rid, id_type, &res_sid); if (stat != IDMAP_SUCCESS) { syslog(LOG_ERR, "smb_lgrp_getsid: " "failed to get a SID for %s id=%u (%d)", (id_type == SMB_IDMAP_USER) ? "user" : "group", *rid, stat); return (SMB_LGRP_NO_SID); } /* * Make sure the returned SID is local */ if (!smb_sid_indomain(smb_localgrp.lg_machine_sid, res_sid)) { syslog(LOG_ERR, "smb_lgrp_getsid: " "local %s (%u) is mapped to a non-local SID", (id_type == SMB_IDMAP_USER) ? "user" : "group", *rid); smb_sid_free(res_sid); return (SMB_LGRP_SID_NOTLOCAL); } (void) smb_sid_getrid(res_sid, rid); *sid = res_sid; return (SMB_LGRP_SUCCESS); } rc = smb_lgrp_dtbl_getsid(db, dom_idx, &dom_sid); if (rc != SMB_LGRP_SUCCESS) { syslog(LOG_ERR, "smb_lgrp_getsid: %s", smb_lgrp_strerror(rc)); return (SMB_LGRP_DB_ERROR); } res_sid = smb_sid_splice(dom_sid, *rid); smb_sid_free(dom_sid); if (res_sid == NULL) { syslog(LOG_ERR, "smb_lgrp_getsid: %s", smb_lgrp_strerror(rc)); return (SMB_LGRP_NO_MEMORY); } *sid = res_sid; return (SMB_LGRP_SUCCESS); } /* * smb_lgrp_getgid * * Converts given local RID to a local gid since for user * defined local groups, gid is stored in the table. */ static int smb_lgrp_getgid(uint32_t rid, gid_t *gid) { smb_sid_t *sid; int idtype; int rc; if ((sid = smb_sid_splice(smb_localgrp.lg_machine_sid, rid)) == NULL) return (SMB_LGRP_NO_MEMORY); idtype = SMB_IDMAP_GROUP; rc = smb_idmap_getid(sid, gid, &idtype); smb_sid_free(sid); return ((rc == IDMAP_SUCCESS) ? SMB_LGRP_SUCCESS : SMB_LGRP_NOT_FOUND); } /* * smb_lgrp_exists * * Returns B_TRUE if the local group with the given name exists. * Otherwise, returns B_FALSE. */ static boolean_t smb_lgrp_exists(char *gname) { sqlite *db; boolean_t rc; if (!smb_lgrp_normalize_name(gname)) return (B_FALSE); db = smb_lgrp_db_open(SMB_LGRP_DB_ORD); if (db == NULL) return (B_FALSE); rc = smb_lgrp_gtbl_exists(db, gname); smb_lgrp_db_close(db); return (rc); } /* * smb_lgrp_pgrp_valid_gname * * Validate posix group name string. */ static int smb_lgrp_pgrp_valid_gname(char *group) { char *ptr = group; char c; int len = 0; int badchar = 0; if (!group || !*group) return (SMB_LGRP_PGRP_INVALID); for (c = *ptr; c != '\0'; ptr++, c = *ptr) { len++; if (!isprint(c) || (c == ':') || (c == '\n')) return (SMB_LGRP_PGRP_INVALID); if (!(islower(c) || isdigit(c))) badchar++; } if ((len > SMB_LGRP_PGRP_MAXGLEN - 1) || (badchar != 0)) return (SMB_LGRP_PGRP_INVALID); if (getgrnam(group) != NULL) return (SMB_LGRP_PGRP_NOTUNIQUE); return (SMB_LGRP_PGRP_UNIQUE); } /* * smb_lgrp_pgrp_add * * Create a posix group with the given name. * This group will be added to the /etc/group file. */ static int smb_lgrp_pgrp_add(char *group) { FILE *etcgrp; FILE *etctmp; int o_mask; int newdone = 0; struct stat sb; char buf[SMB_LGRP_PGRP_GRPBUFSIZ]; gid_t gid; int rc = 0; rc = smb_lgrp_pgrp_valid_gname(group); if ((rc == SMB_LGRP_PGRP_INVALID) || (rc == SMB_LGRP_PGRP_NOTUNIQUE)) return (-1); if ((findnextgid(SMB_LGRP_PGRP_DEFRID, MAXUID, &gid)) != 0) return (-1); if ((etcgrp = fopen(SMB_LGRP_PGRP_GROUP, "r")) == NULL) return (-1); if (fstat(fileno(etcgrp), &sb) < 0) sb.st_mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH; o_mask = umask(077); etctmp = fopen(SMB_LGRP_PGRP_GRPTMP, "w+"); (void) umask(o_mask); if (etctmp == NULL) { (void) fclose(etcgrp); return (-1); } if (lockf(fileno(etctmp), F_LOCK, 0) != 0) { (void) fclose(etcgrp); (void) fclose(etctmp); (void) unlink(SMB_LGRP_PGRP_GRPTMP); return (-1); } if (fchmod(fileno(etctmp), sb.st_mode) != 0 || fchown(fileno(etctmp), sb.st_uid, sb.st_gid) != 0) { (void) lockf(fileno(etctmp), F_ULOCK, 0); (void) fclose(etcgrp); (void) fclose(etctmp); (void) unlink(SMB_LGRP_PGRP_GRPTMP); return (-1); } while (fgets(buf, SMB_LGRP_PGRP_GRPBUFSIZ, etcgrp) != NULL) { /* Check for NameService reference */ if (!newdone && (buf[0] == '+' || buf[0] == '-')) { (void) fprintf(etctmp, "%s::%u:\n", group, gid); newdone = 1; } (void) fputs(buf, etctmp); } (void) fclose(etcgrp); if (!newdone) (void) fprintf(etctmp, "%s::%u:\n", group, gid); if (rename(SMB_LGRP_PGRP_GRPTMP, SMB_LGRP_PGRP_GROUP) < 0) { (void) lockf(fileno(etctmp), F_ULOCK, 0); (void) fclose(etctmp); (void) unlink(SMB_LGRP_PGRP_GRPTMP); return (-1); } (void) lockf(fileno(etctmp), F_ULOCK, 0); (void) fclose(etctmp); return (0); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2013 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_NIC_DB_NAME "/var/smb/smbhosts.db" #define SMB_NIC_DB_TIMEOUT 3000 /* in millisecond */ #define SMB_NIC_DB_VERMAJOR 1 #define SMB_NIC_DB_VERMINOR 0 #define SMB_NIC_DB_MAGIC 0x484F5354 /* HOST */ #define SMB_NIC_DB_ORD 1 /* open read-only */ #define SMB_NIC_DB_ORW 2 /* open read/write */ #define SMB_NIC_DB_SQL \ "CREATE TABLE db_info (" \ " ver_major INTEGER," \ " ver_minor INTEGER," \ " magic INTEGER" \ ");" \ "" \ "CREATE TABLE hosts (" \ " hostname TEXT PRIMARY KEY," \ " comment TEXT," \ " ifnames TEXT" \ ");" #define SMB_NIC_HTBL_NCOL 3 #define SMB_NIC_HTBL_HOST 0 #define SMB_NIC_HTBL_CMNT 1 #define SMB_NIC_HTBL_IFS 2 #define NULL_MSGCHK(msg) ((msg) ? (msg) : "NULL") #define SMB_NIC_MAXIFS 256 #define SMB_NIC_MAXEXCLLIST_LEN 512 typedef struct smb_hostifs { list_node_t if_lnd; char if_host[MAXHOSTNAMELEN]; char if_cmnt[SMB_PI_MAX_COMMENT]; char *if_names[SMB_NIC_MAXIFS]; int if_num; } smb_hostifs_t; typedef struct smb_hosts { list_t h_list; int h_num; int h_ifnum; } smb_hosts_t; typedef struct { smb_nic_t *nl_nics; int nl_cnt; /* number of smb_nic_t structures */ int nl_hcnt; /* number of host names */ long nl_seqnum; /* a random sequence number */ rwlock_t nl_rwl; } smb_niclist_t; static int smb_nic_list_create(void); static void smb_nic_list_destroy(void); static int smb_nic_hlist_create(smb_hosts_t *); static void smb_nic_hlist_destroy(smb_hosts_t *); static int smb_nic_hlist_dbget(smb_hosts_t *); static int smb_nic_hlist_sysget(smb_hosts_t *); static void smb_nic_iflist_destroy(smb_hostifs_t *); static smb_hostifs_t *smb_nic_iflist_decode(const char **, int *); static int smb_nic_dbcreate(void); static sqlite *smb_nic_dbopen(int); static void smb_nic_dbclose(sqlite *); static boolean_t smb_nic_dbexists(void); static boolean_t smb_nic_dbvalidate(void); static int smb_nic_dbaddhost(const char *, const char *, char *); static int smb_nic_dbdelhost(const char *); static int smb_nic_dbsetinfo(sqlite *); static int smb_nic_getinfo(char *, smb_nic_t *, int); static boolean_t smb_nic_nbt_exclude(const smb_nic_t *, const char **, int); static int smb_nic_nbt_get_exclude_list(char *, char **, int); static void smb_close_sockets(int, int); static boolean_t smb_duplicate_nic(smb_hostifs_t *iflist, struct lifreq *lifrp); /* This is the list we will monitor */ static smb_niclist_t smb_niclist; /* * smb_nic_init * * Initializes the interface list. */ int smb_nic_init(void) { int rc; (void) rw_wrlock(&smb_niclist.nl_rwl); smb_nic_list_destroy(); rc = smb_nic_list_create(); (void) rw_unlock(&smb_niclist.nl_rwl); return (rc); } /* * smb_nic_fini * * Destroys the interface list. */ void smb_nic_fini(void) { (void) rw_wrlock(&smb_niclist.nl_rwl); smb_nic_list_destroy(); (void) rw_unlock(&smb_niclist.nl_rwl); } /* * smb_nic_getnum * * Gets the number of interfaces for the specified host. * if host is NULL then total number of interfaces * is returned. It's assumed that given name is a NetBIOS * encoded name. */ int smb_nic_getnum(char *nb_hostname) { int n = 0, i; (void) rw_rdlock(&smb_niclist.nl_rwl); if (nb_hostname != NULL) { for (i = 0; i < smb_niclist.nl_cnt; i++) { /* ignore the suffix */ if (strncasecmp(smb_niclist.nl_nics[i].nic_nbname, nb_hostname, NETBIOS_NAME_SZ - 1) == 0) n++; } } else { n = smb_niclist.nl_cnt; } (void) rw_unlock(&smb_niclist.nl_rwl); return (n); } /* * smb_nic_getfirst * * Returns the first NIC in the interface list and * initializes the given iterator. To get the rest of * NICs smb_nic_getnext() must be called. * * Returns SMB_NIC_SUCCESS upon success or the following: * SMB_NIC_NOT_FOUND - there's no interface available * SMB_NIC_INVALID_ARG - 'ni' is NULL */ int smb_nic_getfirst(smb_niciter_t *ni) { int rc = SMB_NIC_SUCCESS; if (ni == NULL) return (SMB_NIC_INVALID_ARG); (void) rw_rdlock(&smb_niclist.nl_rwl); if (smb_niclist.nl_cnt > 0) { ni->ni_nic = smb_niclist.nl_nics[0]; ni->ni_cookie = 1; ni->ni_seqnum = smb_niclist.nl_seqnum; } else { rc = SMB_NIC_NOT_FOUND; } (void) rw_unlock(&smb_niclist.nl_rwl); return (rc); } /* * smb_nic_getnext * * Returns the next NIC information based on the passed * iterator (ni). The iterator must have previously been * initialized by calling smb_nic_getfirst(). * * Returns SMB_NIC_SUCCESS upon successfully finding the specified NIC * or the following: * SMB_NIC_INVALID_ARG - the specified iterator is invalid * SMB_NIC_NO_MORE - reaches the end of the NIC list * SMB_NIC_CHANGED - sequence number in the iterator is different from * the sequence number in the NIC list which means * the list has been changed between getfirst/getnext * calls. */ int smb_nic_getnext(smb_niciter_t *ni) { int rc = SMB_NIC_SUCCESS; if ((ni == NULL) || (ni->ni_cookie < 1)) return (SMB_NIC_INVALID_ARG); (void) rw_rdlock(&smb_niclist.nl_rwl); if ((smb_niclist.nl_cnt > ni->ni_cookie) && (smb_niclist.nl_seqnum == ni->ni_seqnum)) { ni->ni_nic = smb_niclist.nl_nics[ni->ni_cookie]; ni->ni_cookie++; } else { if (smb_niclist.nl_seqnum != ni->ni_seqnum) rc = SMB_NIC_CHANGED; else rc = SMB_NIC_NO_MORE; } (void) rw_unlock(&smb_niclist.nl_rwl); return (rc); } boolean_t smb_nic_is_local(smb_inaddr_t *ipaddr) { smb_nic_t *cfg; int i; (void) rw_rdlock(&smb_niclist.nl_rwl); for (i = 0; i < smb_niclist.nl_cnt; i++) { cfg = &smb_niclist.nl_nics[i]; if (smb_inet_equal(ipaddr, &cfg->nic_ip)) { (void) rw_unlock(&smb_niclist.nl_rwl); return (B_TRUE); } } (void) rw_unlock(&smb_niclist.nl_rwl); return (B_FALSE); } boolean_t smb_nic_is_same_subnet(smb_inaddr_t *ipaddr) { smb_nic_t *cfg; int i; (void) rw_rdlock(&smb_niclist.nl_rwl); for (i = 0; i < smb_niclist.nl_cnt; i++) { cfg = &smb_niclist.nl_nics[i]; if (smb_inet_same_subnet(ipaddr, &cfg->nic_ip, cfg->nic_mask)) { (void) rw_unlock(&smb_niclist.nl_rwl); return (B_TRUE); } } (void) rw_unlock(&smb_niclist.nl_rwl); return (B_FALSE); } /* * smb_nic_addhost * * Adds an association between the given host and the specified interface * list (if_names). This function can be called as many times as needed, * the associations will be stored in /var/smb/smbhosts.db, which is sqlite * database. If this file exists and it's not empty NIC list is generated * based on the information stored in this file. * * host: actual system's name (not Netbios name) * cmnt: an optional description for the CIFS server running on * the specified host. Can be NULL. * if_num: number of interface names in if_names arg * if_names: array of interface names in string format * * Returns SMB_NIC_SUCCESS upon success, a nonzero value otherwise. */ int smb_nic_addhost(const char *host, const char *cmnt, int if_num, const char **if_names) { char *if_list; char *ifname; int buflen = 0; int rc, i; if ((host == NULL) || (if_num <= 0) || (if_names == NULL)) return (SMB_NIC_INVALID_ARG); if (!smb_nic_dbexists() || !smb_nic_dbvalidate()) { if ((rc = smb_nic_dbcreate()) != SMB_NIC_SUCCESS) return (rc); } for (i = 0; i < if_num; i++) { ifname = (char *)if_names[i]; if ((ifname == NULL) || (*ifname == '\0')) return (SMB_NIC_INVALID_ARG); buflen += strlen(ifname) + 1; } if ((if_list = malloc(buflen)) == NULL) return (SMB_NIC_NO_MEMORY); ifname = if_list; for (i = 0; i < if_num - 1; i++) ifname += snprintf(ifname, buflen, "%s,", if_names[i]); (void) snprintf(ifname, buflen, "%s", if_names[i]); rc = smb_nic_dbaddhost(host, cmnt, if_list); free(if_list); return (rc); } /* * smb_nic_delhost * * Removes the stored interface association for the specified host */ int smb_nic_delhost(const char *host) { if ((host == NULL) || (*host == '\0')) return (SMB_NIC_INVALID_ARG); if (!smb_nic_dbexists()) return (SMB_NIC_SUCCESS); if (!smb_nic_dbvalidate()) { (void) unlink(SMB_NIC_DB_NAME); return (SMB_NIC_SUCCESS); } return (smb_nic_dbdelhost(host)); } /* * smb_nic_list_create * * Creates a NIC list either based on /var/smb/smbhosts.db or * by getting the information from OS. * * Note that the caller of this function should grab the * list lock. */ static int smb_nic_list_create(void) { smb_hosts_t hlist; smb_hostifs_t *iflist; smb_nic_t *nc; char *ifname; char excludestr[SMB_NIC_MAXEXCLLIST_LEN]; char *exclude[SMB_PI_MAX_NETWORKS]; int nexclude = 0; int i, rc; if ((rc = smb_nic_hlist_create(&hlist)) != SMB_NIC_SUCCESS) return (rc); smb_niclist.nl_cnt = 0; smb_niclist.nl_seqnum = random(); smb_niclist.nl_hcnt = hlist.h_num; smb_niclist.nl_nics = calloc(hlist.h_ifnum, sizeof (smb_nic_t)); if (smb_niclist.nl_nics == NULL) { smb_nic_hlist_destroy(&hlist); return (SMB_NIC_NO_MEMORY); } *excludestr = '\0'; (void) smb_config_getstr(SMB_CI_WINS_EXCL, excludestr, sizeof (excludestr)); nexclude = smb_nic_nbt_get_exclude_list(excludestr, exclude, SMB_PI_MAX_NETWORKS); nc = smb_niclist.nl_nics; iflist = list_head(&hlist.h_list); do { for (i = 0; i < iflist->if_num; i++) { ifname = iflist->if_names[i]; if (smb_nic_getinfo(ifname, nc, AF_INET) != SMB_NIC_SUCCESS) { if (smb_nic_getinfo(ifname, nc, AF_INET6) != SMB_NIC_SUCCESS) { continue; } } (void) strlcpy(nc->nic_host, iflist->if_host, sizeof (nc->nic_host)); (void) strlcpy(nc->nic_cmnt, iflist->if_cmnt, sizeof (nc->nic_cmnt)); smb_tonetbiosname(nc->nic_host, nc->nic_nbname, 0x00); if (strchr(ifname, ':')) nc->nic_smbflags |= SMB_NICF_ALIAS; if (smb_nic_nbt_exclude(nc, (const char **)exclude, nexclude)) nc->nic_smbflags |= SMB_NICF_NBEXCL; smb_niclist.nl_cnt++; nc++; } } while ((iflist = list_next(&hlist.h_list, iflist)) != NULL); smb_nic_hlist_destroy(&hlist); return (SMB_NIC_SUCCESS); } static void smb_nic_list_destroy(void) { free(smb_niclist.nl_nics); smb_niclist.nl_nics = NULL; smb_niclist.nl_cnt = 0; } static int smb_nic_getinfo(char *interface, smb_nic_t *nc, int family) { struct lifreq lifrr; int s; boolean_t isv6; struct sockaddr_in6 *sin6; struct sockaddr_in *sin; if ((s = socket(family, SOCK_DGRAM, IPPROTO_IP)) < 0) { return (SMB_NIC_SOCK); } (void) strlcpy(lifrr.lifr_name, interface, sizeof (lifrr.lifr_name)); if (ioctl(s, SIOCGLIFADDR, &lifrr) < 0) { (void) close(s); return (SMB_NIC_IOCTL); } isv6 = (lifrr.lifr_addr.ss_family == AF_INET6); if (isv6) { sin6 = (struct sockaddr_in6 *)(&lifrr.lifr_addr); nc->nic_ip.a_ipv6 = sin6->sin6_addr; nc->nic_ip.a_family = AF_INET6; } else { sin = (struct sockaddr_in *)(&lifrr.lifr_addr); nc->nic_ip.a_ipv4 = (in_addr_t)(sin->sin_addr.s_addr); nc->nic_ip.a_family = AF_INET; } if (smb_inet_iszero(&nc->nic_ip)) { (void) close(s); return (SMB_NIC_BAD_DATA); } /* there is no broadcast or netmask for v6 */ if (!isv6) { if (ioctl(s, SIOCGLIFBRDADDR, &lifrr) < 0) { (void) close(s); return (SMB_NIC_IOCTL); } sin = (struct sockaddr_in *)&lifrr.lifr_broadaddr; nc->nic_bcast = (uint32_t)sin->sin_addr.s_addr; if (ioctl(s, SIOCGLIFNETMASK, &lifrr) < 0) { (void) close(s); return (SMB_NIC_IOCTL); } sin = (struct sockaddr_in *)&lifrr.lifr_addr; nc->nic_mask = (uint32_t)sin->sin_addr.s_addr; } if (ioctl(s, SIOCGLIFFLAGS, &lifrr) < 0) { (void) close(s); return (SMB_NIC_IOCTL); } nc->nic_sysflags = lifrr.lifr_flags; (void) strlcpy(nc->nic_ifname, interface, sizeof (nc->nic_ifname)); (void) close(s); return (SMB_NIC_SUCCESS); } /* * smb_nic_hlist_create * * Creates a list of hosts and their associated interfaces. * If host database exists the information is retrieved from * the database, otherwise it's retrieved from OS. * * The allocated memories for the returned list should be freed * by calling smb_nic_hlist_destroy() */ static int smb_nic_hlist_create(smb_hosts_t *hlist) { int rc; list_create(&hlist->h_list, sizeof (smb_hostifs_t), offsetof(smb_hostifs_t, if_lnd)); hlist->h_num = 0; hlist->h_ifnum = 0; if (smb_nic_dbexists() && smb_nic_dbvalidate()) { rc = smb_nic_hlist_dbget(hlist); errno = EBADF; } else { rc = smb_nic_hlist_sysget(hlist); } if (rc != SMB_NIC_SUCCESS) smb_nic_hlist_destroy(hlist); return (rc); } static void smb_nic_hlist_destroy(smb_hosts_t *hlist) { smb_hostifs_t *iflist; if (hlist == NULL) return; while ((iflist = list_head(&hlist->h_list)) != NULL) { list_remove(&hlist->h_list, iflist); smb_nic_iflist_destroy(iflist); } list_destroy(&hlist->h_list); } static void smb_close_sockets(int s4, int s6) { if (s4) (void) close(s4); if (s6) (void) close(s6); } /* * smb_nic_hlist_sysget * * Get the list of currently plumbed and up interface names. The loopback (lo0) * port is ignored */ static int smb_nic_hlist_sysget(smb_hosts_t *hlist) { smb_hostifs_t *iflist; struct lifconf lifc; struct lifreq lifrl; struct lifreq *lifrp; char *ifname; int ifnum; int i; int s4, s6; struct lifnum lifn; iflist = malloc(sizeof (smb_hostifs_t)); if (iflist == NULL) return (SMB_NIC_NO_MEMORY); bzero(iflist, sizeof (smb_hostifs_t)); if (smb_gethostname(iflist->if_host, sizeof (iflist->if_host), SMB_CASE_PRESERVE) < 0) { free(iflist); return (SMB_NIC_NO_HOST); } (void) smb_config_getstr(SMB_CI_SYS_CMNT, iflist->if_cmnt, sizeof (iflist->if_cmnt)); if ((s4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { free(iflist); return (SMB_NIC_SOCK); } s6 = socket(AF_INET6, SOCK_DGRAM, 0); lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = 0; if (ioctl(s4, SIOCGLIFNUM, (char *)&lifn) < 0) { smb_close_sockets(s4, s6); free(iflist); syslog(LOG_ERR, "hlist_sysget: SIOCGLIFNUM errno=%d", errno); return (SMB_NIC_IOCTL); } lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq); lifc.lifc_buf = malloc(lifc.lifc_len); if (lifc.lifc_buf == NULL) { smb_close_sockets(s4, s6); free(iflist); return (SMB_NIC_NO_MEMORY); } bzero(lifc.lifc_buf, lifc.lifc_len); lifc.lifc_family = AF_UNSPEC; lifc.lifc_flags = 0; if (ioctl(s4, SIOCGLIFCONF, (char *)&lifc) < 0) { smb_close_sockets(s4, s6); free(iflist); free(lifc.lifc_buf); return (SMB_NIC_IOCTL); } lifrp = lifc.lifc_req; ifnum = lifc.lifc_len / sizeof (struct lifreq); hlist->h_num = 0; for (i = 0; i < ifnum; i++, lifrp++) { if ((iflist->if_num > 0) && smb_duplicate_nic(iflist, lifrp)) continue; /* * Get the flags so that we can skip the loopback interface */ (void) memset(&lifrl, 0, sizeof (lifrl)); (void) strlcpy(lifrl.lifr_name, lifrp->lifr_name, sizeof (lifrl.lifr_name)); if (ioctl(s4, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) { if ((s6 < 0) || (ioctl(s6, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0)) { smb_close_sockets(s4, s6); free(lifc.lifc_buf); smb_nic_iflist_destroy(iflist); return (SMB_NIC_IOCTL); } } if (lifrl.lifr_flags & IFF_LOOPBACK) { continue; } if ((lifrl.lifr_flags & IFF_UP) == 0) { continue; } ifname = strdup(lifrp->lifr_name); if (ifname == NULL) { smb_close_sockets(s4, s6); free(lifc.lifc_buf); smb_nic_iflist_destroy(iflist); return (SMB_NIC_NO_MEMORY); } iflist->if_names[iflist->if_num++] = ifname; } hlist->h_ifnum = iflist->if_num; hlist->h_num = 1; smb_close_sockets(s4, s6); free(lifc.lifc_buf); list_insert_tail(&hlist->h_list, iflist); return (SMB_NIC_SUCCESS); } static boolean_t smb_duplicate_nic(smb_hostifs_t *iflist, struct lifreq *lifrp) { int j; /* * throw out duplicate names */ for (j = 0; j < iflist->if_num; j++) { if (strcmp(iflist->if_names[j], lifrp->lifr_name) == 0) return (B_TRUE); } return (B_FALSE); } static int smb_nic_hlist_dbget(smb_hosts_t *hlist) { smb_hostifs_t *iflist; sqlite *db; sqlite_vm *vm; int err = SMB_NIC_SUCCESS; const char **values; char *sql; char *errmsg = NULL; int ncol, rc; sql = sqlite_mprintf("SELECT * FROM hosts"); if (sql == NULL) return (SMB_NIC_NO_MEMORY); db = smb_nic_dbopen(SMB_NIC_DB_ORD); if (db == NULL) { sqlite_freemem(sql); return (SMB_NIC_DBOPEN_FAILED); } rc = sqlite_compile(db, sql, NULL, &vm, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { smb_nic_dbclose(db); syslog(LOG_ERR, "Failed to query hosts info from host " \ "database. Unable to create virtual machine (%s).", NULL_MSGCHK(errmsg)); return (SMB_NIC_DB_ERROR); } do { rc = sqlite_step(vm, &ncol, &values, NULL); if (rc == SQLITE_ROW) { if (ncol != SMB_NIC_HTBL_NCOL) { err = SMB_NIC_DB_ERROR; break; } if ((iflist = smb_nic_iflist_decode(values, &err)) == NULL) { break; } list_insert_tail(&hlist->h_list, iflist); hlist->h_num++; hlist->h_ifnum += iflist->if_num; } } while (rc == SQLITE_ROW); if (rc != SQLITE_DONE && err == SMB_NIC_SUCCESS) { /* set this error if no previous error */ err = SMB_LGRP_DBEXEC_FAILED; } rc = sqlite_finalize(vm, &errmsg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to query hosts info from host " \ "database. Unable to destroy virtual machine (%s).", NULL_MSGCHK(errmsg)); if (err == SMB_NIC_SUCCESS) { /* set this error if no previous error */ err = SMB_NIC_DB_ERROR; } } smb_nic_dbclose(db); return (err); } static smb_hostifs_t * smb_nic_iflist_decode(const char **values, int *err) { smb_hostifs_t *iflist; char *host; char *cmnt; char *ifnames; char *lasts; char *ifname; int if_num = 0; host = (char *)values[SMB_NIC_HTBL_HOST]; cmnt = (char *)values[SMB_NIC_HTBL_CMNT]; ifnames = (char *)values[SMB_NIC_HTBL_IFS]; if ((host == NULL) || (ifnames == NULL)) { *err = SMB_NIC_INVALID_ARG; return (NULL); } iflist = malloc(sizeof (smb_hostifs_t)); if (iflist == NULL) { *err = SMB_NIC_NO_MEMORY; return (NULL); } bzero(iflist, sizeof (smb_hostifs_t)); (void) strlcpy(iflist->if_host, host, sizeof (iflist->if_host)); (void) strlcpy(iflist->if_cmnt, (cmnt) ? cmnt : "", sizeof (iflist->if_cmnt)); if ((ifname = strtok_r(ifnames, ",", &lasts)) == NULL) { *err = SMB_NIC_BAD_DATA; return (NULL); } iflist->if_names[if_num++] = strdup(ifname); while ((ifname = strtok_r(NULL, ",", &lasts)) != NULL) iflist->if_names[if_num++] = strdup(ifname); iflist->if_num = if_num; for (if_num = 0; if_num < iflist->if_num; if_num++) { if (iflist->if_names[if_num] == NULL) { smb_nic_iflist_destroy(iflist); *err = SMB_NIC_NO_MEMORY; return (NULL); } } *err = SMB_NIC_SUCCESS; return (iflist); } /* * smb_nic_iflist_destroy * * Frees allocated memory for the given IF names lists. */ static void smb_nic_iflist_destroy(smb_hostifs_t *iflist) { int i; if (iflist == NULL) return; for (i = 0; i < iflist->if_num; i++) free(iflist->if_names[i]); free(iflist); } /* * Functions to manage host/interface database * * Each entry in the hosts table associates a hostname with a * list of interface names. The host/interface association could * be added by calling smb_nic_addhost() and could be removed by * calling smb_nic_delhost(). If the database exists and it contains * valid information then the inteface list wouldn't be obtained * from system using ioctl. */ /* * smb_nic_dbcreate * * Creates the host database based on the defined SQL statement. * It also initializes db_info table. */ static int smb_nic_dbcreate(void) { sqlite *db = NULL; char *errmsg = NULL; int rc, err = SMB_NIC_SUCCESS; (void) unlink(SMB_NIC_DB_NAME); db = sqlite_open(SMB_NIC_DB_NAME, 0600, &errmsg); if (db == NULL) { syslog(LOG_ERR, "Failed to create host database (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); return (SMB_NIC_DBOPEN_FAILED); } sqlite_busy_timeout(db, SMB_NIC_DB_TIMEOUT); rc = sqlite_exec(db, "BEGIN TRANSACTION", NULL, NULL, &errmsg); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to create host database. Unable to " \ "begin database transaction (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); sqlite_close(db); return (SMB_NIC_DBEXEC_FAILED); } if (sqlite_exec(db, SMB_NIC_DB_SQL, NULL, NULL, &errmsg) == SQLITE_OK) { rc = sqlite_exec(db, "COMMIT TRANSACTION", NULL, NULL, &errmsg); if (rc == SQLITE_OK) err = smb_nic_dbsetinfo(db); if (err != SMB_NIC_SUCCESS) rc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); } else { syslog(LOG_ERR, "Failed to create host database. Unable to " \ "initialize host database (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); rc = sqlite_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, &errmsg); } if (rc != SQLITE_OK) { /* this is bad - database may be left in a locked state */ syslog(LOG_ERR, "Failed to create host database. Unable to " \ "close a transaction (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); err = SMB_NIC_DBINIT_FAILED; } (void) sqlite_close(db); return (err); } /* * smb_nic_dbopen * * Opens host database with the given mode. */ static sqlite * smb_nic_dbopen(int mode) { sqlite *db; char *errmsg = NULL; db = sqlite_open(SMB_NIC_DB_NAME, mode, &errmsg); if (db == NULL) { syslog(LOG_ERR, "Failed to open host database: %s (%s).", SMB_NIC_DB_NAME, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); } return (db); } /* * smb_nic_dbclose * * Closes the given database handle */ static void smb_nic_dbclose(sqlite *db) { if (db) { sqlite_close(db); } } static boolean_t smb_nic_dbexists(void) { return (access(SMB_NIC_DB_NAME, F_OK) == 0); } static boolean_t smb_nic_dbvalidate(void) { sqlite *db; char *errmsg = NULL; char *sql; char **result; int nrow, ncol; boolean_t check = B_TRUE; int rc; sql = sqlite_mprintf("SELECT * FROM db_info"); if (sql == NULL) return (B_FALSE); db = smb_nic_dbopen(SMB_NIC_DB_ORW); if (db == NULL) { sqlite_freemem(sql); return (B_FALSE); } rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to validate host database. Unable " \ "to get database information (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); smb_nic_dbclose(db); return (B_FALSE); } if (nrow != 1 || ncol != 3) { syslog(LOG_ERR, "Failed to validate host database: bad " \ "db_info table."); sqlite_free_table(result); smb_nic_dbclose(db); return (B_FALSE); } if ((atoi(result[3]) != SMB_NIC_DB_VERMAJOR) || (atoi(result[4]) != SMB_NIC_DB_VERMINOR) || (atoi(result[5]) != SMB_NIC_DB_MAGIC)) { syslog(LOG_ERR, "Failed to validate host database: bad " \ "db_info content."); sqlite_free_table(result); smb_nic_dbclose(db); return (B_FALSE); } sqlite_free_table(result); sql = sqlite_mprintf("SELECT hostname FROM hosts"); if (sql == NULL) { smb_nic_dbclose(db); return (B_FALSE); } rc = sqlite_get_table(db, sql, &result, &nrow, &ncol, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to validate host database. Unable " \ "to query for host (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); smb_nic_dbclose(db); return (B_FALSE); } sqlite_free_table(result); if (nrow == 0) /* No hosts in the database */ check = B_FALSE; smb_nic_dbclose(db); return (check); } static int smb_nic_dbaddhost(const char *host, const char *cmnt, char *if_list) { sqlite *db; char *sql; char *errmsg; int rc, err = SMB_NIC_SUCCESS; sql = sqlite_mprintf("REPLACE INTO hosts (hostname, comment, ifnames)" "VALUES ('%s', '%q', '%s')", host, (cmnt) ? cmnt : "", if_list); if (sql == NULL) return (SMB_NIC_NO_MEMORY); db = smb_nic_dbopen(SMB_NIC_DB_ORW); if (db == NULL) { sqlite_freemem(sql); return (SMB_NIC_DBOPEN_FAILED); } rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); smb_nic_dbclose(db); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to add host %s to host database (%s).", host, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); err = SMB_NIC_INSERT_FAILED; } return (err); } static int smb_nic_dbdelhost(const char *host) { sqlite *db; char *sql; char *errmsg; int rc, err = SMB_NIC_SUCCESS; sql = sqlite_mprintf("DELETE FROM hosts WHERE hostname = '%s'", host); if (sql == NULL) return (SMB_NIC_NO_MEMORY); db = smb_nic_dbopen(SMB_NIC_DB_ORW); if (db == NULL) { sqlite_freemem(sql); return (SMB_NIC_DBOPEN_FAILED); } rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); smb_nic_dbclose(db); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to delete host %s from host " \ "database (%s).", host, NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); err = SMB_NIC_DELETE_FAILED; } return (err); } /* * smb_nic_dbsetinfo * * Initializes the db_info table upon database creation. */ static int smb_nic_dbsetinfo(sqlite *db) { char *errmsg = NULL; char *sql; int rc, err = SMB_NIC_SUCCESS; sql = sqlite_mprintf("INSERT INTO db_info (ver_major, ver_minor," " magic) VALUES (%d, %d, %d)", SMB_NIC_DB_VERMAJOR, SMB_NIC_DB_VERMINOR, SMB_NIC_DB_MAGIC); if (sql == NULL) return (SMB_NIC_NO_MEMORY); rc = sqlite_exec(db, sql, NULL, NULL, &errmsg); sqlite_freemem(sql); if (rc != SQLITE_OK) { syslog(LOG_ERR, "Failed to add database information to " \ "host database (%s).", NULL_MSGCHK(errmsg)); sqlite_freemem(errmsg); err = SMB_NIC_DBINIT_ERROR; } return (err); } /* * smb_nic_nbt_get_exclude_list * * Construct an array containing list of i/f names on which NetBIOS traffic is * to be disabled, from a string containing a list of comma separated i/f names. * * Returns the number of i/f on which NetBIOS traffic is to be disabled. */ static int smb_nic_nbt_get_exclude_list(char *excludestr, char **iflist, int max_nifs) { int n = 0; char *entry; bzero(iflist, SMB_PI_MAX_NETWORKS * sizeof (char *)); (void) trim_whitespace(excludestr); (void) strcanon(excludestr, ","); if (*excludestr == '\0') return (0); while (((iflist[n] = strsep(&excludestr, ",")) != NULL) && (n < max_nifs)) { entry = iflist[n]; if (*entry == '\0') continue; n++; } return (n); } /* * smb_nic_nbt_exclude * * Check to see if the given interface name should send NetBIOS traffic or not. * * Returns TRUE if NetBIOS traffic is disabled on an interface name. * Returns FALSE otherwise. */ static boolean_t smb_nic_nbt_exclude(const smb_nic_t *nc, const char **exclude_list, int nexclude) { char buf[INET6_ADDRSTRLEN]; const char *ifname = nc->nic_ifname; int i; if (inet_ntop(AF_INET, &nc->nic_ip, buf, INET6_ADDRSTRLEN) == NULL) buf[0] = '\0'; for (i = 0; i < nexclude; i++) { if (strcmp(ifname, exclude_list[i]) == 0) return (B_TRUE); if ((buf[0] != '\0') && (strcmp(buf, exclude_list[i]) == 0)) return (B_TRUE); } return (B_FALSE); } /* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2019 Nexenta by DDN, Inc. All rights reserved. */ /* * This module provides the interface to the built-in privilege names * and id's. NT privileges are known on the network using strings. Each * system assigns locally unique identifiers (LUID) for use within the * system. Each built-in privilege also has a display-name, which is a * short description of the privilege. The functions here provide an * interface to map between LUIDs, names and display names. */ #include #include #include #include #include static char *smb_priv_getname(uint32_t id); /* * Table of built-in privilege id's, names and display strings. This * table matches the response from an NT4.0 PDC LSARPC service. * Requests for values 0 and 1 return STATUS_NO_SUCH_PRIVILEGE. * * SE_UNSOLICITED_INPUT_NAME/SeUnsolicitedInputPrivilege is defined in * winnt.h but doesn't appear in the list reported by the NT4.0 LSA. */ static smb_privinfo_t priv_table[] = { { 0, "", "", 0 }, { 1, "", "", 0 }, { 2, SE_CREATE_TOKEN_NAME, "Create a token object", 0 }, { 3, SE_ASSIGNPRIMARYTOKEN_NAME, "Replace a process level token", 0 }, { 4, SE_LOCK_MEMORY_NAME, "Lock pages in memory", 0 }, { 5, SE_INCREASE_QUOTA_NAME, "Increase quotas", 0 }, { 6, SE_MACHINE_ACCOUNT_NAME, "Add workstations to domain", 0 }, { 7, SE_TCB_NAME, "Act as part of the operating system", 0 }, { 8, SE_SECURITY_NAME, "Manage auditing and security log", 0 }, { 9, SE_TAKE_OWNERSHIP_NAME, "Take ownership of files or other objects", PF_PRESENTABLE }, { 10, SE_LOAD_DRIVER_NAME, "Load and unload device drivers", 0 }, { 11, SE_SYSTEM_PROFILE_NAME, "Profile system performance", 0 }, { 12, SE_SYSTEMTIME_NAME, "Change the system time", 0 }, { 13, SE_PROF_SINGLE_PROCESS_NAME, "Profile single process", 0 }, { 14, SE_INC_BASE_PRIORITY_NAME, "Increase scheduling priority", 0 }, { 15, SE_CREATE_PAGEFILE_NAME, "Create a pagefile", 0 }, { 16, SE_CREATE_PERMANENT_NAME, "Create permanent shared objects", 0 }, { 17, SE_BACKUP_NAME, "Back up files and directories", PF_PRESENTABLE }, { 18, SE_RESTORE_NAME, "Restore files and directories", PF_PRESENTABLE }, { 19, SE_SHUTDOWN_NAME, "Shut down the system", 0 }, { 20, SE_DEBUG_NAME, "Debug programs", 0 }, { 21, SE_AUDIT_NAME, "Generate security audits", 0 }, { 22, SE_SYSTEM_ENVIRONMENT_NAME, "Modify firmware environment values", 0 }, { 23, SE_CHANGE_NOTIFY_NAME, "Bypass traverse checking", 0 }, { 24, SE_REMOTE_SHUTDOWN_NAME, "Force shutdown from a remote system", 0 }, { 25, SE_READ_FILE_NAME, "Bypass ACL for READ access", PF_PRESENTABLE }, { 26, SE_WRITE_FILE_NAME, "Bypass ACL for WRITE and DELETE access", PF_PRESENTABLE }, }; /* * smb_priv_presentable_num * * Returns number of presentable privileges */ int smb_priv_presentable_num() { int i, num; num = 0; for (i = SE_MIN_LUID; i <= SE_MAX_LUID; i++) if (priv_table[i].flags == PF_PRESENTABLE) num++; return (num); } /* * smb_priv_presentable_ids * * Returns IDs of presentable privileges * Returns 0 in case of invalid parameter and 1 on success. */ int smb_priv_presentable_ids(uint32_t *ids, int num) { int i, j; if (ids == NULL || num <= 0) return (0); for (i = SE_MIN_LUID, j = 0; i <= SE_MAX_LUID; i++) if (priv_table[i].flags == PF_PRESENTABLE) ids[j++] = priv_table[i].id; return (1); } /* * smb_priv_getbyvalue * * Return the privilege info for the specified id (low part of the LUID). * Returns a null pointer if id is out-of-range. */ smb_privinfo_t * smb_priv_getbyvalue(uint32_t id) { if (id < SE_MIN_LUID || id > SE_MAX_LUID) return (0); return (&priv_table[id]); } /* * smb_priv_getbyname * * Return the privilege info for the specified name. Returns a null * pointer if we can't find a matching name in the table. */ smb_privinfo_t * smb_priv_getbyname(char *name) { smb_privinfo_t *entry; int i; if (name == 0) return (0); for (i = SE_MIN_LUID; i <= SE_MAX_LUID; ++i) { entry = &priv_table[i]; if (smb_strcasecmp(name, entry->name, 0) == 0) return (entry); } return (0); } /* * smb_privset_size * * Returns the memory block size needed to keep a complete * set of privileges in a smb_privset_t structure. */ int smb_privset_size() { int pcnt = SE_MAX_LUID - SE_MIN_LUID + 1; return (2 * sizeof (uint32_t) + pcnt * sizeof (smb_luid_attrs_t)); } /* * smb_privset_validate * * Validates the given privilege set structure * Returns 1 if the structure is Ok, otherwise returns 0. */ int smb_privset_validate(smb_privset_t *privset) { int count; uint32_t i; if (privset == 0) { return (0); } count = SE_MAX_LUID - SE_MIN_LUID + 1; if (privset->priv_cnt != count) { return (0); } for (i = 0; i < count; i++) { if (privset->priv[i].luid.hi_part != 0) { return (0); } if (privset->priv[i].luid.lo_part != i + SE_MIN_LUID) { return (0); } } return (1); } /* * smb_privset_init * * initialize all privileges in disable state. */ void smb_privset_init(smb_privset_t *privset) { int count; uint32_t i; if (privset == 0) return; count = SE_MAX_LUID - SE_MIN_LUID + 1; privset->priv_cnt = count; privset->control = 0; for (i = 0; i < count; i++) { privset->priv[i].luid.hi_part = 0; privset->priv[i].luid.lo_part = i + SE_MIN_LUID; privset->priv[i].attrs = 0; } } /* * smb_privset_new * * Allocate memory and initialize all privileges in disable state. * Returns pointer to allocated space or NULL if there is not * enough memory. */ smb_privset_t * smb_privset_new() { smb_privset_t *privset; privset = malloc(smb_privset_size()); if (privset == NULL) return (NULL); smb_privset_init(privset); return (privset); } /* * smb_privset_copy * * Copy privleges information specified by 'src' to the * buffer specified by dst. */ void smb_privset_copy(smb_privset_t *dst, smb_privset_t *src) { if (src == 0 || dst == 0) return; (void) memcpy(dst, src, smb_privset_size()); } /* * smb_privset_merge * * Enable the privileges that are enabled in src in dst */ void smb_privset_merge(smb_privset_t *dst, smb_privset_t *src) { int i; if (src == NULL || dst == NULL) return; for (i = 0; i < src->priv_cnt; i++) { if (src->priv[i].attrs == SE_PRIVILEGE_ENABLED) smb_privset_enable(dst, src->priv[i].luid.lo_part); } } /* * smb_privset_free * * This will free the memory allocated by the 'privset'. */ void smb_privset_free(smb_privset_t *privset) { free(privset); } void smb_privset_enable(smb_privset_t *privset, uint32_t id) { int i; if (privset == NULL) return; for (i = 0; i < privset->priv_cnt; i++) { if (privset->priv[i].luid.lo_part == id) privset->priv[i].attrs = SE_PRIVILEGE_ENABLED; } } void smb_privset_log(smb_privset_t *privset) { smb_luid_t *luid; int i, ecnt; if (privset == NULL) return; for (i = 0, ecnt = 0; i < privset->priv_cnt; ++i) { if (privset->priv[i].attrs != 0) { ecnt++; } } syslog(LOG_DEBUG, " Privilege Count: %d (Enable=%d)", privset->priv_cnt, ecnt); for (i = 0; i < privset->priv_cnt; ++i) { if (privset->priv[i].attrs != 0) { luid = &privset->priv[i].luid; syslog(LOG_DEBUG, " %s", smb_priv_getname(luid->lo_part)); } } } int smb_privset_query(smb_privset_t *privset, uint32_t id) { int i; if (privset == NULL) return (0); for (i = 0; privset->priv_cnt; i++) { if (privset->priv[i].luid.lo_part == id) { if (privset->priv[i].attrs == SE_PRIVILEGE_ENABLED) return (1); else return (0); } } return (0); } static char * smb_priv_getname(uint32_t id) { if (id < SE_MIN_LUID || id > SE_MAX_LUID) return ("Unknown Privilege"); return (priv_table[id].name); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2017 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_PASSWD "/var/smb/smbpasswd" #define SMB_OPASSWD "/var/smb/osmbpasswd" #define SMB_PASSTEMP "/var/smb/ptmp" #define SMB_PASSLCK "/var/smb/.pwd.lock" #define SMB_PWD_DISABLE "*DIS*" #define SMB_PWD_BUFSIZE 256 #define S_WAITTIME 15 typedef enum { SMB_PWD_NAME = 0, SMB_PWD_UID, SMB_PWD_LMHASH, SMB_PWD_NTHASH, SMB_PWD_NARG } smb_pwdarg_t; static struct flock flock = { 0, 0, 0, 0, 0, 0 }; static pid_t lck_pid = 0; /* process's pid at last lock */ static thread_t lck_tid = 0; /* thread that holds the lock */ static int fildes = -1; static mutex_t lck_lock = DEFAULTMUTEX; static void *smb_pwd_hdl = NULL; static struct { smb_passwd_t *(*pwop_getpwnam)(const char *, smb_passwd_t *); smb_passwd_t *(*pwop_getpwuid)(uid_t, smb_passwd_t *); int (*pwop_setcntl)(const char *, int); int (*pwop_setpasswd)(const char *, const char *); int (*pwop_num)(void); int (*pwop_iteropen)(smb_pwditer_t *); smb_luser_t *(*pwop_iterate)(smb_pwditer_t *); void (*pwop_iterclose)(smb_pwditer_t *); } smb_pwd_ops; static int smb_pwd_lock(void); static int smb_pwd_unlock(void); static int smb_pwd_flck(void); static int smb_pwd_fulck(void); /* * buffer structure used by smb_pwd_fgetent/smb_pwd_fputent */ typedef struct smb_pwbuf { char pw_buf[SMB_PWD_BUFSIZE]; smb_passwd_t *pw_pwd; } smb_pwbuf_t; /* * flag values used with smb_pwd_fgetent */ #define SMB_PWD_GETF_ALL 1 /* get all the account info */ #define SMB_PWD_GETF_NOPWD 2 /* password is not needed */ static smb_pwbuf_t *smb_pwd_fgetent(FILE *, smb_pwbuf_t *, uint32_t); static int smb_pwd_fputent(FILE *, const smb_pwbuf_t *); static int smb_pwd_chgpwent(smb_passwd_t *, const char *, int); static int smb_pwd_update(const char *, const char *, int); /* * Local Users Cache * * Simplifying assumptions * * o smbpasswd is a service private file and shouldn't be edited manually * o accounts are only added/modified via passwd and/or smbadm CLIs * o accounts are not removed but disabled using smbadm CLI * o editing smbpasswd manually might result in cache inconsistency * * Cache is created and populated upon service startup. * Cache is updated each time users list is requested if there's been * any change in smbpasswd file. The change criteria is smbpasswd's * modification timestamp. */ /* * User cache handle */ typedef struct smb_uchandle { avl_tree_t uc_cache; rwlock_t uc_cache_lck; timestruc_t uc_timestamp; uint32_t uc_refcnt; uint32_t uc_state; mutex_t uc_mtx; cond_t uc_cv; } smb_uchandle_t; #define SMB_UCHS_NOCACHE 0 #define SMB_UCHS_CREATED 1 #define SMB_UCHS_UPDATING 2 #define SMB_UCHS_UPDATED 3 #define SMB_UCHS_DESTROYING 4 /* * User cache node */ typedef struct smb_ucnode { smb_luser_t cn_user; avl_node_t cn_link; } smb_ucnode_t; static void smb_lucache_create(void); static void smb_lucache_destroy(void); static void smb_lucache_update(void); static int smb_lucache_num(void); static int smb_lucache_lock(void); static void smb_lucache_unlock(void); static int smb_lucache_do_update(void); static void smb_lucache_flush(void); static smb_uchandle_t smb_uch; /* * smb_pwd_init * * Initializes the cache if requested. * Checks to see if a password management utility library * is interposed. If yes then it'll initializes smb_pwd_ops * structure with function pointers from this library. */ void smb_pwd_init(boolean_t create_cache) { if (create_cache) { smb_lucache_create(); #if 0 /* * This pre-loading of the cache results in idmapd requests. * With the change to allow idmapd to call into libsmb to * map names and SIDs, this creates a circular startup * dependency. This call has been temporarily disabled to * avoid this issue. It can be enabled when the name/SID * lookup can be done directly on the LSA service. */ smb_lucache_update(); #endif } smb_pwd_hdl = smb_dlopen(); if (smb_pwd_hdl == NULL) return; bzero((void *)&smb_pwd_ops, sizeof (smb_pwd_ops)); smb_pwd_ops.pwop_getpwnam = (smb_passwd_t *(*)())dlsym(smb_pwd_hdl, "smb_pwd_getpwnam"); smb_pwd_ops.pwop_getpwuid = (smb_passwd_t *(*)())dlsym(smb_pwd_hdl, "smb_pwd_getpwuid"); smb_pwd_ops.pwop_setcntl = (int (*)())dlsym(smb_pwd_hdl, "smb_pwd_setcntl"); smb_pwd_ops.pwop_setpasswd = (int (*)())dlsym(smb_pwd_hdl, "smb_pwd_setpasswd"); smb_pwd_ops.pwop_num = (int (*)())dlsym(smb_pwd_hdl, "smb_pwd_num"); smb_pwd_ops.pwop_iteropen = (int (*)())dlsym(smb_pwd_hdl, "smb_pwd_iteropen"); smb_pwd_ops.pwop_iterclose = (void (*)())dlsym(smb_pwd_hdl, "smb_pwd_iterclose"); smb_pwd_ops.pwop_iterate = (smb_luser_t *(*)())dlsym(smb_pwd_hdl, "smb_pwd_iterate"); if (smb_pwd_ops.pwop_getpwnam == NULL || smb_pwd_ops.pwop_getpwuid == NULL || smb_pwd_ops.pwop_setcntl == NULL || smb_pwd_ops.pwop_setpasswd == NULL || smb_pwd_ops.pwop_num == NULL || smb_pwd_ops.pwop_iteropen == NULL || smb_pwd_ops.pwop_iterclose == NULL || smb_pwd_ops.pwop_iterate == NULL) { smb_dlclose(smb_pwd_hdl); smb_pwd_hdl = NULL; /* If error or function(s) are missing, use original lib */ bzero((void *)&smb_pwd_ops, sizeof (smb_pwd_ops)); } } /* * smb_pwd_fini * * Destroys the cache. * Closes interposed library. */ void smb_pwd_fini(void) { smb_lucache_destroy(); smb_dlclose(smb_pwd_hdl); smb_pwd_hdl = NULL; bzero((void *)&smb_pwd_ops, sizeof (smb_pwd_ops)); } /* * smb_pwd_getpwnam * * Returns a smb password structure for the given user name. * smbpw is a pointer to a buffer allocated by the caller. * * Returns NULL upon failure. */ smb_passwd_t * smb_pwd_getpwnam(const char *name, smb_passwd_t *smbpw) { boolean_t found = B_FALSE; smb_pwbuf_t pwbuf; FILE *fp; int err; if (smb_pwd_ops.pwop_getpwnam != NULL) return (smb_pwd_ops.pwop_getpwnam(name, smbpw)); err = smb_pwd_lock(); if (err != SMB_PWE_SUCCESS) { syslog(LOG_WARNING, "smb_pwdutil: lock failed, err=%d", err); return (NULL); } if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) { syslog(LOG_WARNING, "smb_pwdutil: open failed, %m"); (void) smb_pwd_unlock(); return (NULL); } pwbuf.pw_pwd = smbpw; while (smb_pwd_fgetent(fp, &pwbuf, SMB_PWD_GETF_ALL) != NULL) { if (strcasecmp(name, smbpw->pw_name) == 0) { found = B_TRUE; break; } } (void) fclose(fp); (void) smb_pwd_unlock(); if (!found) { bzero(smbpw, sizeof (smb_passwd_t)); return (NULL); } return (smbpw); } /* * smb_pwd_getpwuid * * Returns a smb password structure for the given UID * smbpw is a pointer to a buffer allocated by the caller. * * Returns NULL upon failure. */ smb_passwd_t * smb_pwd_getpwuid(uid_t uid, smb_passwd_t *smbpw) { boolean_t found = B_FALSE; smb_pwbuf_t pwbuf; FILE *fp; int err; if (smb_pwd_ops.pwop_getpwuid != NULL) return (smb_pwd_ops.pwop_getpwuid(uid, smbpw)); err = smb_pwd_lock(); if (err != SMB_PWE_SUCCESS) { syslog(LOG_WARNING, "smb_pwdutil: lock failed, err=%d", err); return (NULL); } if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) { syslog(LOG_WARNING, "smb_pwdutil: open failed, %m"); (void) smb_pwd_unlock(); return (NULL); } pwbuf.pw_pwd = smbpw; while (smb_pwd_fgetent(fp, &pwbuf, SMB_PWD_GETF_ALL) != NULL) { if (uid == smbpw->pw_uid) { found = B_TRUE; break; } } (void) fclose(fp); (void) smb_pwd_unlock(); if (!found) { bzero(smbpw, sizeof (smb_passwd_t)); return (NULL); } return (smbpw); } /* * smb_pwd_setpasswd * * Update/add the given user to the smbpasswd file. */ int smb_pwd_setpasswd(const char *name, const char *password) { if (smb_pwd_ops.pwop_setpasswd != NULL) return (smb_pwd_ops.pwop_setpasswd(name, password)); return (smb_pwd_update(name, password, 0)); } /* * smb_pwd_setcntl * * Change the account state. This can be making the account * disable/enable or removing its LM hash. */ int smb_pwd_setcntl(const char *name, int control) { if (smb_pwd_ops.pwop_setcntl != NULL) return (smb_pwd_ops.pwop_setcntl(name, control)); if (control == 0) return (SMB_PWE_SUCCESS); return (smb_pwd_update(name, NULL, control)); } /* * smb_pwd_num * * Returns the number of cached local users */ int smb_pwd_num(void) { if (smb_pwd_ops.pwop_num != NULL) return (smb_pwd_ops.pwop_num()); smb_lucache_update(); return (smb_lucache_num()); } /* * smb_pwd_iteropen * * Initalizes the given iterator handle. * This handle will be used to iterate the users cache * by the caller. The cache will be locked for read and it * will remain locked until smb_pwd_iterclose() is called. */ int smb_pwd_iteropen(smb_pwditer_t *iter) { if (iter == NULL) return (SMB_PWE_INVALID_PARAM); if (smb_pwd_ops.pwop_iteropen != NULL) return (smb_pwd_ops.pwop_iteropen(iter)); iter->spi_next = NULL; smb_lucache_update(); return (smb_lucache_lock()); } /* * smb_pwd_iterate * * Scans through users cache using the given iterator */ smb_luser_t * smb_pwd_iterate(smb_pwditer_t *iter) { smb_ucnode_t *ucnode; if (iter == NULL) return (NULL); if (smb_pwd_ops.pwop_iterate != NULL) return (smb_pwd_ops.pwop_iterate(iter)); if (iter->spi_next == NULL) ucnode = avl_first(&smb_uch.uc_cache); else ucnode = AVL_NEXT(&smb_uch.uc_cache, iter->spi_next); if ((iter->spi_next = ucnode) != NULL) return (&ucnode->cn_user); return (NULL); } /* * smb_pwd_iterclose * * Closes the given iterator. Effectively it only unlocks the cache */ void smb_pwd_iterclose(smb_pwditer_t *iter) { if (smb_pwd_ops.pwop_iterclose != NULL) { smb_pwd_ops.pwop_iterclose(iter); return; } if (iter != NULL) smb_lucache_unlock(); } /* * smb_pwd_update * * Updates the password entry of the given user if the user already * has an entry, otherwise it'll add an entry for the user with * given password and control information. */ static int smb_pwd_update(const char *name, const char *password, int control) { struct stat64 stbuf; FILE *src, *dst; int tempfd; int err = SMB_PWE_SUCCESS; smb_pwbuf_t pwbuf; smb_passwd_t smbpw; boolean_t newent = B_TRUE; boolean_t user_disable = B_FALSE; char uxbuf[1024]; struct passwd uxpw; int64_t lm_level; err = smb_pwd_lock(); if (err != SMB_PWE_SUCCESS) return (err); if (stat64(SMB_PASSWD, &stbuf) < 0) { err = SMB_PWE_STAT_FAILED; goto passwd_exit; } if ((tempfd = open(SMB_PASSTEMP, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { err = SMB_PWE_OPEN_FAILED; goto passwd_exit; } if ((dst = fdopen(tempfd, "wF")) == NULL) { err = SMB_PWE_OPEN_FAILED; goto passwd_exit; } if ((src = fopen(SMB_PASSWD, "rF")) == NULL) { err = SMB_PWE_OPEN_FAILED; (void) fclose(dst); (void) unlink(SMB_PASSTEMP); goto passwd_exit; } if (smb_config_getnum(SMB_CI_LM_LEVEL, &lm_level) != SMBD_SMF_OK) lm_level = 4; if (lm_level >= 4) control |= SMB_PWC_NOLM; pwbuf.pw_pwd = &smbpw; /* * copy old password entries to temporary file while replacing * the entry that matches "name" */ while (smb_pwd_fgetent(src, &pwbuf, SMB_PWD_GETF_ALL) != NULL) { if (strcmp(smbpw.pw_name, name) == 0) { if ((control & SMB_PWC_DELETE) != 0) { /* exclude the entry from the new passwd file */ newent = B_FALSE; err = SMB_PWE_SUCCESS; continue; } err = smb_pwd_chgpwent(&smbpw, password, control); if (err == SMB_PWE_USER_DISABLE) user_disable = B_TRUE; err = smb_pwd_fputent(dst, &pwbuf); newent = B_FALSE; } else { err = smb_pwd_fputent(dst, &pwbuf); } if (err != SMB_PWE_SUCCESS) { (void) fclose(src); (void) fclose(dst); goto passwd_exit; } } if (newent) { if (getpwnam_r(name, &uxpw, uxbuf, sizeof (uxbuf))) { bzero(&smbpw, sizeof (smb_passwd_t)); (void) strlcpy(smbpw.pw_name, uxpw.pw_name, sizeof (smbpw.pw_name)); smbpw.pw_uid = uxpw.pw_uid; (void) smb_pwd_chgpwent(&smbpw, password, control); err = smb_pwd_fputent(dst, &pwbuf); } else { err = SMB_PWE_USER_UNKNOWN; } if (err != SMB_PWE_SUCCESS) { (void) fclose(src); (void) fclose(dst); goto passwd_exit; } } (void) fclose(src); if (fclose(dst) != 0) { err = SMB_PWE_CLOSE_FAILED; goto passwd_exit; /* Don't trust the temporary file */ } /* Rename temp to passwd */ if (unlink(SMB_OPASSWD) && access(SMB_OPASSWD, 0) == 0) { err = SMB_PWE_UPDATE_FAILED; (void) unlink(SMB_PASSTEMP); goto passwd_exit; } if (link(SMB_PASSWD, SMB_OPASSWD) == -1) { err = SMB_PWE_UPDATE_FAILED; (void) unlink(SMB_PASSTEMP); goto passwd_exit; } if (rename(SMB_PASSTEMP, SMB_PASSWD) == -1) { err = SMB_PWE_UPDATE_FAILED; (void) unlink(SMB_PASSTEMP); goto passwd_exit; } (void) chmod(SMB_PASSWD, 0400); passwd_exit: (void) smb_pwd_unlock(); if ((err == SMB_PWE_SUCCESS) && user_disable) err = SMB_PWE_USER_DISABLE; return (err); } /* * smb_pwd_fgetent * * Parse the buffer in the passed pwbuf and fill in the * smb password structure to point to the parsed information. * The entry format is: * * ::: * * Returns a pointer to the passed pwbuf structure on success, * otherwise returns NULL. */ static smb_pwbuf_t * smb_pwd_fgetent(FILE *fp, smb_pwbuf_t *pwbuf, uint32_t flags) { char *argv[SMB_PWD_NARG]; char *pwentry; smb_passwd_t *pw; smb_pwdarg_t i; int lm_len, nt_len; pwentry = pwbuf->pw_buf; if (fgets(pwentry, SMB_PWD_BUFSIZE, fp) == NULL) return (NULL); (void) trim_whitespace(pwentry); for (i = 0; i < SMB_PWD_NARG; ++i) { if ((argv[i] = strsep((char **)&pwentry, ":")) == NULL) return (NULL); } if ((*argv[SMB_PWD_NAME] == '\0') || (*argv[SMB_PWD_UID] == '\0')) return (NULL); pw = pwbuf->pw_pwd; bzero(pw, sizeof (smb_passwd_t)); pw->pw_uid = strtoul(argv[SMB_PWD_UID], 0, 10); (void) strlcpy(pw->pw_name, argv[SMB_PWD_NAME], sizeof (pw->pw_name)); if (strcmp(argv[SMB_PWD_LMHASH], SMB_PWD_DISABLE) == 0) { pw->pw_flags |= SMB_PWF_DISABLE; if (flags != SMB_PWD_GETF_NOPWD) { (void) strcpy((char *)pw->pw_lmhash, SMB_PWD_DISABLE); (void) strcpy((char *)pw->pw_nthash, SMB_PWD_DISABLE); } return (pwbuf); } if (flags == SMB_PWD_GETF_NOPWD) return (pwbuf); lm_len = strlen(argv[SMB_PWD_LMHASH]); if (lm_len == SMBAUTH_HEXHASH_SZ) { (void) hextobin(argv[SMB_PWD_LMHASH], SMBAUTH_HEXHASH_SZ, (char *)pw->pw_lmhash, SMBAUTH_HASH_SZ); pw->pw_flags |= SMB_PWF_LM; } else if (lm_len != 0) { return (NULL); } nt_len = strlen(argv[SMB_PWD_NTHASH]); if (nt_len == SMBAUTH_HEXHASH_SZ) { (void) hextobin(argv[SMB_PWD_NTHASH], SMBAUTH_HEXHASH_SZ, (char *)pw->pw_nthash, SMBAUTH_HASH_SZ); pw->pw_flags |= SMB_PWF_NT; } else if (nt_len != 0) { return (NULL); } return (pwbuf); } /* * smb_pwd_chgpwent * * Updates the given smb_passwd_t structure with given password and * control information. */ static int smb_pwd_chgpwent(smb_passwd_t *smbpw, const char *password, int control) { if (control & SMB_PWC_DISABLE) { /* disable the user */ smbpw->pw_flags |= SMB_PWF_DISABLE; (void) strcpy((char *)smbpw->pw_lmhash, SMB_PWD_DISABLE); (void) strcpy((char *)smbpw->pw_nthash, SMB_PWD_DISABLE); smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); return (SMB_PWE_SUCCESS); } if ((control & SMB_PWC_ENABLE) && (smbpw->pw_flags & SMB_PWF_DISABLE)) { /* enable the user if it's been disabled */ *smbpw->pw_lmhash = '\0'; *smbpw->pw_nthash = '\0'; smbpw->pw_flags &= ~(SMB_PWF_LM | SMB_PWF_NT); return (SMB_PWE_SUCCESS); } /* No password update if account is disabled */ if (smbpw->pw_flags & SMB_PWF_DISABLE) return (SMB_PWE_USER_DISABLE); /* This call was just to update the control flags */ if (password == NULL) return (SMB_PWE_SUCCESS); if (control & SMB_PWC_NOLM) { /* LM hash should not be present */ smbpw->pw_flags &= ~SMB_PWF_LM; *smbpw->pw_lmhash = '\0'; } else { smbpw->pw_flags |= SMB_PWF_LM; (void) smb_auth_lm_hash(password, smbpw->pw_lmhash); } smbpw->pw_flags |= SMB_PWF_NT; (void) smb_auth_ntlm_hash(password, smbpw->pw_nthash); return (SMB_PWE_SUCCESS); } /* * smb_pwd_fputent * * If LM/NTLM hash are present, converts them to hex string * and write them along with user's name and Id to the smbpasswd * file. */ static int smb_pwd_fputent(FILE *fp, const smb_pwbuf_t *pwbuf) { smb_passwd_t *pw = pwbuf->pw_pwd; char hex_nthash[SMBAUTH_HEXHASH_SZ+1]; char hex_lmhash[SMBAUTH_HEXHASH_SZ+1]; int rc; if ((pw->pw_flags & SMB_PWF_LM) == SMB_PWF_LM) { (void) bintohex((char *)pw->pw_lmhash, SMBAUTH_HASH_SZ, hex_lmhash, SMBAUTH_HEXHASH_SZ); hex_lmhash[SMBAUTH_HEXHASH_SZ] = '\0'; } else { (void) strcpy(hex_lmhash, (char *)pw->pw_lmhash); } if ((pw->pw_flags & SMB_PWF_NT) == SMB_PWF_NT) { (void) bintohex((char *)pw->pw_nthash, SMBAUTH_HASH_SZ, hex_nthash, SMBAUTH_HEXHASH_SZ); hex_nthash[SMBAUTH_HEXHASH_SZ] = '\0'; } else { (void) strcpy(hex_nthash, (char *)pw->pw_nthash); } rc = fprintf(fp, "%s:%u:%s:%s\n", pw->pw_name, pw->pw_uid, hex_lmhash, hex_nthash); if (rc <= 0) return (SMB_PWE_WRITE_FAILED); return (SMB_PWE_SUCCESS); } /* * smb_pwd_lock * * A wrapper around smb_pwd_flck() which locks smb password * file so that only one thread at a time is operational. */ static int smb_pwd_lock(void) { int res; res = SMB_PWE_SUCCESS; if (smb_pwd_flck()) { switch (errno) { case EINTR: res = SMB_PWE_BUSY; break; case EACCES: res = SMB_PWE_DENIED; break; default: /* Should not happen */ res = SMB_PWE_SYSTEM_ERROR; break; } } return (res); } /* * smb_pwd_unlock * * A wrapper around smb_pwd_fulck() which unlocks * smb password file. */ static int smb_pwd_unlock(void) { if (smb_pwd_fulck()) return (SMB_PWE_SYSTEM_ERROR); return (SMB_PWE_SUCCESS); } /* * smb_pwd_flck * * Creates a lock file and grabs an exclusive (write) lock on it. */ static int smb_pwd_flck(void) { int seconds = 0; (void) mutex_lock(&lck_lock); for (;;) { if (lck_pid != 0 && lck_pid != getpid()) { /* somebody forked */ lck_pid = 0; lck_tid = 0; } if (lck_tid == 0) { if ((fildes = creat(SMB_PASSLCK, 0600)) == -1) break; flock.l_type = F_WRLCK; if (fcntl(fildes, F_SETLK, &flock) != -1) { lck_pid = getpid(); lck_tid = thr_self(); (void) mutex_unlock(&lck_lock); return (0); } (void) close(fildes); fildes = -1; } if (seconds++ >= S_WAITTIME) { /* * For compatibility with the past, pretend * that we were interrupted by SIGALRM. */ errno = EINTR; break; } (void) mutex_unlock(&lck_lock); (void) sleep(1); (void) mutex_lock(&lck_lock); } (void) mutex_unlock(&lck_lock); return (-1); } /* * smb_pwd_fulck * * Unlocks smb password file for operations done via * this library APIs. */ static int smb_pwd_fulck(void) { (void) mutex_lock(&lck_lock); if (lck_tid == thr_self() && fildes >= 0) { flock.l_type = F_UNLCK; (void) fcntl(fildes, F_SETLK, &flock); (void) close(fildes); fildes = -1; lck_pid = 0; lck_tid = 0; (void) mutex_unlock(&lck_lock); return (0); } (void) mutex_unlock(&lck_lock); return (-1); } /* * Local User Cache Functions * * Local user cache is implemented using AVL tree */ /* * smb_lucache_cmp * * AVL compare function, the key is username. */ static int smb_lucache_cmp(const void *p1, const void *p2) { smb_ucnode_t *u1 = (smb_ucnode_t *)p1; smb_ucnode_t *u2 = (smb_ucnode_t *)p2; int rc; rc = strcmp(u1->cn_user.su_name, u2->cn_user.su_name); if (rc < 0) return (-1); if (rc > 0) return (1); return (0); } /* * smb_lucache_update * * Updates the cache if needed. Whether an update is needed * is determined based on smbpasswd file modification timestamp */ static void smb_lucache_update(void) { struct stat64 stbuf; int rc; (void) mutex_lock(&smb_uch.uc_mtx); switch (smb_uch.uc_state) { default: case SMB_UCHS_NOCACHE: assert(0); (void) mutex_unlock(&smb_uch.uc_mtx); return; case SMB_UCHS_CREATED: case SMB_UCHS_UPDATED: break; case SMB_UCHS_UPDATING: /* Want only one thread executing this function at a time */ (void) mutex_unlock(&smb_uch.uc_mtx); return; case SMB_UCHS_DESTROYING: (void) mutex_unlock(&smb_uch.uc_mtx); return; } /* * smb_pwd_lock() is not called here so it can * be checked quickly whether an updated is needed */ if (stat64(SMB_PASSWD, &stbuf) < 0) { (void) mutex_unlock(&smb_uch.uc_mtx); if (errno != ENOENT) return; /* no smbpasswd file; empty the cache */ smb_lucache_flush(); return; } if (stbuf.st_size == 0) { (void) mutex_unlock(&smb_uch.uc_mtx); /* empty smbpasswd file; empty the cache */ smb_lucache_flush(); return; } if ((smb_uch.uc_timestamp.tv_sec == stbuf.st_mtim.tv_sec) && (smb_uch.uc_timestamp.tv_nsec == stbuf.st_mtim.tv_nsec)) { (void) mutex_unlock(&smb_uch.uc_mtx); /* No changes since the last cache update */ return; } smb_uch.uc_state = SMB_UCHS_UPDATING; smb_uch.uc_refcnt++; (void) mutex_unlock(&smb_uch.uc_mtx); rc = smb_lucache_do_update(); (void) mutex_lock(&smb_uch.uc_mtx); if ((rc == SMB_PWE_SUCCESS) && (stat64(SMB_PASSWD, &stbuf) == 0)) smb_uch.uc_timestamp = stbuf.st_mtim; smb_uch.uc_state = SMB_UCHS_UPDATED; smb_uch.uc_refcnt--; (void) cond_broadcast(&smb_uch.uc_cv); (void) mutex_unlock(&smb_uch.uc_mtx); } /* * smb_lucache_do_update * * This function takes care of updating the AVL tree. * If an entry has been updated, it'll be modified in place. * * New entries will be added to a temporary AVL tree then * passwod file is unlocked and all the new entries will * be transferred to the main cache from the temporary tree. * * This function MUST NOT be called directly */ static int smb_lucache_do_update(void) { avl_tree_t tmp_cache; smb_pwbuf_t pwbuf; smb_passwd_t smbpw; smb_ucnode_t uc_node; smb_ucnode_t *uc_newnode; smb_luser_t *user; smb_sid_t *sid; idmap_stat idm_stat; int rc = SMB_PWE_SUCCESS; void *cookie = NULL; FILE *fp; if ((rc = smb_pwd_lock()) != SMB_PWE_SUCCESS) { syslog(LOG_WARNING, "smb_pwdutil: lock failed, err=%d", rc); return (rc); } if ((fp = fopen(SMB_PASSWD, "rF")) == NULL) { syslog(LOG_WARNING, "smb_pwdutil: open failed, %m"); (void) smb_pwd_unlock(); return (SMB_PWE_OPEN_FAILED); } avl_create(&tmp_cache, smb_lucache_cmp, sizeof (smb_ucnode_t), offsetof(smb_ucnode_t, cn_link)); bzero(&pwbuf, sizeof (smb_pwbuf_t)); pwbuf.pw_pwd = &smbpw; (void) rw_rdlock(&smb_uch.uc_cache_lck); while (smb_pwd_fgetent(fp, &pwbuf, SMB_PWD_GETF_NOPWD) != NULL) { uc_node.cn_user.su_name = smbpw.pw_name; uc_newnode = avl_find(&smb_uch.uc_cache, &uc_node, NULL); if (uc_newnode) { /* update the node info */ uc_newnode->cn_user.su_ctrl = smbpw.pw_flags; continue; } /* create a new node */ if ((uc_newnode = malloc(sizeof (smb_ucnode_t))) == NULL) { rc = SMB_PWE_NO_MEMORY; break; } bzero(uc_newnode, sizeof (smb_ucnode_t)); user = &uc_newnode->cn_user; user->su_ctrl = smbpw.pw_flags; idm_stat = smb_idmap_getsid(smbpw.pw_uid, SMB_IDMAP_USER, &sid); if (idm_stat != IDMAP_SUCCESS) { syslog(LOG_WARNING, "smb_pwdutil: couldn't obtain SID " "for uid=%u (%d)", smbpw.pw_uid, idm_stat); free(uc_newnode); continue; } (void) smb_sid_getrid(sid, &user->su_rid); smb_sid_free(sid); user->su_name = strdup(smbpw.pw_name); if (user->su_name == NULL) { rc = SMB_PWE_NO_MEMORY; free(uc_newnode); break; } avl_add(&tmp_cache, uc_newnode); } (void) rw_unlock(&smb_uch.uc_cache_lck); (void) fclose(fp); (void) smb_pwd_unlock(); /* Destroy the temporary list */ (void) rw_wrlock(&smb_uch.uc_cache_lck); while ((uc_newnode = avl_destroy_nodes(&tmp_cache, &cookie)) != NULL) { avl_add(&smb_uch.uc_cache, uc_newnode); } (void) rw_unlock(&smb_uch.uc_cache_lck); avl_destroy(&tmp_cache); return (rc); } /* * smb_lucache_create * * Creates the AVL tree and initializes the global user cache handle. * This function doesn't populate the cache. * User cache is only created by smbd at startup */ static void smb_lucache_create(void) { (void) mutex_lock(&smb_uch.uc_mtx); if (smb_uch.uc_state != SMB_UCHS_NOCACHE) { (void) mutex_unlock(&smb_uch.uc_mtx); return; } avl_create(&smb_uch.uc_cache, smb_lucache_cmp, sizeof (smb_ucnode_t), offsetof(smb_ucnode_t, cn_link)); smb_uch.uc_state = SMB_UCHS_CREATED; bzero(&smb_uch.uc_timestamp, sizeof (timestruc_t)); smb_uch.uc_refcnt = 0; (void) mutex_unlock(&smb_uch.uc_mtx); } /* * smb_lucache_flush * * Removes and frees all the cache entries */ static void smb_lucache_flush(void) { void *cookie = NULL; smb_ucnode_t *ucnode; (void) rw_wrlock(&smb_uch.uc_cache_lck); while ((ucnode = avl_destroy_nodes(&smb_uch.uc_cache, &cookie)) != NULL) { free(ucnode->cn_user.su_name); free(ucnode->cn_user.su_fullname); free(ucnode->cn_user.su_desc); free(ucnode); } (void) rw_unlock(&smb_uch.uc_cache_lck); } /* * smb_lucache_destroy * * Destroys the cache. * This function is only called in smb_pwd_fini() * User cache is only destroyed by smbd upon shutdown */ static void smb_lucache_destroy(void) { (void) mutex_lock(&smb_uch.uc_mtx); switch (smb_uch.uc_state) { case SMB_UCHS_NOCACHE: case SMB_UCHS_DESTROYING: (void) mutex_unlock(&smb_uch.uc_mtx); return; default: break; } smb_uch.uc_state = SMB_UCHS_DESTROYING; while (smb_uch.uc_refcnt > 0) (void) cond_wait(&smb_uch.uc_cv, &smb_uch.uc_mtx); smb_lucache_flush(); avl_destroy(&smb_uch.uc_cache); smb_uch.uc_state = SMB_UCHS_NOCACHE; (void) mutex_unlock(&smb_uch.uc_mtx); } /* * smb_lucache_lock * * Locks the user cache for reading and also * increment the handle reference count. */ static int smb_lucache_lock(void) { (void) mutex_lock(&smb_uch.uc_mtx); switch (smb_uch.uc_state) { case SMB_UCHS_NOCACHE: assert(0); (void) mutex_unlock(&smb_uch.uc_mtx); return (SMB_PWE_DENIED); case SMB_UCHS_DESTROYING: (void) mutex_unlock(&smb_uch.uc_mtx); return (SMB_PWE_DENIED); } smb_uch.uc_refcnt++; (void) mutex_unlock(&smb_uch.uc_mtx); (void) rw_rdlock(&smb_uch.uc_cache_lck); return (SMB_PWE_SUCCESS); } /* * smb_lucache_unlock * * Unlock the cache */ static void smb_lucache_unlock(void) { (void) rw_unlock(&smb_uch.uc_cache_lck); (void) mutex_lock(&smb_uch.uc_mtx); smb_uch.uc_refcnt--; (void) cond_broadcast(&smb_uch.uc_cv); (void) mutex_unlock(&smb_uch.uc_mtx); } /* * smb_lucache_num * * Returns the number of cache entries */ static int smb_lucache_num(void) { int num; (void) mutex_lock(&smb_uch.uc_mtx); switch (smb_uch.uc_state) { case SMB_UCHS_NOCACHE: assert(0); (void) mutex_unlock(&smb_uch.uc_mtx); return (0); case SMB_UCHS_DESTROYING: (void) mutex_unlock(&smb_uch.uc_mtx); return (0); } (void) mutex_unlock(&smb_uch.uc_mtx); (void) rw_rdlock(&smb_uch.uc_cache_lck); num = (int)avl_numnodes(&smb_uch.uc_cache); (void) rw_unlock(&smb_uch.uc_cache_lck); return (num); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include static int smb_reparse_init(const char *, nvlist_t **); static void smb_reparse_free(nvlist_t *); static int smb_reparse_set(const char *, nvlist_t *); /* * Checks the status of the object specified by 'path' * * Returns 0 and fills 'stat' with the proper status on * success, otherwise returns an error code. */ int smb_reparse_stat(const char *path, uint32_t *stat) { struct stat statbuf; char symbuf[MAXREPARSELEN]; int rptaglen; if (lstat(path, &statbuf) != 0) { if (errno == ENOENT) { *stat = SMB_REPARSE_NOTFOUND; return (0); } return (errno); } if ((statbuf.st_mode & S_IFMT) != S_IFLNK) { *stat = SMB_REPARSE_NOTREPARSE; return (0); } bzero(symbuf, MAXREPARSELEN); if (readlink(path, symbuf, MAXREPARSELEN) == -1) return (errno); rptaglen = strlen(FS_REPARSE_TAG_STR); if (strncmp(symbuf, FS_REPARSE_TAG_STR, rptaglen) != 0) *stat = SMB_REPARSE_NOTREPARSE; else *stat = SMB_REPARSE_ISREPARSE; return (0); } /* * If the reparse point specified by the path already exists * it is updated by given service type and its data. Update means * that if such service type does not already exist, it is added * otherwise it is overwritten by given data. * * If the reparse point does not exist, one is created with given * service type and its data. */ int smb_reparse_svcadd(const char *path, const char *svctype, const char *svcdata) { nvlist_t *nvl; int rc; if ((rc = smb_reparse_init(path, &nvl)) != 0) return (rc); if ((rc = reparse_add(nvl, svctype, svcdata)) != 0) { smb_reparse_free(nvl); return (rc); } rc = smb_reparse_set(path, nvl); smb_reparse_free(nvl); return (rc); } /* * Removes the entry for the given service type from the * specified reparse point. If there is no service entry * left, the reparse point object will be deleted. */ int smb_reparse_svcdel(const char *path, const char *svctype) { nvlist_t *nvl; int rc; if ((rc = smb_reparse_init(path, &nvl)) != 0) return (rc); if ((rc = reparse_remove(nvl, svctype)) != 0) { smb_reparse_free(nvl); return (rc); } if (nvlist_next_nvpair(nvl, NULL) == NULL) { /* list is empty remove the object */ rc = reparse_delete(path); if ((rc != 0) && (rc == ENOENT)) rc = 0; } else { rc = smb_reparse_set(path, nvl); } smb_reparse_free(nvl); return (rc); } /* * Obtains data of the given service type from the specified * reparse point. Function allocates the memory needed to hold * the service data so the caller must free this memory by * calling free(). * * If 'svcdata' is NULL, successful return means that the reparse * point contains a record for the given service type. */ int smb_reparse_svcget(const char *path, const char *svctype, char **svcdata) { nvlist_t *nvl; nvpair_t *nvp; char *stype, *sdata; int rc; if ((rc = smb_reparse_init(path, &nvl)) != 0) return (rc); rc = ENODATA; nvp = nvlist_next_nvpair(nvl, NULL); while (nvp != NULL) { stype = nvpair_name(nvp); if ((stype != NULL) && (strcasecmp(stype, svctype) == 0)) { if ((rc = nvpair_value_string(nvp, &sdata)) != 0) break; if (svcdata != NULL) { if ((*svcdata = strdup(sdata)) == NULL) rc = ENOMEM; } rc = 0; break; } nvp = nvlist_next_nvpair(nvl, nvp); } smb_reparse_free(nvl); return (rc); } /* * Initializes the given nvpair list. * * This function assumes that the object specified by this path * is a reparse point, so it does not do any verification. * * If specified reparse point does not exist the function * returns successfully with an empty nvpair list. * * If the object exists and readlink is successful then nvpair * list is polulated with the reparse service information, otherwise * an error code is returned. */ static int smb_reparse_init(const char *path, nvlist_t **nvl) { char rp_data[MAXREPARSELEN]; int rc; if ((*nvl = reparse_init()) == NULL) return (ENOMEM); bzero(rp_data, MAXREPARSELEN); if ((rc = readlink(path, rp_data, MAXREPARSELEN)) == -1) { if (errno == ENOENT) return (0); reparse_free(*nvl); return (errno); } if ((rc = reparse_parse(rp_data, *nvl)) != 0) { reparse_free(*nvl); return (rc); } return (0); } /* * Frees given nvlist */ static void smb_reparse_free(nvlist_t *nvl) { reparse_free(nvl); } /* * Create a reparse point with given services in the passed * nvlist. If the reparse point already exists, it will be * deleted and a new one with the given data is created. */ static int smb_reparse_set(const char *path, nvlist_t *nvl) { char *rp_data; int rc; if ((rc = reparse_unparse(nvl, &rp_data)) != 0) return (rc); rc = reparse_delete(path); if ((rc != 0) && (rc != ENOENT)) { free(rp_data); return (rc); } rc = reparse_create(path, rp_data); free(rp_data); return (rc); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2017 Nexenta Systems, Inc. All rights reserved. * Copyright 2018 RackTop Systems. */ #include #include extern int smb_pwd_num(void); extern int smb_lgrp_numbydomain(smb_domain_type_t, int *); static uint32_t smb_sam_lookup_user(char *, smb_sid_t **); static uint32_t smb_sam_lookup_group(char *, smb_sid_t **); /* * Local well-known accounts data structure table and prototypes */ typedef struct smb_lwka { uint32_t lwka_rid; char *lwka_name; uint16_t lwka_type; } smb_lwka_t; static smb_lwka_t lwka_tbl[] = { { 500, "Administrator", SidTypeUser }, { 501, "Guest", SidTypeUser }, { 502, "KRBTGT", SidTypeUser }, { 512, "Domain Admins", SidTypeGroup }, { 513, "Domain Users", SidTypeGroup }, { 514, "Domain Guests", SidTypeGroup }, { 516, "Domain Controllers", SidTypeGroup }, { 517, "Cert Publishers", SidTypeGroup }, { 518, "Schema Admins", SidTypeGroup }, { 519, "Enterprise Admins", SidTypeGroup }, { 520, "Global Policy Creator Owners", SidTypeGroup }, { 533, "RAS and IAS Servers", SidTypeGroup } }; #define SMB_LWKA_NUM (sizeof (lwka_tbl)/sizeof (lwka_tbl[0])) static smb_lwka_t *smb_lwka_lookup_name(char *); static smb_lwka_t *smb_lwka_lookup_sid(smb_sid_t *); /* * Looks up the given name in local account databases: * * SMB Local users are looked up in /var/smb/smbpasswd * SMB Local groups are looked up in /var/smb/smbgroup.db * * If the account is found, its information is populated * in the passed smb_account_t structure. Caller must free * allocated memories by calling smb_account_free() upon * successful return. * * The type of account is specified by 'type', which can be user, * alias (local group) or unknown. If the caller doesn't know * whether the name is a user or group name then SidTypeUnknown * should be passed. * * If a local user and group have the same name, the user will * always be picked. Note that this situation cannot happen on * Windows systems. * * If a SMB local user/group is found but it turns out that * it'll be mapped to a domain user/group the lookup is considered * failed and NT_STATUS_NONE_MAPPED is returned. * * Return status: * * NT_STATUS_NOT_FOUND This is not a local account * NT_STATUS_NONE_MAPPED It's a local account but cannot be * translated. * other error status codes. */ uint32_t smb_sam_lookup_name(char *domain, char *name, uint16_t type, smb_account_t *account) { smb_domain_t di; smb_sid_t *sid; uint32_t status; smb_lwka_t *lwka; bzero(account, sizeof (smb_account_t)); if (domain != NULL) { if (!smb_domain_lookup_name(domain, &di) || (di.di_type != SMB_DOMAIN_LOCAL)) return (NT_STATUS_NOT_FOUND); /* Only Netbios hostname is accepted */ if (smb_strcasecmp(domain, di.di_nbname, 0) != 0) return (NT_STATUS_NONE_MAPPED); } else { if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di)) return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); } if (smb_strcasecmp(name, di.di_nbname, 0) == 0) { /* This is the local domain name */ account->a_type = SidTypeDomain; account->a_name = strdup(""); account->a_domain = strdup(di.di_nbname); account->a_sid = smb_sid_dup(di.di_binsid); account->a_domsid = smb_sid_dup(di.di_binsid); account->a_rid = (uint32_t)-1; if (!smb_account_validate(account)) { smb_account_free(account); return (NT_STATUS_NO_MEMORY); } return (NT_STATUS_SUCCESS); } if ((lwka = smb_lwka_lookup_name(name)) != NULL) { sid = smb_sid_splice(di.di_binsid, lwka->lwka_rid); type = lwka->lwka_type; } else { switch (type) { case SidTypeUser: status = smb_sam_lookup_user(name, &sid); if (status != NT_STATUS_SUCCESS) return (status); break; case SidTypeAlias: status = smb_sam_lookup_group(name, &sid); if (status != NT_STATUS_SUCCESS) return (status); break; case SidTypeUnknown: type = SidTypeUser; status = smb_sam_lookup_user(name, &sid); if (status == NT_STATUS_SUCCESS) break; if (status == NT_STATUS_NONE_MAPPED) return (status); type = SidTypeAlias; status = smb_sam_lookup_group(name, &sid); if (status != NT_STATUS_SUCCESS) return (status); break; default: return (NT_STATUS_INVALID_PARAMETER); } } account->a_name = strdup(name); account->a_sid = sid; account->a_domain = strdup(di.di_nbname); account->a_domsid = smb_sid_split(sid, &account->a_rid); account->a_type = type; if (!smb_account_validate(account)) { smb_account_free(account); return (NT_STATUS_NO_MEMORY); } return (NT_STATUS_SUCCESS); } /* * Looks up the given SID in local account databases: * * SMB Local users are looked up in /var/smb/smbpasswd * SMB Local groups are looked up in /var/smb/smbgroup.db * * If the account is found, its information is populated * in the passed smb_account_t structure. Caller must free * allocated memories by calling smb_account_free() upon * successful return. * * Return status: * * NT_STATUS_NOT_FOUND This is not a local account * NT_STATUS_NONE_MAPPED It's a local account but cannot be * translated. * other error status codes. */ uint32_t smb_sam_lookup_sid(smb_sid_t *sid, smb_account_t *account) { char hostname[MAXHOSTNAMELEN]; smb_passwd_t smbpw; smb_group_t grp; smb_lwka_t *lwka; smb_domain_t di; uint32_t rid; uid_t id; int id_type; int rc; bzero(account, sizeof (smb_account_t)); if (!smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di)) return (NT_STATUS_CANT_ACCESS_DOMAIN_INFO); if (smb_sid_cmp(sid, di.di_binsid)) { /* This is the local domain SID */ account->a_type = SidTypeDomain; account->a_name = strdup(""); account->a_domain = strdup(di.di_nbname); account->a_sid = smb_sid_dup(sid); account->a_domsid = smb_sid_dup(sid); account->a_rid = (uint32_t)-1; if (!smb_account_validate(account)) { smb_account_free(account); return (NT_STATUS_NO_MEMORY); } return (NT_STATUS_SUCCESS); } if (!smb_sid_indomain(di.di_binsid, sid)) { /* This is not a local SID */ return (NT_STATUS_NOT_FOUND); } if ((lwka = smb_lwka_lookup_sid(sid)) != NULL) { account->a_type = lwka->lwka_type; account->a_name = strdup(lwka->lwka_name); } else { id_type = SMB_IDMAP_UNKNOWN; if (smb_idmap_getid(sid, &id, &id_type) != IDMAP_SUCCESS) return (NT_STATUS_NONE_MAPPED); switch (id_type) { case SMB_IDMAP_USER: account->a_type = SidTypeUser; if (smb_pwd_getpwuid(id, &smbpw) == NULL) return (NT_STATUS_NO_SUCH_USER); account->a_name = strdup(smbpw.pw_name); account->a_flags = smbpw.pw_flags; break; case SMB_IDMAP_GROUP: account->a_type = SidTypeAlias; (void) smb_sid_getrid(sid, &rid); rc = smb_lgrp_getbyrid(rid, SMB_DOMAIN_LOCAL, &grp); if (rc != SMB_LGRP_SUCCESS) return (NT_STATUS_NO_SUCH_ALIAS); account->a_name = strdup(grp.sg_name); smb_lgrp_free(&grp); break; default: return (NT_STATUS_NONE_MAPPED); } } if (smb_getnetbiosname(hostname, MAXHOSTNAMELEN) == 0) account->a_domain = strdup(hostname); account->a_sid = smb_sid_dup(sid); account->a_domsid = smb_sid_split(sid, &account->a_rid); if (!smb_account_validate(account)) { smb_account_free(account); return (NT_STATUS_NO_MEMORY); } return (NT_STATUS_SUCCESS); } /* * Returns number of SMB users, i.e. users who have entry * in /var/smb/smbpasswd */ int smb_sam_usr_cnt(void) { return (smb_pwd_num()); } /* * Updates a list of groups in which the given user is a member * by adding any local (SAM) groups. * * We are a member of local groups where the local group * contains either the user's primary SID, or any of their * other SIDs such as from domain groups, SID history, etc. * We can have indirect membership via domain groups. */ uint32_t smb_sam_usr_groups(smb_sid_t *user_sid, smb_ids_t *gids) { smb_ids_t new_gids; smb_id_t *ids, *new_ids; smb_giter_t gi; smb_group_t lgrp; int i, gcnt, total_cnt; uint32_t ret; boolean_t member; /* * First pass: count groups to be added (gcnt) */ gcnt = 0; if (smb_lgrp_iteropen(&gi) != SMB_LGRP_SUCCESS) return (NT_STATUS_INTERNAL_ERROR); while (smb_lgrp_iterate(&gi, &lgrp) == SMB_LGRP_SUCCESS) { member = B_FALSE; if (smb_lgrp_is_member(&lgrp, user_sid)) member = B_TRUE; else for (i = 0, ids = gids->i_ids; i < gids->i_cnt; i++, ids++) { if (smb_lgrp_is_member(&lgrp, ids->i_sid)) { member = B_TRUE; break; } } /* Careful: only count lgrp once */ if (member) gcnt++; smb_lgrp_free(&lgrp); } smb_lgrp_iterclose(&gi); if (gcnt == 0) return (NT_STATUS_SUCCESS); /* * Second pass: add to groups list. * Do not modify gcnt after here. */ if (smb_lgrp_iteropen(&gi) != SMB_LGRP_SUCCESS) return (NT_STATUS_INTERNAL_ERROR); /* * Expand the list (copy to a new, larger one) * Note: were're copying pointers from the old * array to the new (larger) array, and then * adding new pointers after what we copied. */ ret = 0; new_gids.i_cnt = gids->i_cnt; total_cnt = gids->i_cnt + gcnt; new_gids.i_ids = malloc(total_cnt * sizeof (smb_id_t)); if (new_gids.i_ids == NULL) { ret = NT_STATUS_NO_MEMORY; goto out; } (void) memcpy(new_gids.i_ids, gids->i_ids, gids->i_cnt * sizeof (smb_id_t)); new_ids = new_gids.i_ids + gids->i_cnt; (void) memset(new_ids, 0, gcnt * sizeof (smb_id_t)); /* * Add group SIDs starting at the end of the * previous list. (new_ids) */ while (smb_lgrp_iterate(&gi, &lgrp) == SMB_LGRP_SUCCESS) { member = B_FALSE; if (smb_lgrp_is_member(&lgrp, user_sid)) member = B_TRUE; else for (i = 0, ids = gids->i_ids; i < gids->i_cnt; i++, ids++) { if (smb_lgrp_is_member(&lgrp, ids->i_sid)) { member = B_TRUE; break; } } if (member && (new_gids.i_cnt < (gids->i_cnt + gcnt))) { new_ids->i_sid = smb_sid_dup(lgrp.sg_id.gs_sid); if (new_ids->i_sid == NULL) { smb_lgrp_free(&lgrp); ret = NT_STATUS_NO_MEMORY; goto out; } new_ids->i_attrs = lgrp.sg_attr; new_ids++; new_gids.i_cnt++; } smb_lgrp_free(&lgrp); } out: smb_lgrp_iterclose(&gi); if (ret != 0) { if (new_gids.i_ids != NULL) { /* * Free only the new sids we added. * The old ones were copied ptrs. */ ids = new_gids.i_ids + gids->i_cnt; for (i = 0; i < gcnt; i++, ids++) { smb_sid_free(ids->i_sid); } free(new_gids.i_ids); } return (ret); } /* * Success! Update passed gids and * free the old array. */ free(gids->i_ids); *gids = new_gids; return (NT_STATUS_SUCCESS); } /* * Returns the number of built-in or local groups stored * in /var/smb/smbgroup.db */ int smb_sam_grp_cnt(smb_domain_type_t dtype) { int grpcnt; int rc; switch (dtype) { case SMB_DOMAIN_BUILTIN: rc = smb_lgrp_numbydomain(SMB_DOMAIN_BUILTIN, &grpcnt); break; case SMB_DOMAIN_LOCAL: rc = smb_lgrp_numbydomain(SMB_DOMAIN_LOCAL, &grpcnt); break; default: rc = SMB_LGRP_INVALID_ARG; } return ((rc == SMB_LGRP_SUCCESS) ? grpcnt : 0); } /* * Determines whether the given SID is a member of the group * specified by gname. */ boolean_t smb_sam_grp_ismember(const char *gname, smb_sid_t *sid) { smb_group_t grp; boolean_t ismember = B_FALSE; if (smb_lgrp_getbyname((char *)gname, &grp) == SMB_LGRP_SUCCESS) { ismember = smb_lgrp_is_member(&grp, sid); smb_lgrp_free(&grp); } return (ismember); } /* * Frees memories allocated for the passed account fields. * Initializes @account after all. */ void smb_account_free(smb_account_t *account) { free(account->a_name); free(account->a_domain); smb_sid_free(account->a_sid); smb_sid_free(account->a_domsid); bzero(account, sizeof (smb_account_t)); } /* * Validates the given account. */ boolean_t smb_account_validate(smb_account_t *account) { return ((account->a_name != NULL) && (account->a_sid != NULL) && (account->a_domain != NULL) && (account->a_domsid != NULL)); } /* * Lookup local SMB user account database (/var/smb/smbpasswd) * if there's a match query its SID from idmap service and make * sure the SID is a local SID. * * The memory for the returned SID must be freed by the caller. */ static uint32_t smb_sam_lookup_user(char *name, smb_sid_t **sid) { smb_passwd_t smbpw; if (smb_pwd_getpwnam(name, &smbpw) == NULL) return (NT_STATUS_NO_SUCH_USER); if (smb_idmap_getsid(smbpw.pw_uid, SMB_IDMAP_USER, sid) != IDMAP_SUCCESS) return (NT_STATUS_NONE_MAPPED); if (!smb_sid_islocal(*sid)) { smb_sid_free(*sid); return (NT_STATUS_NONE_MAPPED); } return (NT_STATUS_SUCCESS); } /* * Lookup local SMB group account database (/var/smb/smbgroup.db) * The memory for the returned SID must be freed by the caller. */ static uint32_t smb_sam_lookup_group(char *name, smb_sid_t **sid) { smb_group_t grp; if (smb_lgrp_getbyname(name, &grp) != SMB_LGRP_SUCCESS) return (NT_STATUS_NO_SUCH_ALIAS); *sid = smb_sid_dup(grp.sg_id.gs_sid); smb_lgrp_free(&grp); return ((*sid == NULL) ? NT_STATUS_NO_MEMORY : NT_STATUS_SUCCESS); } static smb_lwka_t * smb_lwka_lookup_name(char *name) { int i; for (i = 0; i < SMB_LWKA_NUM; i++) { if (smb_strcasecmp(name, lwka_tbl[i].lwka_name, 0) == 0) return (&lwka_tbl[i]); } return (NULL); } static smb_lwka_t * smb_lwka_lookup_sid(smb_sid_t *sid) { uint32_t rid; int i; (void) smb_sid_getrid(sid, &rid); if (rid > 999) return (NULL); for (i = 0; i < SMB_LWKA_NUM; i++) { if (rid == lwka_tbl[i].lwka_rid) return (&lwka_tbl[i]); } return (NULL); } /* * smb_sid_islocal * * Check a SID to see if it belongs to the local domain. */ boolean_t smb_sid_islocal(smb_sid_t *sid) { smb_domain_t di; boolean_t islocal = B_FALSE; if (smb_domain_lookup_type(SMB_DOMAIN_LOCAL, &di)) islocal = smb_sid_indomain(di.di_binsid, sid); return (islocal); } void smb_ids_free(smb_ids_t *ids) { smb_id_t *id; int i; if ((ids != NULL) && (ids->i_ids != NULL)) { id = ids->i_ids; for (i = 0; i < ids->i_cnt; i++, id++) smb_sid_free(id->i_sid); free(ids->i_ids); } } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2015 Nexenta Systems, Inc. All rights reserved. * Copyright 2023 Oxide Computer Company */ /* helper functions for using libscf with CIFS */ #include #include #include #include #include #include #include #include #include #include #include #include /* * smb_smf_scf_log_error(msg) * Logs error messages from scf API's */ static void smb_smf_scf_log_error(char *msg) { if (msg == NULL) msg = "SMBD SMF problems"; syslog(LOG_ERR, "%s: %s", msg, scf_strerror(scf_error())); } /* * smb_smf_create_service_pgroup(handle, pgroup) * * create a new property group at service level. */ int smb_smf_create_service_pgroup(smb_scfhandle_t *handle, char *pgroup) { int ret = SMBD_SMF_OK; int err; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * only create a handle if it doesn't exist. It is ok to exist * since the pg handle will be set as a side effect. */ if (handle->scf_pg == NULL) if ((handle->scf_pg = scf_pg_create(handle->scf_handle)) == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * if the pgroup exists, we are done. If it doesn't, then we * need to actually add one to the service instance. */ if (scf_service_get_pg(handle->scf_service, pgroup, handle->scf_pg) != 0) { /* doesn't exist so create one */ if (scf_service_add_pg(handle->scf_service, pgroup, SCF_GROUP_APPLICATION, 0, handle->scf_pg) != 0) { err = scf_error(); if (err != SCF_ERROR_NONE) smb_smf_scf_log_error(NULL); switch (err) { case SCF_ERROR_PERMISSION_DENIED: ret = SMBD_SMF_NO_PERMISSION; break; default: ret = SMBD_SMF_SYSTEM_ERR; break; } } } return (ret); } /* * Start transaction on current pg in handle. * The pg could be service or instance level. * Must be called after pg handle is obtained * from create or get. */ int smb_smf_start_transaction(smb_scfhandle_t *handle) { int ret = SMBD_SMF_OK; if (!handle || (!handle->scf_pg)) return (SMBD_SMF_SYSTEM_ERR); /* * lookup the property group and create it if it doesn't already * exist. */ if (handle->scf_state == SCH_STATE_INIT) { if (ret == SMBD_SMF_OK) { handle->scf_trans = scf_transaction_create(handle->scf_handle); if (handle->scf_trans != NULL) { if (scf_transaction_start(handle->scf_trans, handle->scf_pg) != 0) { ret = SMBD_SMF_SYSTEM_ERR; scf_transaction_destroy( handle->scf_trans); handle->scf_trans = NULL; } } else { ret = SMBD_SMF_SYSTEM_ERR; } } } if (ret == SMBD_SMF_SYSTEM_ERR && scf_error() == SCF_ERROR_PERMISSION_DENIED) ret = SMBD_SMF_NO_PERMISSION; return (ret); } /* * smb_smf_end_transaction(handle) * * Commit the changes that were added to the transaction in the * handle. Do all necessary cleanup. */ int smb_smf_end_transaction(smb_scfhandle_t *handle) { int ret = SMBD_SMF_OK; int rc; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); if (handle->scf_trans == NULL) { ret = SMBD_SMF_SYSTEM_ERR; } else { rc = scf_transaction_commit(handle->scf_trans); if (rc == 1) { ret = SMBD_SMF_OK; } else if (rc == 0) { ret = SMBD_SMF_INVALID_ARG; smb_smf_scf_log_error( "Failed to commit, old pg: transaction"); } else { ret = SMBD_SMF_SYSTEM_ERR; smb_smf_scf_log_error( "Failed to commit, error: transaction"); } scf_transaction_destroy_children(handle->scf_trans); scf_transaction_destroy(handle->scf_trans); handle->scf_trans = NULL; } return (ret); } /* * Sets string property in current pg */ int smb_smf_set_string_property(smb_scfhandle_t *handle, char *propname, char *valstr) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_transaction_entry_t *entry = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * properties must be set in transactions and don't take * effect until the transaction has been ended/committed. */ value = scf_value_create(handle->scf_handle); entry = scf_entry_create(handle->scf_handle); if (value != NULL && entry != NULL) { if (scf_transaction_property_change(handle->scf_trans, entry, propname, SCF_TYPE_ASTRING) == 0 || scf_transaction_property_new(handle->scf_trans, entry, propname, SCF_TYPE_ASTRING) == 0) { if (scf_value_set_astring(value, valstr) == 0) { if (scf_entry_add_value(entry, value) != 0) { ret = SMBD_SMF_SYSTEM_ERR; scf_value_destroy(value); } /* the value is in the transaction */ value = NULL; } else { /* value couldn't be constructed */ ret = SMBD_SMF_SYSTEM_ERR; } /* the entry is in the transaction */ entry = NULL; } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (ret == SMBD_SMF_SYSTEM_ERR) { if (scf_error() == SCF_ERROR_PERMISSION_DENIED) { ret = SMBD_SMF_NO_PERMISSION; } } /* * cleanup if there were any errors that didn't leave these * values where they would be cleaned up later. */ if (value != NULL) scf_value_destroy(value); if (entry != NULL) scf_entry_destroy(entry); return (ret); } /* * Gets string property value.upto sz size. * Caller is responsible to have enough memory allocated. */ int smb_smf_get_string_property(smb_scfhandle_t *handle, char *propname, char *valstr, size_t sz) { int ret = SMBD_SMF_OK; scf_value_t *value; scf_property_t *prop; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); value = scf_value_create(handle->scf_handle); prop = scf_property_create(handle->scf_handle); if (value && prop && (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { if (scf_property_get_value(prop, value) == 0) { if (scf_value_get_astring(value, valstr, sz) < 0) { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (value != NULL) scf_value_destroy(value); if (prop != NULL) scf_property_destroy(prop); return (ret); } /* * Set integer value of property. * The value is returned as int64_t value * Caller ensures appropriate translation. */ int smb_smf_set_integer_property(smb_scfhandle_t *handle, char *propname, int64_t valint) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_transaction_entry_t *entry = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * properties must be set in transactions and don't take * effect until the transaction has been ended/committed. */ value = scf_value_create(handle->scf_handle); entry = scf_entry_create(handle->scf_handle); if (value != NULL && entry != NULL) { if (scf_transaction_property_change(handle->scf_trans, entry, propname, SCF_TYPE_INTEGER) == 0 || scf_transaction_property_new(handle->scf_trans, entry, propname, SCF_TYPE_INTEGER) == 0) { scf_value_set_integer(value, valint); if (scf_entry_add_value(entry, value) != 0) { ret = SMBD_SMF_SYSTEM_ERR; scf_value_destroy(value); } /* the value is in the transaction */ value = NULL; } /* the entry is in the transaction */ entry = NULL; } else { ret = SMBD_SMF_SYSTEM_ERR; } if (ret == SMBD_SMF_SYSTEM_ERR) { if (scf_error() == SCF_ERROR_PERMISSION_DENIED) { ret = SMBD_SMF_NO_PERMISSION; } } /* * cleanup if there were any errors that didn't leave these * values where they would be cleaned up later. */ if (value != NULL) scf_value_destroy(value); if (entry != NULL) scf_entry_destroy(entry); return (ret); } /* * Gets integer property value. * Caller is responsible to have enough memory allocated. */ int smb_smf_get_integer_property(smb_scfhandle_t *handle, char *propname, int64_t *valint) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_property_t *prop = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); value = scf_value_create(handle->scf_handle); prop = scf_property_create(handle->scf_handle); if ((prop) && (value) && (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { if (scf_property_get_value(prop, value) == 0) { if (scf_value_get_integer(value, valint) != 0) { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (value != NULL) scf_value_destroy(value); if (prop != NULL) scf_property_destroy(prop); return (ret); } /* * Set boolean value of property. * The value is returned as int64_t value * Caller ensures appropriate translation. */ int smb_smf_set_boolean_property(smb_scfhandle_t *handle, char *propname, uint8_t valbool) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_transaction_entry_t *entry = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * properties must be set in transactions and don't take * effect until the transaction has been ended/committed. */ value = scf_value_create(handle->scf_handle); entry = scf_entry_create(handle->scf_handle); if (value != NULL && entry != NULL) { if (scf_transaction_property_change(handle->scf_trans, entry, propname, SCF_TYPE_BOOLEAN) == 0 || scf_transaction_property_new(handle->scf_trans, entry, propname, SCF_TYPE_BOOLEAN) == 0) { scf_value_set_boolean(value, valbool); if (scf_entry_add_value(entry, value) != 0) { ret = SMBD_SMF_SYSTEM_ERR; scf_value_destroy(value); } /* the value is in the transaction */ value = NULL; } /* the entry is in the transaction */ entry = NULL; } else { ret = SMBD_SMF_SYSTEM_ERR; } if (ret == SMBD_SMF_SYSTEM_ERR) { if (scf_error() == SCF_ERROR_PERMISSION_DENIED) { ret = SMBD_SMF_NO_PERMISSION; } } /* * cleanup if there were any errors that didn't leave these * values where they would be cleaned up later. */ if (value != NULL) scf_value_destroy(value); if (entry != NULL) scf_entry_destroy(entry); return (ret); } /* * Gets boolean property value. * Caller is responsible to have enough memory allocated. */ int smb_smf_get_boolean_property(smb_scfhandle_t *handle, char *propname, uint8_t *valbool) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_property_t *prop = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); value = scf_value_create(handle->scf_handle); prop = scf_property_create(handle->scf_handle); if ((prop) && (value) && (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { if (scf_property_get_value(prop, value) == 0) { if (scf_value_get_boolean(value, valbool) != 0) { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (value != NULL) scf_value_destroy(value); if (prop != NULL) scf_property_destroy(prop); return (ret); } /* * Sets a blob property value. */ int smb_smf_set_opaque_property(smb_scfhandle_t *handle, char *propname, void *voidval, size_t sz) { int ret = SMBD_SMF_OK; scf_value_t *value; scf_transaction_entry_t *entry; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * properties must be set in transactions and don't take * effect until the transaction has been ended/committed. */ value = scf_value_create(handle->scf_handle); entry = scf_entry_create(handle->scf_handle); if (value != NULL && entry != NULL) { if (scf_transaction_property_change(handle->scf_trans, entry, propname, SCF_TYPE_OPAQUE) == 0 || scf_transaction_property_new(handle->scf_trans, entry, propname, SCF_TYPE_OPAQUE) == 0) { if (scf_value_set_opaque(value, voidval, sz) == 0) { if (scf_entry_add_value(entry, value) != 0) { ret = SMBD_SMF_SYSTEM_ERR; scf_value_destroy(value); } /* the value is in the transaction */ value = NULL; } else { /* value couldn't be constructed */ ret = SMBD_SMF_SYSTEM_ERR; } /* the entry is in the transaction */ entry = NULL; } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (ret == SMBD_SMF_SYSTEM_ERR) { if (scf_error() == SCF_ERROR_PERMISSION_DENIED) { ret = SMBD_SMF_NO_PERMISSION; } } /* * cleanup if there were any errors that didn't leave these * values where they would be cleaned up later. */ if (value != NULL) scf_value_destroy(value); if (entry != NULL) scf_entry_destroy(entry); return (ret); } /* * Gets a blob property value. * Caller is responsible to have enough memory allocated. */ int smb_smf_get_opaque_property(smb_scfhandle_t *handle, char *propname, void *v, size_t sz) { int ret = SMBD_SMF_OK; scf_value_t *value = NULL; scf_property_t *prop = NULL; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); value = scf_value_create(handle->scf_handle); prop = scf_property_create(handle->scf_handle); if ((prop) && (value) && (scf_pg_get_property(handle->scf_pg, propname, prop) == 0)) { if (scf_property_get_value(prop, value) == 0) { if (scf_value_get_opaque(value, (char *)v, sz) != sz) { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } } else { ret = SMBD_SMF_SYSTEM_ERR; } if (value != NULL) scf_value_destroy(value); if (prop != NULL) scf_property_destroy(prop); return (ret); } /* * Delete a property (for properties obsoleted during an upgrade). */ int smb_smf_delete_property(smb_scfhandle_t *handle, char *propname) { scf_transaction_entry_t *entry; int ret = SMBD_SMF_OK; if (handle == NULL) return (SMBD_SMF_SYSTEM_ERR); if (handle->scf_trans == NULL) return (SMBD_SMF_SYSTEM_ERR); /* * properties must be set in transactions and don't take * effect until the transaction has been ended/committed. */ entry = scf_entry_create(handle->scf_handle); if (entry == NULL) { ret = SMBD_SMF_SYSTEM_ERR; goto out; } if (scf_transaction_property_delete(handle->scf_trans, entry, propname) == 0) { /* the entry is in the transaction */ entry = NULL; } else { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: /* Did not exist. We're done. */ ret = SMBD_SMF_OK; goto out; case SCF_ERROR_PERMISSION_DENIED: ret = SMBD_SMF_NO_PERMISSION; goto out; default: ret = SMBD_SMF_SYSTEM_ERR; goto out; } } out: scf_entry_destroy(entry); return (ret); } /* * Put the smb service into maintenance mode. */ int smb_smf_maintenance_mode(void) { return (smf_maintain_instance(SMBD_DEFAULT_INSTANCE_FMRI, 0)); } /* * Restart the smb service. */ int smb_smf_restart_service(void) { return (smf_restart_instance(SMBD_DEFAULT_INSTANCE_FMRI)); } /* * smb_smf_scf_init() * * must be called before using any of the SCF functions. * Returns smb_scfhandle_t pointer if success. */ smb_scfhandle_t * smb_smf_scf_init(char *svc_name) { smb_scfhandle_t *handle; handle = malloc(sizeof (smb_scfhandle_t)); if (handle != NULL) { bzero((char *)handle, sizeof (smb_scfhandle_t)); handle->scf_state = SCH_STATE_INITIALIZING; handle->scf_handle = scf_handle_create(SCF_VERSION); if (handle->scf_handle != NULL) { if (scf_handle_bind(handle->scf_handle) == 0) { handle->scf_scope = scf_scope_create(handle->scf_handle); if (handle->scf_scope == NULL) goto err; if (scf_handle_get_local_scope( handle->scf_handle, handle->scf_scope) != 0) goto err; handle->scf_service = scf_service_create(handle->scf_handle); if (handle->scf_service == NULL) goto err; if (scf_scope_get_service(handle->scf_scope, svc_name, handle->scf_service) != SCF_SUCCESS) { goto err; } handle->scf_pg = scf_pg_create(handle->scf_handle); if (handle->scf_pg == NULL) goto err; handle->scf_state = SCH_STATE_INIT; } else { goto err; } } else { free(handle); handle = NULL; smb_smf_scf_log_error( "Could not access SMF repository"); } } return (handle); /* error handling/unwinding */ err: (void) smb_smf_scf_fini(handle); if (scf_error() != SCF_ERROR_NOT_FOUND) (void) smb_smf_scf_log_error("SMF initialization problem"); return (NULL); } /* * smb_smf_scf_fini(handle) * * must be called when done. Called with the handle allocated in * smb_smf_scf_init(), it cleans up the state and frees any SCF resources * still in use. */ void smb_smf_scf_fini(smb_scfhandle_t *handle) { if (handle != NULL) { int unbind = 0; scf_iter_destroy(handle->scf_pg_iter); handle->scf_pg_iter = NULL; scf_iter_destroy(handle->scf_inst_iter); handle->scf_inst_iter = NULL; unbind = 1; scf_scope_destroy(handle->scf_scope); handle->scf_scope = NULL; scf_instance_destroy(handle->scf_instance); handle->scf_instance = NULL; scf_service_destroy(handle->scf_service); handle->scf_service = NULL; scf_pg_destroy(handle->scf_pg); handle->scf_pg = NULL; handle->scf_state = SCH_STATE_UNINIT; if (unbind) (void) scf_handle_unbind(handle->scf_handle); scf_handle_destroy(handle->scf_handle); handle->scf_handle = NULL; free(handle); } } /* * 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) 2009, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * This module provides Security Descriptor handling functions. */ #include #include #include #include #include #include #define SMB_SHR_ACE_READ_PERMS (ACE_READ_PERMS | ACE_EXECUTE | ACE_SYNCHRONIZE) #define SMB_SHR_ACE_CONTROL_PERMS (ACE_MODIFY_PERMS & (~ACE_DELETE_CHILD)) /* * This mapping table is provided to map permissions set by chmod * using 'read_set' and 'modify_set' to what Windows share ACL GUI * expects as Read and Control, respectively. */ static struct { int am_ace_perms; int am_share_perms; } smb_ace_map[] = { { ACE_MODIFY_PERMS, SMB_SHR_ACE_CONTROL_PERMS }, { ACE_READ_PERMS, SMB_SHR_ACE_READ_PERMS } }; #define SMB_ACE_MASK_MAP_SIZE (sizeof (smb_ace_map)/sizeof (smb_ace_map[0])) static void smb_sd_set_sacl(smb_sd_t *, smb_acl_t *, boolean_t, int); static void smb_sd_set_dacl(smb_sd_t *, smb_acl_t *, boolean_t, int); void smb_sd_init(smb_sd_t *sd, uint8_t revision) { bzero(sd, sizeof (smb_sd_t)); sd->sd_revision = revision; } /* * smb_sd_term * * Free non-NULL members of 'sd' which has to be in * absolute (pointer) form. */ void smb_sd_term(smb_sd_t *sd) { assert(sd); assert((sd->sd_control & SE_SELF_RELATIVE) == 0); smb_sid_free(sd->sd_owner); smb_sid_free(sd->sd_group); smb_acl_free(sd->sd_dacl); smb_acl_free(sd->sd_sacl); bzero(sd, sizeof (smb_sd_t)); } uint32_t smb_sd_len(smb_sd_t *sd, uint32_t secinfo) { uint32_t length = SMB_SD_HDRSIZE; if (secinfo & SMB_OWNER_SECINFO) length += smb_sid_len(sd->sd_owner); if (secinfo & SMB_GROUP_SECINFO) length += smb_sid_len(sd->sd_group); if (secinfo & SMB_DACL_SECINFO) length += smb_acl_len(sd->sd_dacl); if (secinfo & SMB_SACL_SECINFO) length += smb_acl_len(sd->sd_sacl); return (length); } /* * smb_sd_get_secinfo * * Return the security information mask for the specified security * descriptor. */ uint32_t smb_sd_get_secinfo(smb_sd_t *sd) { uint32_t sec_info = 0; if (sd == NULL) return (0); if (sd->sd_owner) sec_info |= SMB_OWNER_SECINFO; if (sd->sd_group) sec_info |= SMB_GROUP_SECINFO; if (sd->sd_dacl) sec_info |= SMB_DACL_SECINFO; if (sd->sd_sacl) sec_info |= SMB_SACL_SECINFO; return (sec_info); } /* * Adjust the Access Mask so that ZFS ACE mask and Windows ACE read mask match. */ static int smb_sd_adjust_read_mask(int mask) { int i; for (i = 0; i < SMB_ACE_MASK_MAP_SIZE; ++i) { if (smb_ace_map[i].am_ace_perms == mask) return (smb_ace_map[i].am_share_perms); } return (mask); } /* * Get ZFS acl from the share path via acl_get() method. */ static uint32_t smb_sd_read_acl(char *path, smb_fssd_t *fs_sd) { acl_t *z_acl; ace_t *z_ace; fs_sd->sd_gid = fs_sd->sd_uid = 0; errno = 0; if (acl_get(path, 0, &z_acl) != 0) { switch (errno) { case EACCES: return (NT_STATUS_ACCESS_DENIED); case ENOENT: return (NT_STATUS_OBJECT_PATH_NOT_FOUND); default: return (NT_STATUS_INTERNAL_ERROR); } } if ((z_ace = (ace_t *)z_acl->acl_aclp) == NULL) return (NT_STATUS_INVALID_ACL); for (int i = 0; i < z_acl->acl_cnt; i++, z_ace++) z_ace->a_access_mask = smb_sd_adjust_read_mask(z_ace->a_access_mask); fs_sd->sd_zdacl = z_acl; fs_sd->sd_zsacl = NULL; return (NT_STATUS_SUCCESS); } /* * smb_sd_read * * Reads ZFS acl from filesystem using acl_get() method. Convert the ZFS acl to * a Win SD and return the Win SD in absolute form. * * NOTE: upon successful return caller MUST free the memory allocated * for the returned SD by calling smb_sd_term(). */ uint32_t smb_sd_read(char *path, smb_sd_t *sd, uint32_t secinfo) { smb_fssd_t fs_sd; uint32_t status = NT_STATUS_SUCCESS; uint32_t sd_flags; int error; sd_flags = SMB_FSSD_FLAGS_DIR; smb_fssd_init(&fs_sd, secinfo, sd_flags); error = smb_sd_read_acl(path, &fs_sd); if (error != NT_STATUS_SUCCESS) { smb_fssd_term(&fs_sd); return (error); } status = smb_sd_fromfs(&fs_sd, sd); smb_fssd_term(&fs_sd); return (status); } /* * Apply ZFS acl to the share path via acl_set() method. * A NULL ACL pointer here represents an error. * Null or empty ACLs are handled in smb_sd_tofs(). */ static uint32_t smb_sd_write_acl(char *path, smb_fssd_t *fs_sd) { acl_t *z_acl; ace_t *z_ace; uint32_t status = NT_STATUS_SUCCESS; z_acl = fs_sd->sd_zdacl; if (z_acl == NULL) return (NT_STATUS_INVALID_ACL); z_ace = (ace_t *)z_acl->acl_aclp; if (z_ace == NULL) return (NT_STATUS_INVALID_ACL); fs_sd->sd_gid = fs_sd->sd_uid = 0; if (acl_set(path, z_acl) != 0) status = NT_STATUS_INTERNAL_ERROR; return (status); } /* * smb_sd_write * * Takes a Win SD in absolute form, converts it to * ZFS acl and applies the acl to the share path via acl_set() method. */ uint32_t smb_sd_write(char *path, smb_sd_t *sd, uint32_t secinfo) { smb_fssd_t fs_sd; uint32_t status = NT_STATUS_SUCCESS; uint32_t sd_flags; int error; sd_flags = SMB_FSSD_FLAGS_DIR; smb_fssd_init(&fs_sd, secinfo, sd_flags); error = smb_sd_tofs(sd, &fs_sd); if (error != NT_STATUS_SUCCESS) { smb_fssd_term(&fs_sd); return (error); } status = smb_sd_write_acl(path, &fs_sd); smb_fssd_term(&fs_sd); return (status); } /* * smb_sd_tofs * * Creates a filesystem security structure based on the given * Windows security descriptor. */ uint32_t smb_sd_tofs(smb_sd_t *sd, smb_fssd_t *fs_sd) { smb_sid_t *sid; uint32_t status = NT_STATUS_SUCCESS; uint16_t sd_control; idmap_stat idm_stat; int idtype; int flags = 0; sd_control = sd->sd_control; /* * ZFS only has one set of flags so for now only * Windows DACL flags are taken into account. */ if (sd_control & SE_DACL_DEFAULTED) flags |= ACL_DEFAULTED; if (sd_control & SE_DACL_AUTO_INHERITED) flags |= ACL_AUTO_INHERIT; if (sd_control & SE_DACL_PROTECTED) flags |= ACL_PROTECTED; if (fs_sd->sd_flags & SMB_FSSD_FLAGS_DIR) flags |= ACL_IS_DIR; /* Owner */ if (fs_sd->sd_secinfo & SMB_OWNER_SECINFO) { sid = sd->sd_owner; if (!smb_sid_isvalid(sid)) return (NT_STATUS_INVALID_SID); idtype = SMB_IDMAP_USER; idm_stat = smb_idmap_getid(sid, &fs_sd->sd_uid, &idtype); if (idm_stat != IDMAP_SUCCESS) { return (NT_STATUS_NONE_MAPPED); } } /* Group */ if (fs_sd->sd_secinfo & SMB_GROUP_SECINFO) { sid = sd->sd_group; if (!smb_sid_isvalid(sid)) return (NT_STATUS_INVALID_SID); idtype = SMB_IDMAP_GROUP; idm_stat = smb_idmap_getid(sid, &fs_sd->sd_gid, &idtype); if (idm_stat != IDMAP_SUCCESS) { return (NT_STATUS_NONE_MAPPED); } } /* DACL */ if (fs_sd->sd_secinfo & SMB_DACL_SECINFO) { if (sd->sd_control & SE_DACL_PRESENT) { status = smb_acl_to_zfs(sd->sd_dacl, flags, SMB_DACL_SECINFO, &fs_sd->sd_zdacl); if (status != NT_STATUS_SUCCESS) return (status); } else return (NT_STATUS_INVALID_ACL); } /* SACL */ if (fs_sd->sd_secinfo & SMB_SACL_SECINFO) { if (sd->sd_control & SE_SACL_PRESENT) { status = smb_acl_to_zfs(sd->sd_sacl, flags, SMB_SACL_SECINFO, &fs_sd->sd_zsacl); if (status != NT_STATUS_SUCCESS) { return (status); } } else { return (NT_STATUS_INVALID_ACL); } } return (status); } /* * smb_sd_fromfs * * Makes an Windows style security descriptor in absolute form * based on the given filesystem security information. * * Should call smb_sd_term() for the returned sd to free allocated * members. */ uint32_t smb_sd_fromfs(smb_fssd_t *fs_sd, smb_sd_t *sd) { uint32_t status = NT_STATUS_SUCCESS; smb_acl_t *acl = NULL; smb_sid_t *sid; idmap_stat idm_stat; assert(fs_sd); assert(sd); smb_sd_init(sd, SECURITY_DESCRIPTOR_REVISION); /* Owner */ if (fs_sd->sd_secinfo & SMB_OWNER_SECINFO) { idm_stat = smb_idmap_getsid(fs_sd->sd_uid, SMB_IDMAP_USER, &sid); if (idm_stat != IDMAP_SUCCESS) { smb_sd_term(sd); return (NT_STATUS_NONE_MAPPED); } sd->sd_owner = sid; } /* Group */ if (fs_sd->sd_secinfo & SMB_GROUP_SECINFO) { idm_stat = smb_idmap_getsid(fs_sd->sd_gid, SMB_IDMAP_GROUP, &sid); if (idm_stat != IDMAP_SUCCESS) { smb_sd_term(sd); return (NT_STATUS_NONE_MAPPED); } sd->sd_group = sid; } /* DACL */ if (fs_sd->sd_secinfo & SMB_DACL_SECINFO) { if (fs_sd->sd_zdacl != NULL) { acl = smb_acl_from_zfs(fs_sd->sd_zdacl); if (acl == NULL) { smb_sd_term(sd); return (NT_STATUS_INTERNAL_ERROR); } /* * Need to sort the ACL before send it to Windows * clients. Winodws GUI is sensitive about the order * of ACEs. */ smb_acl_sort(acl); smb_sd_set_dacl(sd, acl, B_TRUE, fs_sd->sd_zdacl->acl_flags); } else { smb_sd_set_dacl(sd, NULL, B_FALSE, 0); } } /* SACL */ if (fs_sd->sd_secinfo & SMB_SACL_SECINFO) { if (fs_sd->sd_zsacl != NULL) { acl = smb_acl_from_zfs(fs_sd->sd_zsacl); if (acl == NULL) { smb_sd_term(sd); return (NT_STATUS_INTERNAL_ERROR); } smb_sd_set_sacl(sd, acl, B_TRUE, fs_sd->sd_zsacl->acl_flags); } else { smb_sd_set_sacl(sd, NULL, B_FALSE, 0); } } return (status); } static void smb_sd_set_dacl(smb_sd_t *sd, smb_acl_t *acl, boolean_t present, int flags) { assert((sd->sd_control & SE_SELF_RELATIVE) == 0); sd->sd_dacl = acl; if (flags & ACL_DEFAULTED) sd->sd_control |= SE_DACL_DEFAULTED; if (flags & ACL_AUTO_INHERIT) sd->sd_control |= SE_DACL_AUTO_INHERITED; if (flags & ACL_PROTECTED) sd->sd_control |= SE_DACL_PROTECTED; if (present) sd->sd_control |= SE_DACL_PRESENT; } static void smb_sd_set_sacl(smb_sd_t *sd, smb_acl_t *acl, boolean_t present, int flags) { assert((sd->sd_control & SE_SELF_RELATIVE) == 0); sd->sd_sacl = acl; if (flags & ACL_DEFAULTED) sd->sd_control |= SE_SACL_DEFAULTED; if (flags & ACL_AUTO_INHERIT) sd->sd_control |= SE_SACL_AUTO_INHERITED; if (flags & ACL_PROTECTED) sd->sd_control |= SE_SACL_PROTECTED; if (present) sd->sd_control |= SE_SACL_PRESENT; } /* * smb_fssd_init * * Initializes the given FS SD structure. */ void smb_fssd_init(smb_fssd_t *fs_sd, uint32_t secinfo, uint32_t flags) { bzero(fs_sd, sizeof (smb_fssd_t)); fs_sd->sd_secinfo = secinfo; fs_sd->sd_flags = flags; } /* * smb_fssd_term * * Frees allocated memory for acl fields. */ void smb_fssd_term(smb_fssd_t *fs_sd) { assert(fs_sd); acl_free(fs_sd->sd_zdacl); acl_free(fs_sd->sd_zsacl); bzero(fs_sd, sizeof (smb_fssd_t)); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _SMB_SQLITE_H #define _SMB_SQLITE_H #ifdef __cplusplus extern "C" { #endif #include #ifdef _LP64 /* * We cannot make 64-bit version of libsqlite because the code * has some problems. */ sqlite * sqlite_open(const char *filename __unused, int mode __unused, char **errmsg __unused) { return (NULL); } void sqlite_close(sqlite *db __unused) { } char * sqlite_mprintf(const char *fmt __unused, ...) { return (NULL); } void sqlite_freemem(void *p __unused) { } int sqlite_compile(sqlite *db __unused, const char *zSql __unused, const char **pzTail __unused, sqlite_vm **ppVm __unused, char **pzErrmsg __unused) { return (SQLITE_ERROR); } void sqlite_free_table(char **res __unused) { } int sqlite_last_insert_rowid(sqlite *db __unused) { return (-1); } void sqlite_busy_timeout(sqlite *db __unused, int ms __unused) { } int sqlite_get_table(sqlite *db __unused, const char *zSql __unused, char ***pazResult __unused, int *pnRow __unused, int *pnColumn __unused, char **pzErrMsg __unused) { return (SQLITE_ERROR); } int sqlite_step(sqlite_vm *pVm __unused, int *pN __unused, const char ***pazValue __unused, const char ***pazColName __unused) { return (SQLITE_ERROR); } int sqlite_exec(sqlite *db __unused, const char *zSql __unused, sqlite_callback xCallback __unused, void *pArg __unused, char **pzErrMsg __unused) { return (SQLITE_ERROR); } int sqlite_finalize(sqlite_vm *pVm __unused, char **pzErrMsg __unused) { return (SQLITE_ERROR); } #endif /* _LP64 */ #ifdef __cplusplus } #endif #endif /* _SMB_SQLITE_H */ # # 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) 2010, Oracle and/or its affiliates. All rights reserved. # # # Generate the table mapping NT status codes to strings. # The table is sorted in numerical order by putting the # numeric constants as a comment first on each line, and # then running the table body through sort(1). # BEGIN { printf("/* Table generated by smb_status_gen.awk */\n"); printf("#include \n\n"); printf("typedef struct smb_status_table {\n"); printf("\tunsigned int value;\n"); printf("\tconst char *name;\n"); printf("} smb_status_table_t;\n\n"); printf("static const smb_status_table_t ntx_table[] = {\n"); } /^#define.NT_STATUS_/ { # Skip the _SEVERITY defines. if ( $2 ~ /^NT_STATUS_SEVERITY_/ ) next # Make sure the constant looks as expected. if ( $3 !~ /^0x[0-9A-F]+$/ ) { print "Warning: Unexpected format: "$0 > "/dev/stderr" exit 1; } # print: comment { macro, string }, printf("\t/* %s */\t{ %s,\t\"%s\" },\n", $3, $2, substr($2,11)) | "sort" ; } END { close("sort"); printf("};\n"); } /* * 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. */ /* * This file provides a text translation service for NT status codes. */ #include #include /* * Include the generated file with ntx_table[] * See smb_status_gen.awk */ #include "smb_status_tbl.h" static const int ntx_rows = sizeof (ntx_table) / sizeof (ntx_table[0]); /* * Comparison function for bsearch(3C). */ static int xlate_compare(const void *vkey, const void *vrow) { const smb_status_table_t *key = vkey; const smb_status_table_t *row = vrow; if (key->value == row->value) return (0); if (key->value < row->value) return (-1); return (1); } /* * Translate an ntstatus value to a meaningful text string. If there isn't * a corresponding text string in the table, the text representation of the * status value is returned. This uses a static buffer so there is a * possible concurrency issue if the caller hangs on to this pointer for a * while but it should be harmless and really remote since the value will * almost always be found in the table. */ const char * xlate_nt_status(unsigned int ntstatus) { static char unknown[16]; smb_status_table_t key; const smb_status_table_t *tep; key.value = ntstatus; key.name = NULL; tep = bsearch(&key, ntx_table, ntx_rows, sizeof (*tep), xlate_compare); if (tep != NULL) return (tep->name); (void) sprintf(unknown, "0x%08X", ntstatus); return ((const char *)unknown); } /* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include /* * This is exported NODIRECT so that smbd can provide it's own. */ void smb_vsyslog(int pri, const char *fmt, va_list ap) { vsyslog(pri, fmt, ap); } /* * This is exported NODIRECT so that fksmbd can provide it's own. */ void smb_syslog(int pri, const char *fmt, ...) { va_list ap; va_start(ap, fmt); smb_vsyslog(pri, fmt, ap); va_end(ap); } /* * Helper for smb_vsyslog(). Does %m substitutions. */ char * smb_syslog_fmt_m(char *buf, int buflen, const char *str, int err) { char *bp = buf; const char *sp = str; const char *endp = buf + buflen - 1; while ((*bp = *sp) != '\0' && bp != endp) { if ((*sp++ == '%') && (*sp == 'm')) { sp++; if (strerror_r(err, bp, endp - bp) == 0) bp += strlen(bp); } else { bp++; } } *bp = '\0'; return (buf); } /* * 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 2018 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SMB_LIB_ALT "/usr/lib/smbsrv/libsmbex.so" static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int); extern int __multi_innetgr(); extern int __netdir_getbyaddr_nosrv(struct netconfig *, struct nd_hostservlist **, struct netbuf *); #define C2H(c) "0123456789ABCDEF"[(c)] #define H2C(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : \ ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \ '\0') #define DEFAULT_SBOX_SIZE 256 /* * * hexdump * * Simple hex dump display function. Displays nbytes of buffer in hex and * printable format. Non-printing characters are shown as '.'. It is safe * to pass a null pointer. Each line begins with the offset. If nbytes is * 0, the line will be blank except for the offset. Example output: * * 00000000 54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61 This is a progra * 00000010 6D 20 74 65 73 74 2E 00 m test.. * */ void hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start) { static char *hex = "0123456789ABCDEF"; int i, count; int offset; unsigned char *p; char ascbuf[64]; char hexbuf[64]; char *ap = ascbuf; char *hp = hexbuf; if ((p = buffer) == NULL) return; offset = *start; *ap = '\0'; *hp = '\0'; count = 0; for (i = 0; i < nbytes; ++i) { if (i && (i % 16) == 0) { smb_tracef("%06X %s %s", offset, hexbuf, ascbuf); ap = ascbuf; hp = hexbuf; count = 0; offset += 16; } ap += sprintf(ap, "%c", (*p >= 0x20 && *p < 0x7F) ? *p : '.'); hp += sprintf(hp, " %c%c", hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]); ++p; ++count; } if (count) { smb_tracef("%06X %-48s %s", offset, hexbuf, ascbuf); offset += count; } *start = offset; } void hexdump(unsigned char *buffer, int nbytes) { unsigned long start = 0; hexdump_offset(buffer, nbytes, &start); } /* * bintohex * * Converts the given binary data (srcbuf) to * its equivalent hex chars (hexbuf). * * hexlen should be at least twice as srclen. * if hexbuf is not big enough returns 0. * otherwise returns number of valid chars in * hexbuf which is srclen * 2. */ size_t bintohex(const char *srcbuf, size_t srclen, char *hexbuf, size_t hexlen) { size_t outlen; char c; outlen = srclen << 1; if (hexlen < outlen) return (0); while (srclen-- > 0) { c = *srcbuf++; *hexbuf++ = C2H(c & 0xF); *hexbuf++ = C2H((c >> 4) & 0xF); } return (outlen); } /* * hextobin * * Converts hex to binary. * * Assuming hexbuf only contains hex digits (chars) * this function convert every two bytes of hexbuf * to one byte and put it in dstbuf. * * hexlen should be an even number. * dstlen should be at least half of hexlen. * * Returns 0 if sizes are not correct, otherwise * returns the number of converted bytes in dstbuf * which is half of hexlen. */ size_t hextobin(const char *hexbuf, size_t hexlen, char *dstbuf, size_t dstlen) { size_t outlen; if ((hexlen % 2) != 0) return (0); outlen = hexlen >> 1; if (dstlen < outlen) return (0); while (hexlen > 0) { *dstbuf = H2C(*hexbuf) & 0x0F; hexbuf++; *dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0; hexbuf++; hexlen -= 2; } return (outlen); } /* * Trim leading and trailing characters in the set defined by class * from a buffer containing a null-terminated string. * For example, if the input buffer contained "ABtext23" and class * contains "ABC123", the buffer will contain "text" on return. * * This function modifies the contents of buf in place and returns * a pointer to buf. */ char * strtrim(char *buf, const char *class) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); p += strspn(p, class); if (p != buf) { while ((*q = *p++) != '\0') ++q; } while (q != buf) { --q; if (strspn(q, class) == 0) return (buf); *q = '\0'; } return (buf); } /* * Strip the characters in the set defined by class from a buffer * containing a null-terminated string. * For example, if the input buffer contained "XYA 1textZ string3" * and class contains "123XYZ", the buffer will contain "A text string" * on return. * * This function modifies the contents of buf in place and returns * a pointer to buf. */ char * strstrip(char *buf, const char *class) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); while (*p) { p += strspn(p, class); *q++ = *p++; } *q = '\0'; return (buf); } /* * trim_whitespace * * Trim leading and trailing whitespace chars (as defined by isspace) * from a buffer. Example; if the input buffer contained " text ", * it will contain "text", when we return. We assume that the buffer * contains a null terminated string. A pointer to the buffer is * returned. */ char * trim_whitespace(char *buf) { char *p = buf; char *q = buf; if (buf == NULL) return (NULL); while (*p && isspace(*p)) ++p; while ((*q = *p++) != 0) ++q; if (q != buf) { while ((--q, isspace(*q)) != 0) *q = '\0'; } return (buf); } /* * This is the hash mechanism used to encrypt passwords for commands like * SamrSetUserInformation. It uses a 256 byte s-box. */ void rand_hash( unsigned char *data, size_t datalen, unsigned char *key, size_t keylen) { unsigned char sbox[DEFAULT_SBOX_SIZE]; unsigned char tmp; unsigned char index_i = 0; unsigned char index_j = 0; unsigned char j = 0; int i; for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) sbox[i] = (unsigned char)i; for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) { j += (sbox[i] + key[i % keylen]); tmp = sbox[i]; sbox[i] = sbox[j]; sbox[j] = tmp; } for (i = 0; i < datalen; ++i) { index_i++; index_j += sbox[index_i]; tmp = sbox[index_i]; sbox[index_i] = sbox[index_j]; sbox[index_j] = tmp; tmp = sbox[index_i] + sbox[index_j]; data[i] = data[i] ^ sbox[tmp]; } } /* * smb_chk_hostaccess * * Determines whether the specified host is in the given access list. * * We match on aliases of the hostname as well as on the canonical name. * Names in the access list may be either hosts or netgroups; they're * not distinguished syntactically. We check for hosts first because * it's cheaper (just M*N strcmp()s), then try netgroups. * * Function returns: * -1 for "all" (list is empty "" or "*") * 0 not found (host is not in the list or list is NULL) * 1 found * */ int smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list) { char addr[INET_ADDRSTRLEN]; char buff[256]; char *cstr = access_list, *gr = access_list; char *host; int clres; int i; int nentries = 0; int off; int response; int sbr = 0; struct nd_hostservlist *clnames; struct in_addr inaddr; struct sockaddr_in sa; struct sockaddr_in6 sa6; struct netbuf buf; struct netconfig *config; struct netent n, *np; if (access_list == NULL) return (0); /* If access list is empty or "*" - then it's "all" */ if (*access_list == '\0' || strcmp(access_list, "*") == 0) return (-1); switch (ipaddr->a_family) { case AF_INET: inaddr.s_addr = ipaddr->a_ipv4; sa.sin_family = AF_INET; sa.sin_port = 0; sa.sin_addr = inaddr; buf.len = buf.maxlen = sizeof (sa); buf.buf = (char *)&sa; config = getnetconfigent("tcp"); break; case AF_INET6: sa6.sin6_family = AF_INET6; sa6.sin6_port = 0; sa6.sin6_addr = ipaddr->a_ipv6; buf.len = buf.maxlen = sizeof (sa6); buf.buf = (char *)&sa6; config = getnetconfigent("tcp6"); break; default: return (1); } if (config == NULL) return (1); /* Try to lookup client hostname */ clres = __netdir_getbyaddr_nosrv(config, &clnames, &buf); freenetconfigent(config); for (;;) { if ((cstr = strpbrk(cstr, "[]:")) != NULL) { switch (*cstr) { case '[': case ']': sbr = !sbr; cstr++; continue; case ':': if (sbr) { cstr++; continue; } *cstr = '\0'; } } /* * If the list name has a '-' prepended then a match of * the following name implies failure instead of success. */ if (*gr == '-') { response = 0; gr++; } else { response = 1; } /* * First check if we have '@' entry, as it doesn't * require client hostname. */ if (*gr == '@') { gr++; if (!isdigit(*gr) && *gr != '[') { /* Netname support */ if ((np = getnetbyname_r(gr, &n, buff, sizeof (buff))) != NULL && np->n_net != 0) { while ((np->n_net & 0xFF000000u) == 0) np->n_net <<= 8; np->n_net = htonl(np->n_net); if (inet_ntop(AF_INET, &np->n_net, addr, INET_ADDRSTRLEN) == NULL) break; if (inet_matchaddr(buf.buf, addr) == 1) return (response); } } else { if (inet_matchaddr(buf.buf, gr) == 1) return (response); } if (cstr == NULL) break; gr = ++cstr; continue; } /* * No other checks can be performed if client address * can't be resolved. */ if (clres) { if (cstr == NULL) break; gr = ++cstr; continue; } /* Otherwise loop through all client hostname aliases */ for (i = 0; i < clnames->h_cnt; i++) { host = clnames->h_hostservs[i].h_host; /* * If the list name begins with a dot then * do a domain name suffix comparison. * A single dot matches any name with no * suffix. */ if (*gr == '.') { if (*(gr + 1) == '\0') { if (strchr(host, '.') == NULL) return (response); } else { off = strlen(host) - strlen(gr); if (off > 0 && strcasecmp(host + off, gr) == 0) { return (response); } } } else { /* Just do a hostname match */ if (strcasecmp(gr, host) == 0) return (response); } } nentries++; if (cstr == NULL) break; gr = ++cstr; } if (clres) return (0); return (smb_netgroup_match(clnames, access_list, nentries)); } /* * smb_netgroup_match * * Check whether any of the hostnames in clnames are * members (or non-members) of the netgroups in glist. * Since the innetgr lookup is rather expensive, the * result is cached. The cached entry is valid only * for VALID_TIME seconds. This works well because * typically these lookups occur in clusters when * a client is mounting. * * Note that this routine establishes a host membership * in a list of netgroups - we've no idea just which * netgroup in the list it is a member of. * * glist is a character array containing grc strings * representing netgroup names (optionally prefixed * with '-'). Each string is ended with '\0' and * followed immediately by the next string. */ static boolean_t smb_netgroup_match(struct nd_hostservlist *clnames, char *glist, int grc) { char **grl; char *gr; int nhosts = clnames->h_cnt; char *host; int i, j, n; boolean_t response; boolean_t belong = B_FALSE; static char *domain = NULL; if (domain == NULL) { int ssize; domain = malloc(SYS_NMLN); if (domain == NULL) return (B_FALSE); ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN); if (ssize > SYS_NMLN) { free(domain); domain = malloc(ssize); if (domain == NULL) return (B_FALSE); ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize); } /* Check for error in syscall or NULL domain name */ if (ssize <= 1) return (B_FALSE); } grl = calloc(grc, sizeof (char *)); if (grl == NULL) return (B_FALSE); for (i = 0, gr = glist; i < grc && !belong; ) { /* * If the netgroup name has a '-' prepended * then a match of this name implies a failure * instead of success. */ response = (*gr != '-') ? B_TRUE : B_FALSE; /* * Subsequent names with or without a '-' (but no mix) * can be grouped together for a single check. */ for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) { if ((response && *gr == '-') || (!response && *gr != '-')) break; grl[n] = response ? gr : gr + 1; } /* * Check the netgroup for each * of the hosts names (usually just one). */ for (j = 0; j < nhosts && !belong; j++) { host = clnames->h_hostservs[j].h_host; if (__multi_innetgr(n, grl, 1, &host, 0, NULL, 1, &domain)) belong = B_TRUE; } } free(grl); return (belong ? response : B_FALSE); } /* * Resolve the ZFS dataset from a path. * Returns, * 0 = On success. * -1 = Failure to open /etc/mnttab file or to get ZFS dataset. */ int smb_getdataset(libzfs_handle_t *libhdl, const char *path, char *dataset, size_t len) { char tmppath[MAXPATHLEN]; char *cp; FILE *fp; struct mnttab mnttab; struct mnttab mntpref; int rc = -1; /* * Optimisation: if the path is the default mountpoint then * the dataset name can be determined from path. * Attempt to open dataset by derived name and, if successful, * check if its mountpoint matches path. */ if (libhdl != NULL) { zfs_handle_t *hdl; char mountpnt[ZFS_MAXPROPLEN]; char *dsname = (char *)path + strspn(path, "/"); hdl = zfs_open(libhdl, dsname, ZFS_TYPE_FILESYSTEM); if (hdl != NULL) { if ((zfs_prop_get(hdl, ZFS_PROP_MOUNTPOINT, mountpnt, sizeof (mountpnt), NULL, NULL, 0, B_FALSE) == 0) && (strcmp(mountpnt, path) == 0)) { zfs_close(hdl); (void) strlcpy(dataset, dsname, len); return (0); } zfs_close(hdl); } } /* * Couldn't find a filesystem optimistically, use mnttab */ if ((fp = fopen(MNTTAB, "r")) == NULL) return (-1); (void) memset(&mnttab, '\0', sizeof (mnttab)); (void) strlcpy(tmppath, path, MAXPATHLEN); cp = tmppath; while (*cp != '\0') { resetmnttab(fp); (void) memset(&mntpref, '\0', sizeof (mntpref)); mntpref.mnt_mountp = tmppath; if (getmntany(fp, &mnttab, &mntpref) == 0) { if (mnttab.mnt_fstype == NULL) break; if (strcmp(mnttab.mnt_fstype, "zfs") != 0) break; /* * Ensure that there are no leading slashes * (required for zfs_open). */ cp = mnttab.mnt_special; cp += strspn(cp, "/"); (void) strlcpy(dataset, cp, len); rc = 0; break; } if (strcmp(tmppath, "/") == 0) break; if ((cp = strrchr(tmppath, '/')) == NULL) break; /* * The path has multiple components. * Remove the last component and try again. */ *cp = '\0'; if (tmppath[0] == '\0') (void) strcpy(tmppath, "/"); cp = tmppath; } (void) fclose(fp); return (rc); } /* * smb_dlopen * * Check to see if an interposer library exists. If it exists * and reports a valid version number and key (UUID), return * a handle to the library. Otherwise, return NULL. */ void * smb_dlopen(void) { uuid_t uuid; void *interposer_hdl; typedef int (*smbex_versionfn_t)(smbex_version_t *); smbex_versionfn_t getversion; smbex_version_t *version; bzero(&uuid, sizeof (uuid_t)); if (uuid_parse(SMBEX_KEY, uuid) < 0) return (NULL); interposer_hdl = dlopen(SMB_LIB_ALT, RTLD_NOW | RTLD_LOCAL); if (interposer_hdl == NULL) return (NULL); bzero(&getversion, sizeof (smbex_versionfn_t)); getversion = (smbex_versionfn_t)dlsym(interposer_hdl, "smbex_get_version"); if ((getversion == NULL) || (version = malloc(sizeof (smbex_version_t))) == NULL) { (void) dlclose(interposer_hdl); return (NULL); } bzero(version, sizeof (smbex_version_t)); if ((getversion(version) != 0) || (version->v_version != SMBEX_VERSION) || (uuid_compare(version->v_uuid, uuid) != 0)) { free(version); (void) dlclose(interposer_hdl); return (NULL); } free(version); return (interposer_hdl); } /* * smb_dlclose * * Closes handle to the interposed library. */ void smb_dlclose(void *handle) { if (handle) (void) dlclose(handle); } /* * This function is a wrapper for getnameinfo() to look up a hostname given an * IP address. The hostname returned by this function is used for constructing * the service principal name field of KRB AP-REQs. Hence, it should be * converted to lowercase for RFC 4120 section 6.2.1 conformance. */ int smb_getnameinfo(smb_inaddr_t *ip, char *hostname, int hostlen, int flags) { socklen_t salen; struct sockaddr_in6 sin6; struct sockaddr_in sin; void *sp; int rc; if (ip->a_family == AF_INET) { salen = sizeof (struct sockaddr_in); sin.sin_family = ip->a_family; sin.sin_port = 0; sin.sin_addr.s_addr = ip->a_ipv4; sp = &sin; } else { salen = sizeof (struct sockaddr_in6); sin6.sin6_family = ip->a_family; sin6.sin6_port = 0; (void) memcpy(&sin6.sin6_addr.s6_addr, &ip->a_ipv6, sizeof (sin6.sin6_addr.s6_addr)); sp = &sin6; } if ((rc = (getnameinfo((struct sockaddr *)sp, salen, hostname, hostlen, NULL, 0, flags))) == 0) (void) smb_strlwr(hostname); return (rc); } /* * A share name is considered invalid if it contains control * characters or any of the following characters (MSDN 236388). * * " / \ [ ] : | < > + ; , ? * = */ uint32_t smb_name_validate_share(const char *sharename) { const char *invalid = "\"/\\[]:|<>+;,?*="; const char *p; if (sharename == NULL) return (ERROR_INVALID_PARAMETER); if (strpbrk(sharename, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = sharename; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * User and group names are limited to 256 characters, cannot be terminated * by '.' and must not contain control characters or any of the following * characters. * * " / \ [ ] < > + ; , ? * = @ */ uint32_t smb_name_validate_account(const char *name) { const char *invalid = "\"/\\[]<>+;,?*=@"; const char *p; int len; if ((name == NULL) || (*name == '\0')) return (ERROR_INVALID_PARAMETER); len = strlen(name); if ((len > MAXNAMELEN) || (name[len - 1] == '.')) return (ERROR_INVALID_NAME); if (strpbrk(name, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = name; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Check a domain name for RFC 1035 and 1123 compliance. Domain names may * contain alphanumeric characters, hyphens and dots. The first and last * character of a label must be alphanumeric. Interior characters may be * alphanumeric or hypens. * * Domain names should not contain underscores but we allow them because * Windows names are often in non-compliance with this rule. */ uint32_t smb_name_validate_domain(const char *domain) { boolean_t new_label = B_TRUE; const char *p; char label_terminator; if (domain == NULL) return (ERROR_INVALID_PARAMETER); if (*domain == '\0') return (ERROR_INVALID_NAME); label_terminator = *domain; for (p = domain; *p != '\0'; ++p) { if (new_label) { if (!isalnum(*p)) return (ERROR_INVALID_NAME); new_label = B_FALSE; label_terminator = *p; continue; } if (*p == '.') { if (!isalnum(label_terminator)) return (ERROR_INVALID_NAME); new_label = B_TRUE; label_terminator = *p; continue; } label_terminator = *p; if (isalnum(*p) || *p == '-' || *p == '_') continue; return (ERROR_INVALID_NAME); } if (!isalnum(label_terminator)) return (ERROR_INVALID_NAME); return (ERROR_SUCCESS); } /* * A NetBIOS domain name can contain letters (a-zA-Z), numbers (0-9) and * hyphens. * * It cannot: * - be blank or longer than 15 chracters * - contain all numbers * - be the same as the computer name */ uint32_t smb_name_validate_nbdomain(const char *name) { char netbiosname[NETBIOS_NAME_SZ]; const char *p; int len; if (name == NULL) return (ERROR_INVALID_PARAMETER); len = strlen(name); if (len == 0 || len >= NETBIOS_NAME_SZ) return (ERROR_INVALID_NAME); if (strspn(name, "0123456789") == len) return (ERROR_INVALID_NAME); if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) { if (smb_strcasecmp(name, netbiosname, 0) == 0) return (ERROR_INVALID_NAME); } for (p = name; *p != '\0'; ++p) { if (isalnum(*p) || *p == '-' || *p == '_') continue; return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * A workgroup name can contain 1 to 15 characters but cannot be the same * as the NetBIOS name. The name must begin with a letter or number. * * The name cannot consist entirely of spaces or dots, which is covered * by the requirement that the name must begin with an alphanumeric * character. * * The name must not contain control characters or any of the following * characters. * * " / \ [ ] : | < > + = ; , ? */ uint32_t smb_name_validate_workgroup(const char *workgroup) { char netbiosname[NETBIOS_NAME_SZ]; const char *invalid = "\"/\\[]:|<>+=;,?"; const char *p; if (workgroup == NULL) return (ERROR_INVALID_PARAMETER); if (*workgroup == '\0' || (!isalnum(*workgroup))) return (ERROR_INVALID_NAME); if (strlen(workgroup) >= NETBIOS_NAME_SZ) return (ERROR_INVALID_NAME); if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) { if (smb_strcasecmp(workgroup, netbiosname, 0) == 0) return (ERROR_INVALID_NAME); } if (strpbrk(workgroup, invalid) != NULL) return (ERROR_INVALID_NAME); for (p = workgroup; *p != '\0'; p++) { if (iscntrl(*p)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Check for invalid characters in the given path. The list of invalid * characters includes control characters and the following: * * " / \ [ ] : | < > + ; , ? * = * * Since this is checking a path not each component, '/' is accepted * as separator not an invalid character, except as the first character * since this is supposed to be a relative path. */ uint32_t smb_name_validate_rpath(const char *relpath) { char *invalid = "\"\\[]:|<>+;,?*="; char *cp; if ((relpath == NULL) || (*relpath == '\0') || (*relpath == '/')) return (ERROR_INVALID_NAME); if (strpbrk(relpath, invalid)) return (ERROR_INVALID_NAME); for (cp = (char *)relpath; *cp != '\0'; cp++) { if (iscntrl(*cp)) return (ERROR_INVALID_NAME); } return (ERROR_SUCCESS); } /* * Parse a string to obtain the account and domain names as separate strings. * * Names containing a backslash ('\') are known as qualified or composite * names. The string preceding the backslash should be the domain name * and the string following the slash should be a name within that domain. * * Names that do not contain a backslash are known as isolated names. * An isolated name may be a single label, such as john, or may be in * user principal name (UPN) form, such as john@example.com. * * domain\name * domain/name * name * name@domain * * If we encounter any of the forms above in arg, the @, / or \ separator * is replaced by \0 and the name and domain pointers are set to point to * the appropriate components in arg. Otherwise, name and domain pointers * will be set to NULL. */ void smb_name_parse(char *arg, char **account, char **domain) { char *p; *account = NULL; *domain = NULL; if ((p = strpbrk(arg, "/\\@")) != NULL) { if (*p == '@') { *p = '\0'; ++p; *domain = p; *account = arg; } else { *p = '\0'; ++p; *account = p; *domain = arg; } } } /* * The txid is an arbitrary transaction. A new txid is returned on each call. * * 0 or -1 are not assigned so that they can be used to detect * invalid conditions. */ uint32_t smb_get_txid(void) { static mutex_t txmutex; static uint32_t txid; uint32_t txid_ret; (void) mutex_lock(&txmutex); if (txid == 0) txid = time(NULL); do { ++txid; } while (txid == 0 || txid == (uint32_t)-1); txid_ret = txid; (void) mutex_unlock(&txmutex); return (txid_ret); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include static char *wka_nbdomain[] = { "", "NT Pseudo Domain", "NT Authority", "Builtin", "Internet$" }; /* * Predefined well known accounts table */ static smb_wka_t wka_tbl[] = { { 0, "S-1-0-0", "Null", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-1-0", "Everyone", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-2-0", "Local", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-0", "Creator Owner", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-1", "Creator Group", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-2", "Creator Owner Server", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-3", "Creator Group Server", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-4", "Owner Rights", SidTypeWellKnownGroup, 0, NULL, NULL }, { 0, "S-1-3-5", "Group Rights", SidTypeWellKnownGroup, 0, NULL, NULL }, { 1, "S-1-5", "NT Pseudo Domain", SidTypeDomain, 0, NULL, NULL }, { 2, "S-1-5-1", "Dialup", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-2", "Network", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-3", "Batch", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-4", "Interactive", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-6", "Service", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-7", "Anonymous", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-8", "Proxy", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-9", "Enterprise Domain Controllers", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-10", "Self", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-11", "Authenticated Users", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-12", "Restricted", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-13", "Terminal Server User", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-14", "Remote Interactive Logon", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-15", "This Organization", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-18", "System", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-19", "Local Service", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-20", "Network Service", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-33", "Write Restricted", SidTypeWellKnownGroup, 0, NULL, NULL }, { 2, "S-1-5-1000", "Other Organization", SidTypeWellKnownGroup, 0, NULL, NULL }, { 3, "S-1-5-32", "Builtin", SidTypeDomain, 0, NULL, NULL }, { 4, "S-1-7", "Internet$", SidTypeDomain, 0, NULL, NULL }, { 3, "S-1-5-32-544", "Administrators", SidTypeAlias, SMB_WKAFLG_LGRP_ENABLE, "Members can fully administer the computer/domain", NULL }, { 3, "S-1-5-32-545", "Users", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-546", "Guests", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-547", "Power Users", SidTypeAlias, SMB_WKAFLG_LGRP_ENABLE, "Members can share directories", NULL }, { 3, "S-1-5-32-548", "Account Operators", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-549", "Server Operators", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-550", "Print Operators", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-551", "Backup Operators", SidTypeAlias, SMB_WKAFLG_LGRP_ENABLE, "Members can bypass file security to back up files", NULL }, { 3, "S-1-5-32-552", "Replicator", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-766", "Current Owner", SidTypeAlias, 0, NULL, NULL }, { 3, "S-1-5-32-767", "Current Group", SidTypeAlias, 0, NULL, NULL }, }; #define SMB_WKA_NUM (sizeof (wka_tbl)/sizeof (wka_tbl[0])) static int smb_wka_init(void); static void smb_wka_fini(void); /* * Looks up well known accounts table for the given SID. * Upon success returns a pointer to the account entry in * the table, otherwise returns NULL. */ smb_wka_t * smb_wka_lookup_sid(smb_sid_t *sid) { smb_wka_t *entry; int i; if (!smb_wka_init()) return (NULL); for (i = 0; i < SMB_WKA_NUM; ++i) { entry = &wka_tbl[i]; if (entry->wka_binsid == NULL) return (NULL); if (smb_sid_cmp(sid, entry->wka_binsid)) return (entry); } return (NULL); } /* * Looks up well known accounts table for the given name. * Upon success returns a pointer to the binary SID of the * entry, otherwise returns NULL. */ smb_sid_t * smb_wka_get_sid(const char *name) { smb_wka_t *entry; smb_sid_t *sid = NULL; if (!smb_wka_init()) return (NULL); if ((entry = smb_wka_lookup_name(name)) != NULL) sid = entry->wka_binsid; return (sid); } /* * Looks up well known accounts table for the given name. * Upon success returns a pointer to the account entry in * the table, otherwise returns NULL. */ smb_wka_t * smb_wka_lookup_name(const char *name) { smb_wka_t *entry; int i; for (i = 0; i < SMB_WKA_NUM; ++i) { entry = &wka_tbl[i]; if (!smb_strcasecmp(name, entry->wka_name, 0)) return (entry); } return (NULL); } /* * Lookup a name in the BUILTIN domain. */ smb_wka_t * smb_wka_lookup_builtin(const char *name) { smb_wka_t *entry; int i; for (i = 0; i < SMB_WKA_NUM; ++i) { entry = &wka_tbl[i]; if (entry->wka_domidx != 3) continue; if (!smb_strcasecmp(name, entry->wka_name, 0)) return (entry); } return (NULL); } /* * Returns the Netbios domain name for the given index */ char * smb_wka_get_domain(int idx) { if ((idx >= 0) && (idx < SMB_WKA_NUM)) return (wka_nbdomain[idx]); return (NULL); } /* * This function adds well known groups to groups in a user's * access token (gids). * * "Network" SID is added for all users connecting over CIFS. * * "Authenticated Users" SID is added for all users except Guest * and Anonymous. * * "Guests" SID is added for guest users and Administrators SID * is added for admin users. */ uint32_t smb_wka_token_groups(uint32_t flags, smb_ids_t *gids) { smb_id_t *id; int total_cnt; total_cnt = gids->i_cnt + 3; gids->i_ids = realloc(gids->i_ids, total_cnt * sizeof (smb_id_t)); if (gids->i_ids == NULL) return (NT_STATUS_NO_MEMORY); id = gids->i_ids + gids->i_cnt; id->i_sid = smb_sid_dup(smb_wka_get_sid("Network")); id->i_attrs = 0x7; if (id->i_sid == NULL) return (NT_STATUS_NO_MEMORY); id++; gids->i_cnt++; if ((flags & SMB_ATF_ANON) == 0) { if (flags & SMB_ATF_GUEST) id->i_sid = smb_sid_dup(smb_wka_get_sid("Guests")); else id->i_sid = smb_sid_dup(smb_wka_get_sid("Authenticated Users")); id->i_attrs = 0x7; if (id->i_sid == NULL) return (NT_STATUS_NO_MEMORY); id++; gids->i_cnt++; } if (flags & SMB_ATF_ADMIN) { id->i_sid = smb_sid_dup(smb_wka_get_sid("Administrators")); id->i_attrs = 0x7; if (id->i_sid == NULL) return (NT_STATUS_NO_MEMORY); gids->i_cnt++; } return (NT_STATUS_SUCCESS); } /* * Generate binary SIDs from the string SIDs for the well-known * accounts table. Callers MUST not free the binary SID pointer. */ static int smb_wka_init(void) { static boolean_t wka_init = B_FALSE; static mutex_t wka_mutex; smb_wka_t *entry; int i; (void) mutex_lock(&wka_mutex); if (wka_init) { (void) mutex_unlock(&wka_mutex); return (B_TRUE); } for (i = 0; i < SMB_WKA_NUM; ++i) { entry = &wka_tbl[i]; entry->wka_binsid = smb_sid_fromstr(entry->wka_sid); if (entry->wka_binsid == NULL) { smb_wka_fini(); (void) mutex_unlock(&wka_mutex); return (B_FALSE); } } wka_init = B_TRUE; (void) mutex_unlock(&wka_mutex); return (B_TRUE); } /* * Private cleanup for smb_wka_init. */ static void smb_wka_fini(void) { int i; for (i = 0; i < SMB_WKA_NUM; ++i) { if (wka_tbl[i].wka_binsid) { free(wka_tbl[i].wka_binsid); wka_tbl[i].wka_binsid = NULL; } } }