| /* |
| * 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. |
| * Copyright 2018 RackTop Systems. |
| */ |
| |
| #include <strings.h> |
| #include <smbsrv/libsmb.h> |
| |
| 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); |
| 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); |
| } |
| } |