diff --git a/auth-passwd.c b/auth-passwd.c index a074ffe5f..997daa025 100644 --- a/auth-passwd.c +++ b/auth-passwd.c @@ -57,7 +57,10 @@ #include "authfd.h" #ifdef WINDOWS +#define SECURITY_WIN32 +#include #include "logonuser.h" +#include "misc_internal.h" #include "monitor_wrap.h" #endif @@ -271,23 +274,48 @@ done: int sys_auth_passwd(struct ssh *ssh, const char *password) { + wchar_t *user_utf16 = NULL, *pwd_utf16 = NULL, *unam_utf16 = NULL, *udom_utf16 = L"."; Authctxt *authctxt = ssh->authctxt; - wchar_t *user_utf16 = NULL, *udom_utf16 = NULL, *pwd_utf16 = NULL, *tmp; HANDLE token = NULL; - int r = 0; + WCHAR domain_upn[MAX_UPN_LEN + 1]; + ULONG domain_upn_len = ARRAYSIZE(domain_upn); - if ((user_utf16 = utf8_to_utf16(authctxt->pw->pw_name)) == NULL || - (pwd_utf16 = utf8_to_utf16(password)) == NULL) { - fatal("out of memory"); + user_utf16 = utf8_to_utf16(authctxt->pw->pw_name); + pwd_utf16 = utf8_to_utf16(password); + if (user_utf16 == NULL || pwd_utf16 == NULL) { + debug("out of memory"); goto done; } + + /* the format for the user will be constrained to the output of get_passwd() + * so only the only two formats are NetBiosDomain\SamAccountName which is + * a domain account or just SamAccountName in which is a local account */ + + /* default assumption - local user */ + unam_utf16 = user_utf16; - if ((tmp = wcschr(user_utf16, L'@')) != NULL) { - udom_utf16 = tmp + 1; - *tmp = L'\0'; + /* translate to domain user if format contains a backslash */ + wchar_t * backslash = wcschr(user_utf16, L'\\'); + if (backslash != NULL) { + + /* attempt to format into upn format as this is preferred for login */ + if (TranslateNameW(user_utf16, NameSamCompatible, + NameUserPrincipal, domain_upn, &domain_upn_len) != 0) { + unam_utf16 = domain_upn; + udom_utf16 = NULL; + } + + /* user likely does not have upn so just use SamCompatibleName */ + else { + debug3("%s: Unable to discover upn for user '%s': %d", + __FUNCTION__, user_utf16, GetLastError()); + *backslash = '\0'; + unam_utf16 = backslash + 1; + udom_utf16 = user_utf16; + } } - if (LogonUserExExWHelper(user_utf16, udom_utf16, pwd_utf16, LOGON32_LOGON_NETWORK_CLEARTEXT, + if (LogonUserExExWHelper(unam_utf16, udom_utf16, pwd_utf16, LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, NULL, &token, NULL, NULL, NULL, NULL) == TRUE) password_auth_token = token; else { @@ -304,17 +332,14 @@ sys_auth_passwd(struct ssh *ssh, const char *password) sys_auth_passwd_lsa(authctxt, password); } } - + done: - if (password_auth_token) - r = 1; if (user_utf16) free(user_utf16); - if (pwd_utf16) SecureZeroMemory(pwd_utf16, sizeof(wchar_t) * wcslen(pwd_utf16)); - return r; + return (password_auth_token) ? 1 : 0; } #endif /* WINDOWS */ diff --git a/contrib/win32/openssh/sshd.vcxproj b/contrib/win32/openssh/sshd.vcxproj index 7a0a92649..4d5f93075 100644 --- a/contrib/win32/openssh/sshd.vcxproj +++ b/contrib/win32/openssh/sshd.vcxproj @@ -462,8 +462,6 @@ - - diff --git a/contrib/win32/openssh/win32iocompat.vcxproj b/contrib/win32/openssh/win32iocompat.vcxproj index 2798ea626..d58a121d0 100644 --- a/contrib/win32/openssh/win32iocompat.vcxproj +++ b/contrib/win32/openssh/win32iocompat.vcxproj @@ -290,6 +290,8 @@ + + diff --git a/contrib/win32/win32compat/inc/grp.h b/contrib/win32/win32compat/inc/grp.h index e28862962..9124fec72 100644 --- a/contrib/win32/win32compat/inc/grp.h +++ b/contrib/win32/win32compat/inc/grp.h @@ -3,14 +3,6 @@ #include #include "sys/types.h" -typedef enum { - LOCAL_GROUP = 0, - DOMAIN_GROUP = 1, - GLOBAL_UNIVERSAL_GROUP = 2 -} group_type; - char ** getusergroups(const char *user, int *numgroups); -void populate_user_groups(char **group_name, int *group_index, DWORD groupsread, DWORD totalgroups, LPBYTE buf, group_type groupType); -void print_user_groups(const char *user, char **user_groups, int num_user_groups); #endif diff --git a/contrib/win32/win32compat/inc/pwd.h b/contrib/win32/win32compat/inc/pwd.h index 7728ccf35..5ea4375e7 100644 --- a/contrib/win32/win32compat/inc/pwd.h +++ b/contrib/win32/win32compat/inc/pwd.h @@ -38,7 +38,6 @@ char *user_from_uid(uid_t uid, int nouser); struct passwd *w32_getpwuid(uid_t uid); struct passwd *w32_getpwnam(const char *username); -struct passwd* w32_getpwtoken(HANDLE); struct passwd *getpwent(void); void endpwent(void); diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 2c272af13..3bcf5f336 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -1,6 +1,9 @@ /* * Author: Manoj Ampalam * +* Author: Bryan Berns +* Modified group detection use s4u token information +* * Copyright(c) 2016 Microsoft Corp. * All rights reserved * @@ -28,6 +31,8 @@ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#define UMDF_USING_NTSTATUS +#define SECURITY_WIN32 #include #include #include @@ -36,6 +41,9 @@ #include #include #include +#include +#include +#include #include "inc\unistd.h" #include "inc\sys\stat.h" @@ -49,6 +57,7 @@ #include "inc\fcntl.h" #include "inc\utf.h" #include "signal_internal.h" +#include "misc_internal.h" #include "debug.h" #include "w32fd.h" #include "inc\string.h" @@ -1086,28 +1095,6 @@ invalid_parameter_handler(const wchar_t* expression, const wchar_t* function, co debug3("Expression: %s", expression); } -int -get_machine_domain_name(wchar_t *domain, int size) -{ - LPWKSTA_INFO_100 pBuf = NULL; - NET_API_STATUS nStatus; - LPWSTR pszServerName = NULL; - - nStatus = NetWkstaGetInfo(pszServerName, 100, (LPBYTE *)&pBuf); - if (nStatus != NERR_Success) { - error("Unable to fetch the machine domain, error:%d\n", nStatus); - return 0; - } - - debug3("Machine domain:%ls", pBuf->wki100_langroup); - wcscpy_s(domain, size, pBuf->wki100_langroup); - - if (pBuf != NULL) - NetApiBufferFree(pBuf); - - return 1; -} - /* * This method will fetch all the groups (listed below) even if the user is indirectly a member. * - Local machine groups @@ -1117,216 +1104,164 @@ get_machine_domain_name(wchar_t *domain, int size) */ char ** getusergroups(const char *user, int *ngroups) -{ - LPGROUP_USERS_INFO_0 local_groups = NULL; - LPGROUP_USERS_INFO_0 domain_groups = NULL; - LPGROUP_USERS_INFO_0 global_universal_groups = NULL; - DWORD num_local_groups_read = 0; - DWORD total_local_groups = 0; - DWORD num_domain_groups_read = 0; - DWORD total_domain_groups = 0; - DWORD num_global_universal_groups_read = 0; - DWORD total_global_universal_groups = 0; - - DWORD flags = LG_INCLUDE_INDIRECT; - NET_API_STATUS nStatus; - wchar_t *user_name_utf16 = NULL; - char *user_domain = NULL; - LPWSTR dc_name_utf16 = NULL; - char **user_groups = NULL; - int num_user_groups = 0; - wchar_t machine_domain_name_utf16[DNLEN + 1] = { 0 }; - wchar_t local_user_fmt_utf16[UNLEN + DNLEN + 2] = { 0 }; - size_t local_user_fmt_len = UNLEN + DNLEN + 2; - char *user_name = NULL; - - user_name = malloc(strlen(user)+1); - if(!user_name) { - error("failed to allocate memory!"); - goto cleanup; - } - - memcpy(user_name, user, strlen(user)+1); - - if (user_domain = strchr(user_name, '@')) { - char *t = user_domain; - user_domain++; - *t='\0'; - } - - user_name_utf16 = utf8_to_utf16(user_name); - if (!user_name_utf16) { - error("utf8_to_utf16 failed! for %s", user_name); - goto cleanup; - } - - /* Fetch groups on the Local machine */ - if(get_machine_domain_name(machine_domain_name_utf16, DNLEN+1)) { - if (machine_domain_name_utf16) { - if(!machine_domain_name) - machine_domain_name = utf16_to_utf8(machine_domain_name_utf16); - - if (user_domain) { - wcscpy_s(local_user_fmt_utf16, local_user_fmt_len, machine_domain_name_utf16); - wcscat_s(local_user_fmt_utf16, local_user_fmt_len, L"\\"); - } - - wcscat_s(local_user_fmt_utf16, local_user_fmt_len, user_name_utf16); - nStatus = NetUserGetLocalGroups(NULL, - local_user_fmt_utf16, - 0, - flags, - (LPBYTE *)&local_groups, - MAX_PREFERRED_LENGTH, - &num_local_groups_read, - &total_local_groups); - - if (NERR_Success != nStatus) - error("Failed to get local groups on this machine, error: %d\n", nStatus); - } - } - - if (user_domain) { - /* Fetch Domain groups */ - nStatus = NetGetDCName(NULL, machine_domain_name_utf16, (LPBYTE *)&dc_name_utf16); - if (NERR_Success == nStatus) { - debug3("domain controller name: %ls", dc_name_utf16); - - nStatus = NetUserGetLocalGroups(dc_name_utf16, - user_name_utf16, - 0, - flags, - (LPBYTE *)&domain_groups, - MAX_PREFERRED_LENGTH, - &num_domain_groups_read, - &total_domain_groups); - - if (NERR_Success != nStatus) - error("Failed to get domain groups from DC:%s error: %d\n", dc_name_utf16, nStatus); - } - else - error("Failed to get the domain controller name, error: %d\n", nStatus); - - /* Fetch global, universal groups */ - nStatus = NetUserGetGroups(dc_name_utf16, - user_name_utf16, - 0, - (LPBYTE *)&global_universal_groups, - MAX_PREFERRED_LENGTH, - &num_global_universal_groups_read, - &total_global_universal_groups); - - if (NERR_Success != nStatus) - error("Failed to get global,universal groups from DC:%ls error: %d\n", dc_name_utf16, nStatus); - } - - int total_user_groups = num_local_groups_read + num_domain_groups_read + num_global_universal_groups_read; - - /* populate the output */ - user_groups = malloc(total_user_groups * sizeof(*user_groups)); - - populate_user_groups(user_groups, &num_user_groups, num_local_groups_read, total_local_groups, (LPBYTE) local_groups, LOCAL_GROUP); - if (user_domain) { - populate_user_groups(user_groups, &num_user_groups, num_domain_groups_read, total_domain_groups, (LPBYTE)domain_groups, DOMAIN_GROUP); - populate_user_groups(user_groups, &num_user_groups, num_global_universal_groups_read, total_global_universal_groups, (LPBYTE)global_universal_groups, GLOBAL_UNIVERSAL_GROUP); - } - - for (int i = 0; i < num_user_groups; i++) - to_lower_case(user_groups[i]); - - print_user_groups(user, user_groups, num_user_groups); - - cleanup: - if(local_groups) - NetApiBufferFree(local_groups); - - if(domain_groups) - NetApiBufferFree(domain_groups); - - if(global_universal_groups) - NetApiBufferFree(global_universal_groups); - - if(dc_name_utf16) - NetApiBufferFree(dc_name_utf16); - - if(user_name_utf16) - free(user_name_utf16); - - if(user_name) - free(user_name); - - *ngroups = num_user_groups; - return user_groups; -} - -/* This method will return in "group@domain" format */ -char * -append_domain_to_groupname(char *groupname) { - if(!groupname) return NULL; + /* early declarations and initializations to support cleanup */ + HANDLE logon_token = NULL; + PTOKEN_GROUPS group_buf = NULL; + PLSA_REFERENCED_DOMAIN_LIST domain_list = NULL; + PLSA_TRANSLATED_NAME name_list = NULL; + PSID * group_sids = NULL; + LSA_HANDLE lsa_policy = NULL; - int len = (int) strlen(machine_domain_name) + (int) strlen(groupname) + 2; - char *groupname_with_domain = malloc(len); - if(!groupname_with_domain) { - error("failed to allocate memory!"); + /* initialize return values */ + errno = 0; + *ngroups = 0; + char ** user_groups = NULL; + + /* fetch the computer name so we can determine if the specified user is local or not */ + wchar_t computer_name[CNLEN + 1]; + DWORD computer_name_size = ARRAYSIZE(computer_name); + if (GetComputerNameW(computer_name, &computer_name_size) == 0) { + goto cleanup; + } + + /* get token that can be used for getting group information */ + if ((logon_token = get_user_token((char *)user, 0)) == NULL) { + debug3("%s: get_user_token() failed for user %s.", __FUNCTION__, user); + goto cleanup; + } + + /* allocate area for group information */ + DWORD group_size = 0; + if (GetTokenInformation(logon_token, TokenGroups, NULL, 0, &group_size) == 0 + && GetLastError() != ERROR_INSUFFICIENT_BUFFER || + (group_buf = (PTOKEN_GROUPS)malloc(group_size)) == NULL) { + debug3("%s: GetTokenInformation() failed: %d", __FUNCTION__, GetLastError()); + goto cleanup; + } + + /* read group sids from logon token -- this will return a list of groups + * similiar to the data returned when you do a whoami /groups command */ + if (GetTokenInformation(logon_token, TokenGroups, group_buf, group_size, &group_size) == 0) { + debug3("%s: GetTokenInformation() failed for user '%s'.", __FUNCTION__, user); + goto cleanup; + } + + /* allocate and copy the sids to a a structure we can pass to lookup */ + if ((group_sids = (PSID *)malloc(sizeof(PSID) * group_buf->GroupCount)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + DWORD group_sids_count = 0; + for (DWORD i = 0; i < group_buf->GroupCount; i++) { + + /* only bother with group thats are 'enabled' from a security perspective */ + if ((group_buf->Groups[i].Attributes & SE_GROUP_ENABLED) == 0 || + !IsValidSid(group_buf->Groups[i].Sid)) + continue; + + /* only bother with groups that are builtin or classic domain/local groups + * also ignore domain users and builtin users since these will be meaningless + * since they do not resolve properly on workgroup computers; these would + * never meaningfully be used in the server configuration */ + SID * sid = group_buf->Groups[i].Sid; + DWORD sub = sid->SubAuthority[0]; + DWORD rid = sid->SubAuthority[sid->SubAuthorityCount - 1]; + SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY; + if (memcmp(&nt_authority, GetSidIdentifierAuthority(sid), sizeof(SID_IDENTIFIER_AUTHORITY)) == 0 && ( + sub == SECURITY_NT_NON_UNIQUE || sub == SECURITY_BUILTIN_DOMAIN_RID) && + rid != DOMAIN_GROUP_RID_USERS && rid != DOMAIN_ALIAS_RID_USERS) { + group_sids[group_sids_count++] = sid; + } + } + + /* open a new connection to the lsa policy provider for group lookup */ + LSA_OBJECT_ATTRIBUTES ObjectAttributes; + ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes)); + if (LsaOpenPolicy(NULL, &ObjectAttributes, POLICY_LOOKUP_NAMES, &lsa_policy) != 0) { + debug3("%s: LsaOpenPolicy() failed for user '%s'.", __FUNCTION__, user); + goto cleanup; + } + + /* translate all the sids to real group names */ + NTSTATUS lsa_ret = LsaLookupSids(lsa_policy, group_sids_count, group_sids, &domain_list, &name_list); + if (lsa_ret != STATUS_SUCCESS) { + debug3("%s: LsaLookupSids() failed for user '%s' with return %d.", __FUNCTION__, user, lsa_ret); + goto cleanup; + } + + /* allocate memory to hold points to all group names; we double the value + * in order to account for local groups that we trim the domain qualifier */ + if ((user_groups = (char**)malloc(sizeof(char*) * group_sids_count * 2)) == NULL) { + errno = ENOMEM; + goto cleanup; + } + + /* enumerate all groups and add to group list */ + for (DWORD group_index = 0; group_index < group_sids_count; group_index++) { + + /* ignore unresolvable or invalid domains */ + if (name_list[group_index].DomainIndex == -1) + continue; + + PLSA_UNICODE_STRING domain = &(domain_list->Domains[name_list[group_index].DomainIndex].Name); + PLSA_UNICODE_STRING name = &(name_list[group_index].Name); + + /* add group name in netbios\\name format */ + int current_group = (*ngroups)++; + wchar_t formatted_group[DNLEN + 1 + GNLEN + 1]; + swprintf_s(formatted_group, ARRAYSIZE(formatted_group), L"%wZ\\%wZ", domain, name); + _wcslwr_s(formatted_group, ARRAYSIZE(formatted_group)); + debug3("Added group '%ls' for user '%s'.", formatted_group, user); + user_groups[current_group] = utf16_to_utf8(formatted_group); + if (user_groups[current_group] == NULL) { + errno = ENOMEM; + goto cleanup; + } + + /* for local accounts trim the domain qualifier */ + if (wcslen(computer_name) == domain->Length / sizeof(wchar_t) && + _wcsnicmp(computer_name, domain->Buffer, domain->Length / sizeof(wchar_t)) == 0) + { + current_group = (*ngroups)++; + swprintf_s(formatted_group, ARRAYSIZE(formatted_group), L"%wZ", name); + _wcslwr_s(formatted_group, ARRAYSIZE(formatted_group)); + debug3("Added group '%ls' for user '%s'.", formatted_group, user); + user_groups[current_group] = utf16_to_utf8(formatted_group); + if (user_groups[current_group] == NULL) { + errno = ENOMEM; + goto cleanup; + } + } + } + +cleanup: + + if (domain_list) + LsaFreeMemory(domain_list); + if (name_list) + LsaFreeMemory(name_list); + if (group_buf) + free(group_buf); + if (logon_token) + CloseHandle(logon_token); + if (group_sids) + free(group_sids); + if (lsa_policy) + LsaClose(lsa_policy); + + /* special cleanup - if ran out of memory while allocating groups */ + if (user_groups && errno == ENOMEM || *ngroups == 0) { + for (int group = 0; group < *ngroups; group++) + if (user_groups[group]) free(user_groups[group]); + *ngroups = 0; + free(user_groups); return NULL; } - strcpy_s(groupname_with_domain, len, groupname); - strcat_s(groupname_with_domain, len, "@"); - strcat_s(groupname_with_domain, len, machine_domain_name); - - groupname_with_domain[len-1]= '\0'; - - return groupname_with_domain; -} - -void -populate_user_groups(char **group_name, int *group_index, DWORD groupsread, DWORD totalgroups, LPBYTE buf, group_type groupType) -{ - if(0 == groupsread) return; - char *user_group_name = NULL; - - if (groupType == GLOBAL_UNIVERSAL_GROUP) { - LPGROUP_USERS_INFO_0 pTmpBuf = (LPGROUP_USERS_INFO_0)buf; - for (DWORD i = 0; (i < groupsread) && pTmpBuf; i++, pTmpBuf++) { - if (!(user_group_name = utf16_to_utf8(pTmpBuf->grui0_name))) { - error("utf16_to_utf8 failed to convert:%ls", pTmpBuf->grui0_name); - return; - } - - group_name[*group_index] = append_domain_to_groupname(user_group_name); - if(group_name[*group_index]) - (*group_index)++; - } - } else { - LPLOCALGROUP_USERS_INFO_0 pTmpBuf = (LPLOCALGROUP_USERS_INFO_0)buf; - for (DWORD i = 0; (i < groupsread) && pTmpBuf; i++, pTmpBuf++) { - if (!(user_group_name = utf16_to_utf8(pTmpBuf->lgrui0_name))) { - error("utf16_to_utf8 failed to convert:%ls", pTmpBuf->lgrui0_name); - return; - } - - if(groupType == DOMAIN_GROUP) - group_name[*group_index] = append_domain_to_groupname(user_group_name); - else - group_name[*group_index] = user_group_name; - - if (group_name[*group_index]) - (*group_index)++; - } - } - - if (groupsread < totalgroups) - error("groupsread:%d totalgroups:%d groupType:%d", groupsread, totalgroups, groupType); -} - -void -print_user_groups(const char *user, char **user_groups, int num_user_groups) -{ - debug3("Group list for user:%s", user); - for(int i=0; i < num_user_groups; i++) - debug3("group name:%s", user_groups[i]); + /* downsize the array to the actual size and return */ + return (char**)realloc(user_groups, sizeof(char*) * (*ngroups)); } void diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index ef7a563d7..f1082b63a 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -22,6 +22,9 @@ #define errno_from_Win32LastError() errno_from_Win32Error(GetLastError()) +/* maximum size for user principal name as defined in ad schema */ +#define MAX_UPN_LEN 1024 + static char *machine_domain_name; /* removes first '/' for Windows paths that are unix styled. Ex: /c:/ab.cd */ @@ -38,9 +41,8 @@ void file_time_to_unix_time(const LPFILETIME, time_t *); int file_attr_to_st_mode(wchar_t * path, DWORD attributes); void invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); void to_lower_case(char *s); -int get_machine_domain_name(wchar_t *domain, int size); wchar_t* get_program_data_path(); -HANDLE get_user_token(char* user); +HANDLE get_user_token(char* user, int impersonation); int load_user_profile(HANDLE user_token, char* user); int create_directory_withsddl(wchar_t *path, wchar_t *sddl); int is_absolute_path(const char *); \ No newline at end of file diff --git a/contrib/win32/win32compat/pwd.c b/contrib/win32/win32compat/pwd.c index b7764497d..118e298d8 100644 --- a/contrib/win32/win32compat/pwd.c +++ b/contrib/win32/win32compat/pwd.c @@ -1,6 +1,10 @@ /* * Author: NoMachine * + * Author: Bryan Berns + * Normalized and optimized login routines and added support for + * internet-linked accounts. + * * Copyright (c) 2009, 2011 NoMachine * All rights reserved * @@ -99,213 +103,145 @@ reset_pw() } static struct passwd* -get_passwd(const char *user_utf8, LPWSTR user_sid) +get_passwd(const wchar_t * user_utf16, PSID sid) { + wchar_t user_resolved[DNLEN + 1 + UNLEN + 1]; struct passwd *ret = NULL; - wchar_t *user_utf16 = NULL, *uname_utf16, *udom_utf16, *tmp; - char *uname_utf8 = NULL, *uname_upn = NULL, *udom_utf8 = NULL, *pw_home_utf8 = NULL, *user_sid_utf8 = NULL; - LPBYTE user_info = NULL; - LPWSTR user_sid_local = NULL; + wchar_t *sid_string = NULL; wchar_t reg_path[PATH_MAX], profile_home[PATH_MAX], profile_home_exp[PATH_MAX]; - HKEY reg_key = 0; - int tmp_len = PATH_MAX; - PDOMAIN_CONTROLLER_INFOW pdc = NULL; - DWORD dsStatus, uname_upn_len = 0, uname_len = 0, udom_len = 0; - wchar_t wmachine_name[MAX_COMPUTERNAME_LENGTH + 1]; - DWORD wmachine_name_len = MAX_COMPUTERNAME_LENGTH + 1; - errno_t r = 0; + DWORD reg_path_len = PATH_MAX; + HKEY reg_key = 0; + + BYTE binary_sid[SECURITY_MAX_SID_SIZE]; + DWORD sid_size = ARRAYSIZE(binary_sid); + WCHAR domain_name[DNLEN + 1] = L""; + DWORD domain_name_size = DNLEN + 1; + SID_NAME_USE account_type = 0; errno = 0; reset_pw(); - if ((user_utf16 = utf8_to_utf16(user_utf8)) == NULL) { - errno = ENOMEM; - goto done; + + /* skip forward lookup on name if sid was passed in */ + if (sid != NULL) + CopySid(sizeof(binary_sid), binary_sid, sid); + + /* attempt to lookup the account; this will verify the account is valid and + * is will return its sid and the realm that owns it */ + else if(LookupAccountNameW(NULL, user_utf16, binary_sid, &sid_size, + domain_name, &domain_name_size, &account_type) == 0) { + errno = ENOENT; + debug("%s: LookupAccountName() failed: %d.", __FUNCTION__, GetLastError()); + goto cleanup; } - /*find domain part if any*/ - if ((tmp = wcschr(user_utf16, L'\\')) != NULL) { - udom_utf16 = user_utf16; - uname_utf16 = tmp + 1; - *tmp = L'\0'; - - } else if ((tmp = wcschr(user_utf16, L'@')) != NULL) { - udom_utf16 = tmp + 1; - uname_utf16 = user_utf16; - *tmp = L'\0'; - } else { - uname_utf16 = user_utf16; - udom_utf16 = NULL; + /* convert the binary string to a string */ + if (ConvertSidToStringSidW((PSID) binary_sid, &sid_string) == FALSE) { + errno = ENOENT; + goto cleanup; } - if (udom_utf16) { - /* this should never fail */ - GetComputerNameW(wmachine_name, &wmachine_name_len); - /* If this is a local account (domain part and computer name are the same), strip out domain */ - if (_wcsicmp(udom_utf16, wmachine_name) == 0) - udom_utf16 = NULL; + /* lookup the account name from the sid */ + WCHAR user_name[UNLEN + 1]; + DWORD user_name_length = ARRAYSIZE(user_name); + domain_name_size = DNLEN + 1; + if (LookupAccountSidW(NULL, binary_sid, user_name, &user_name_length, + domain_name, &domain_name_size, &account_type) == 0) { + errno = ENOENT; + debug("%s: LookupAccountSid() failed: %d.", __FUNCTION__, GetLastError()); + goto cleanup; } - if (user_sid == NULL) { - NET_API_STATUS status; - if ((status = NetUserGetInfo(udom_utf16, uname_utf16, 23, &user_info)) != NERR_Success) { - debug3("NetUserGetInfo() failed with error: %d for user: %ls and domain: %ls \n", status, uname_utf16, udom_utf16); - - if ((dsStatus = DsGetDcNameW(NULL, udom_utf16, NULL, NULL, DS_DIRECTORY_SERVICE_PREFERRED, &pdc)) != ERROR_SUCCESS) { - error("DsGetDcNameW() failed with error: %d \n", dsStatus); - errno = ENOENT; - goto done; - } - - if ((status = NetUserGetInfo(pdc->DomainControllerName, uname_utf16, 23, &user_info)) != NERR_Success) { - debug3("NetUserGetInfo() with domainController: %ls failed with error: %d \n", pdc->DomainControllerName, status); - errno = ENOENT; - goto done; - } - } - - if (ConvertSidToStringSidW(((LPUSER_INFO_23)user_info)->usri23_user_sid, &user_sid_local) == FALSE) { - debug3("NetUserGetInfo() Succeded but ConvertSidToStringSidW() failed with error: %d\n", GetLastError()); - errno = ENOENT; - goto done; - } - - user_sid = user_sid_local; + /* verify passed account is actually a user account */ + if (account_type != SidTypeUser) { + errno = ENOENT; + debug3("%s: Invalid account type: %d.", __FUNCTION__, account_type); + goto cleanup; } + /* fetch the computer name so we can determine if the specified user is local or not */ + wchar_t computer_name[CNLEN + 1]; + DWORD computer_name_size = ARRAYSIZE(computer_name); + if (GetComputerNameW(computer_name, &computer_name_size) == 0) { + goto cleanup; + } + + /* if standard local user name, just use name without decoration */ + if (_wcsicmp(domain_name, computer_name) == 0) + wcscpy_s(user_resolved, ARRAYSIZE(user_resolved), user_name); + + /* put any other format in sam compatible format */ + else + swprintf_s(user_resolved, ARRAYSIZE(user_resolved), L"%s\\%s", domain_name, user_name); + /* if one of below fails, set profile path to Windows directory */ - if (swprintf_s(reg_path, PATH_MAX, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%ls", user_sid) == -1 || + if (swprintf_s(reg_path, PATH_MAX, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList\\%ls", sid_string) == -1 || RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY, ®_key) != 0 || - RegQueryValueExW(reg_key, L"ProfileImagePath", 0, NULL, (LPBYTE)profile_home, &tmp_len) != 0 || - ExpandEnvironmentStringsW(profile_home, NULL, 0) > PATH_MAX || + RegQueryValueExW(reg_key, L"ProfileImagePath", 0, NULL, (LPBYTE)profile_home, ®_path_len) != 0 || + ExpandEnvironmentStringsW(profile_home, NULL, 0) > PATH_MAX || ExpandEnvironmentStringsW(profile_home, profile_home_exp, PATH_MAX) == 0) if (GetWindowsDirectoryW(profile_home_exp, PATH_MAX) == 0) { debug3("GetWindowsDirectoryW failed with %d", GetLastError()); errno = EOTHER; - goto done; + goto cleanup; } - if ((uname_utf8 = utf16_to_utf8(uname_utf16)) == NULL || - (udom_utf16 && (udom_utf8 = utf16_to_utf8(udom_utf16)) == NULL) || - (pw_home_utf8 = utf16_to_utf8(profile_home_exp)) == NULL || - (user_sid_utf8 = utf16_to_utf8(user_sid)) == NULL) { + /* convert to utf8, make name lowercase, and assign to output structure*/ + _wcslwr_s(user_resolved, wcslen(user_resolved) + 1); + if ((pw.pw_name = utf16_to_utf8(user_resolved)) == NULL || + (pw.pw_dir = utf16_to_utf8(profile_home_exp)) == NULL || + (pw.pw_sid = utf16_to_utf8(sid_string)) == NULL) { + reset_pw(); errno = ENOMEM; - goto done; - } - uname_len = (DWORD)strlen(uname_utf8); - uname_upn_len = uname_len + 1; - if (udom_utf8) { - udom_len = (DWORD)strlen(udom_utf8); - uname_upn_len += udom_len + 1; + goto cleanup; } - if ((uname_upn = malloc(uname_upn_len)) == NULL) { - errno = ENOMEM; - goto done; - } - - if ((r = memcpy_s(uname_upn, uname_upn_len, uname_utf8, uname_len + 1)) != 0) { - debug3("memcpy_s failed with error: %d.", r); - goto done; - } - if (udom_utf8) { - /* TODO - get domain FQDN */ - uname_upn[uname_len] = '@'; - if ((r = memcpy_s(uname_upn + uname_len + 1, udom_len + 1, udom_utf8, udom_len + 1)) != 0) { - debug3("memcpy_s failed with error: %d.", r); - goto done; - } - } - - to_lower_case(uname_upn); - pw.pw_name = uname_upn; - uname_upn = NULL; - pw.pw_dir = pw_home_utf8; - pw_home_utf8 = NULL; - pw.pw_sid = user_sid_utf8; - user_sid_utf8 = NULL; ret = &pw; -done: - if (user_utf16) - free(user_utf16); - if (uname_utf8) - free(uname_utf8); - if (uname_upn) - free(uname_upn); - if (udom_utf8) - free(udom_utf8); - if (pw_home_utf8) - free(pw_home_utf8); - if (user_sid_utf8) - free(user_sid_utf8); - if (user_info) - NetApiBufferFree(user_info); - if (user_sid_local) - LocalFree(user_sid_local); +cleanup: + + if (sid_string) + LocalFree(sid_string); if (reg_key) RegCloseKey(reg_key); - if (pdc) - NetApiBufferFree(pdc); + return ret; } struct passwd* w32_getpwnam(const char *user_utf8) { - return get_passwd(user_utf8, NULL); -} - -struct passwd* -w32_getpwtoken(HANDLE t) -{ - wchar_t* wuser = NULL; - char* user_utf8 = NULL; - ULONG needed = 0; - struct passwd *ret = NULL; - DWORD info_len = 0; - TOKEN_USER* info = NULL; - LPWSTR user_sid = NULL; - - errno = 0; - - if (GetUserNameExW(NameSamCompatible, NULL, &needed) != 0 || - (wuser = malloc(needed * sizeof(wchar_t))) == NULL || - GetUserNameExW(NameSamCompatible, wuser, &needed) == 0 || - (user_utf8 = utf16_to_utf8(wuser)) == NULL || - GetTokenInformation(t, TokenUser, NULL, 0, &info_len) == TRUE || - (info = (TOKEN_USER*)malloc(info_len)) == NULL || - GetTokenInformation(t, TokenUser, info, info_len, &info_len) == FALSE || - ConvertSidToStringSidW(info->User.Sid, &user_sid) == FALSE) { + wchar_t * user_utf16 = utf8_to_utf16(user_utf8); + if (user_utf16 == NULL) { errno = ENOMEM; - goto done; + return NULL; } - ret = get_passwd(user_utf8, user_sid); -done: - if (wuser) - free(wuser); - if (user_utf8) - free(user_utf8); - if (info) - free(info); - if (user_sid) - LocalFree(user_sid); - return ret; + return get_passwd(user_utf16, NULL); } struct passwd* w32_getpwuid(uid_t uid) { - HANDLE token; - struct passwd* ret; - if ((OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) == FALSE) { - debug("unable to get process token"); - errno = EOTHER; - return NULL; - } + struct passwd* ret = NULL; + HANDLE token = NULL; + TOKEN_USER* info = NULL; + DWORD info_len = 0; + + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) == FALSE || + GetTokenInformation(token, TokenUser, NULL, 0, &info_len) == TRUE || + (info = (TOKEN_USER*)malloc(info_len)) == NULL || + GetTokenInformation(token, TokenUser, info, info_len, &info_len) == FALSE) + goto cleanup; + + ret = get_passwd(NULL, info->User.Sid); + +cleanup: + + if (token) + CloseHandle(token); + if (info) + free(info); - ret = w32_getpwtoken(token); - CloseHandle(token); return ret; } diff --git a/contrib/win32/win32compat/spawn-ext.c b/contrib/win32/win32compat/spawn-ext.c index 41a41c169..80d6dc569 100644 --- a/contrib/win32/win32compat/spawn-ext.c +++ b/contrib/win32/win32compat/spawn-ext.c @@ -13,7 +13,7 @@ __posix_spawn_asuser(pid_t *pidp, const char *path, const posix_spawn_file_actio /* use token generated from password auth if already present */ HANDLE user_token = password_auth_token; - if (!user_token && (user_token = get_user_token(user)) == NULL) { + if (!user_token && (user_token = get_user_token(user, 1)) == NULL) { error("unable to get security token for user %s", user); errno = EOTHER; return -1; diff --git a/contrib/win32/win32compat/win32_usertoken_utils.c b/contrib/win32/win32compat/win32_usertoken_utils.c index 683b55e49..b8f023ffd 100644 --- a/contrib/win32/win32compat/win32_usertoken_utils.c +++ b/contrib/win32/win32compat/win32_usertoken_utils.c @@ -1,6 +1,10 @@ /* * Author: Manoj Ampalam -* Utitilites to generate user tokens +* Utilities to generate user tokens +* +* Author: Bryan Berns +* Updated s4u, logon, and profile loading routines to use +* normalized login names. * * Copyright (c) 2015 Microsoft Corp. * All rights reserved @@ -28,7 +32,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - +#define SECURITY_WIN32 #define UMDF_USING_NTSTATUS #include #include @@ -36,6 +40,7 @@ #include #include #include +#include #include "inc\utf.h" #include "logonuser.h" @@ -87,142 +92,131 @@ done: return; } - -static HANDLE -LoadProfile(HANDLE user_token, wchar_t* user, wchar_t* domain) { - PROFILEINFOW profileInfo; - HANDLE ret = NULL; - - profileInfo.dwFlags = PI_NOUI; - profileInfo.lpProfilePath = NULL; - profileInfo.lpUserName = user; - profileInfo.lpDefaultPath = NULL; - profileInfo.lpServerName = domain; - profileInfo.lpPolicyPath = NULL; - profileInfo.hProfile = NULL; - profileInfo.dwSize = sizeof(profileInfo); - EnablePrivilege("SeBackupPrivilege", 1); - EnablePrivilege("SeRestorePrivilege", 1); - if (LoadUserProfileW(user_token, &profileInfo) == FALSE) { - debug("Loading user (%ls,%ls) profile failed ERROR: %d", user, domain, GetLastError()); - goto done; - } - else - ret = profileInfo.hProfile; -done: - EnablePrivilege("SeBackupPrivilege", 0); - EnablePrivilege("SeRestorePrivilege", 0); - return ret; -} - -#define MAX_USER_LEN 64 -/* https://technet.microsoft.com/en-us/library/active-directory-maximum-limits-scalability(v=ws.10).aspx */ -#define MAX_FQDN_LEN 64 -#define MAX_PW_LEN 64 - -static HANDLE -generate_s4u_user_token(wchar_t* user_cpn) { - HANDLE lsa_handle = 0, token = 0; - LSA_OPERATIONAL_MODE mode; +HANDLE +generate_s4u_user_token(wchar_t* user_cpn, int impersonation) { + HANDLE lsa_handle = NULL, token = NULL; ULONG auth_package_id; NTSTATUS ret, subStatus; void * logon_info = NULL; size_t logon_info_size; - LSA_STRING logon_process_name, auth_package_name, originName; - TOKEN_SOURCE sourceContext; - PKERB_INTERACTIVE_PROFILE pProfile = NULL; - LUID logonId; + LSA_STRING logon_process_name, auth_package_name, origin_name; + TOKEN_SOURCE source_context; + PKERB_INTERACTIVE_PROFILE profile = NULL; + LUID logon_id = { 0, 0 }; QUOTA_LIMITS quotas; - DWORD cbProfile; - BOOL domain_user; + DWORD profile_size; - domain_user = wcschr(user_cpn, L'@')? TRUE : FALSE; + /* the format for the user will be constrained to the output of get_passwd() + * so only the only two formats are a NetBiosDomain\SamAccountName which is + * a domain account or just SamAccountName in which is a local account */ + BOOL domain_user = wcschr(user_cpn, L'\\') != NULL; + + /* initialize connection to local security provider */ + if (impersonation) { - InitLsaString(&logon_process_name, "sshd"); - if (domain_user) - InitLsaString(&auth_package_name, MICROSOFT_KERBEROS_NAME_A); - else - InitLsaString(&auth_package_name, MSV1_0_PACKAGE_NAME); - - InitLsaString(&originName, "sshd"); - if (ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode) != STATUS_SUCCESS) - goto done; + /* trusted mode - used for impersonation */ + LSA_OPERATIONAL_MODE mode; + InitLsaString(&logon_process_name, "sshd"); + if ((ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode)) != STATUS_SUCCESS) + goto done; + } + else { + /* untrusted mode - used for information lookup */ + if (LsaConnectUntrusted(&lsa_handle) != STATUS_SUCCESS) + goto done; + } + InitLsaString(&auth_package_name, (domain_user) ? MICROSOFT_KERBEROS_NAME_A : MSV1_0_PACKAGE_NAME); if (ret = LsaLookupAuthenticationPackage(lsa_handle, &auth_package_name, &auth_package_id) != STATUS_SUCCESS) goto done; if (domain_user) { + + /* lookup the upn for the user */ + WCHAR domain_upn[MAX_UPN_LEN + 1]; + ULONG domain_upn_len = ARRAYSIZE(domain_upn); + if (TranslateNameW(user_cpn, NameSamCompatible, + NameUserPrincipal, domain_upn, &domain_upn_len) == 0) { + + /* upn lookup failed so resort to attempting samcompatiblename */ + debug3("%s: Unable to discover upn for user '%s': %d", + __FUNCTION__, user_cpn, GetLastError()); + wcscpy_s(domain_upn, ARRAYSIZE(domain_upn), user_cpn); + } + KERB_S4U_LOGON *s4u_logon; logon_info_size = sizeof(KERB_S4U_LOGON); - logon_info_size += (wcslen(user_cpn) * 2 + 2); - logon_info = malloc(logon_info_size); + + /* additional buffer is necessary at end to hold user name */ + logon_info_size += (wcslen(domain_upn) * sizeof(wchar_t)); + logon_info = calloc(1, logon_info_size); if (logon_info == NULL) goto done; s4u_logon = (KERB_S4U_LOGON*)logon_info; s4u_logon->MessageType = KerbS4ULogon; - s4u_logon->Flags = 0; - s4u_logon->ClientUpn.Length = (USHORT)wcslen(user_cpn) * 2; + s4u_logon->Flags = (impersonation) ? 0x0 : 0x8; + + /* copy the user name into the memory immediately after the structure */ + s4u_logon->ClientUpn.Length = (USHORT)wcslen(domain_upn) * sizeof(wchar_t); s4u_logon->ClientUpn.MaximumLength = s4u_logon->ClientUpn.Length; - s4u_logon->ClientUpn.Buffer = (WCHAR*)(s4u_logon + 1); - if (memcpy_s(s4u_logon->ClientUpn.Buffer, s4u_logon->ClientUpn.Length + 2, user_cpn, s4u_logon->ClientUpn.Length + 2)) + s4u_logon->ClientUpn.Buffer = (PWSTR)(s4u_logon + 1); + if (memcpy_s(s4u_logon->ClientUpn.Buffer, s4u_logon->ClientUpn.Length, + domain_upn, s4u_logon->ClientUpn.Length)) goto done; - s4u_logon->ClientRealm.Length = 0; - s4u_logon->ClientRealm.MaximumLength = 0; - s4u_logon->ClientRealm.Buffer = 0; - } else { + } + else { + MSV1_0_S4U_LOGON *s4u_logon; logon_info_size = sizeof(MSV1_0_S4U_LOGON); - /* additional buffer size = size of user_cpn + size of "." and their null terminators */ - logon_info_size += (wcslen(user_cpn) * 2 + 2) + 4; - logon_info = malloc(logon_info_size); + + /* additional buffer is necessary at end to hold user and computer name */ + logon_info_size += (wcslen(user_cpn) + wcslen(L".")) * sizeof(wchar_t); + logon_info = calloc(1, logon_info_size); if (logon_info == NULL) goto done; s4u_logon = (MSV1_0_S4U_LOGON*)logon_info; s4u_logon->MessageType = MsV1_0S4ULogon; - s4u_logon->Flags = 0; - s4u_logon->UserPrincipalName.Length = (USHORT)wcslen(user_cpn) * 2; + s4u_logon->Flags = 0x0; + + /* copy the user name into the memory immediately after the structure */ + s4u_logon->UserPrincipalName.Length = (USHORT)wcslen(user_cpn) * sizeof(wchar_t); s4u_logon->UserPrincipalName.MaximumLength = s4u_logon->UserPrincipalName.Length; s4u_logon->UserPrincipalName.Buffer = (WCHAR*)(s4u_logon + 1); - if(memcpy_s(s4u_logon->UserPrincipalName.Buffer, s4u_logon->UserPrincipalName.Length + 2, user_cpn, s4u_logon->UserPrincipalName.Length + 2)) + if (memcpy_s(s4u_logon->UserPrincipalName.Buffer, s4u_logon->UserPrincipalName.Length, + user_cpn, s4u_logon->UserPrincipalName.Length)) goto done; - s4u_logon->DomainName.Length = 2; - s4u_logon->DomainName.MaximumLength = 2; - s4u_logon->DomainName.Buffer = ((WCHAR*)s4u_logon->UserPrincipalName.Buffer) + wcslen(user_cpn) + 1; - if(memcpy_s(s4u_logon->DomainName.Buffer, 4, L".", 4)) + + /* copy the computer name immediately after the user name */ + s4u_logon->DomainName.Length = (USHORT)wcslen(L".") * sizeof(wchar_t); + s4u_logon->DomainName.MaximumLength = s4u_logon->DomainName.Length; + s4u_logon->DomainName.Buffer = (PWSTR)(((PBYTE)s4u_logon->UserPrincipalName.Buffer) + + s4u_logon->UserPrincipalName.Length); + if (memcpy_s(s4u_logon->DomainName.Buffer, s4u_logon->DomainName.Length, + L".", s4u_logon->DomainName.Length)) goto done; } - if(memcpy_s(sourceContext.SourceName, TOKEN_SOURCE_LENGTH, "sshd", sizeof(sourceContext.SourceName))) + if (strcpy_s(source_context.SourceName, TOKEN_SOURCE_LENGTH, "sshd") != 0 || + AllocateLocallyUniqueId(&source_context.SourceIdentifier) != TRUE) goto done; - if (AllocateLocallyUniqueId(&sourceContext.SourceIdentifier) != TRUE) - goto done; - - if (ret = LsaLogonUser(lsa_handle, - &originName, - Network, - auth_package_id, - logon_info, - (ULONG)logon_info_size, - NULL, - &sourceContext, - (PVOID*)&pProfile, - &cbProfile, - &logonId, - &token, - "as, - &subStatus) != STATUS_SUCCESS) { - debug("LsaLogonUser failed NTSTATUS: %d", ret); + InitLsaString(&origin_name, "sshd"); + if ((ret = LsaLogonUser(lsa_handle, &origin_name, Network, auth_package_id, + logon_info, (ULONG)logon_info_size, NULL, &source_context, + (PVOID*)&profile, &profile_size, &logon_id, &token, "as, &subStatus)) != STATUS_SUCCESS) { + debug("%s: LsaLogonUser() failed: %d SubStatus %d.", __FUNCTION__, ret, subStatus); goto done; } - debug3("LsaLogonUser succeeded"); + + debug3("LsaLogonUser Succeeded (Impersonation: %d)", impersonation); + done: if (lsa_handle) LsaDeregisterLogonProcess(lsa_handle); if (logon_info) free(logon_info); - if (pProfile) - LsaFreeReturnBuffer(pProfile); + if (profile) + LsaFreeReturnBuffer(profile); return token; } @@ -230,53 +224,45 @@ done: HANDLE process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg) { - wchar_t *providerw = NULL; HANDLE token = NULL, lsa_handle = NULL; LSA_OPERATIONAL_MODE mode; - ULONG auth_package_id, logon_info_size = 0; + ULONG auth_package_id; NTSTATUS ret, subStatus; - wchar_t *logon_info_w = NULL; - LSA_STRING logon_process_name, lsa_auth_package_name, originName; - TOKEN_SOURCE sourceContext; - PVOID pProfile = NULL; - LUID logonId; + LSA_STRING logon_process_name, lsa_auth_package_name, origin_name; + TOKEN_SOURCE source_context; + PVOID profile = NULL; + LUID logon_id = { 0, 0 }; QUOTA_LIMITS quotas; - DWORD cbProfile; + DWORD profile_size; int retVal = -1; - char *domain = NULL, *logon_info = NULL, user_name[UNLEN] = { 0, }, *tmp = NULL; + wchar_t *user_utf16 = NULL, *pwd_utf16 = NULL, *seperator = NULL; + wchar_t logon_info[UNLEN + 1 + PWLEN + 1 + DNLEN + 1]; + ULONG logon_info_size = ARRAYSIZE(logon_info); debug3("LSA auth request, user:%s lsa_pkg:%s ", user, lsa_pkg); - logon_info_size = (ULONG)(strlen(user) + strlen(pwd) + 2); // 1 - ";", 1 - "\0" - strcpy_s(user_name, _countof(user_name), user); - if (tmp = strstr(user_name, "@")) { - domain = tmp + 1; - *tmp = '\0'; - logon_info_size++; // 1 - ";" - } - - logon_info = malloc(logon_info_size); - if(!logon_info) - fatal("%s out of memory", __func__); - - strcpy_s(logon_info, logon_info_size, user_name); - strcat_s(logon_info, logon_info_size, ";"); - strcat_s(logon_info, logon_info_size, pwd); - - if (domain) { - strcat_s(logon_info, logon_info_size, ";"); - strcat_s(logon_info, logon_info_size, domain); - } - - if (NULL == (logon_info_w = utf8_to_utf16(logon_info))) { - error("utf8_to_utf16 failed to convert %s", logon_info); + if ((user_utf16 = utf8_to_utf16(user)) == NULL || + (pwd_utf16 = utf8_to_utf16(pwd)) == NULL) goto done; + + /* the format for the user will be constrained to the output of get_passwd() + * so only the only two formats are NetBiosDomain\SamAccountName which is + * a domain account or just SamAccountName in which is a local account */ + + seperator = wcschr(user_utf16, L'\\'); + if (seperator != NULL) { + /* domain user: generate login info string user;password;domain */ + swprintf_s(logon_info, ARRAYSIZE(logon_info), L"%s;%s;%.*s", + seperator + 1, pwd_utf16, (int) (seperator - user_utf16), user_utf16); + } else { + /* local user: generate login info string user;password */ + swprintf_s(logon_info, ARRAYSIZE(logon_info), L"%s;%s", + user_utf16, pwd_utf16); } - /* call into LSA provider , get and duplicate token */ InitLsaString(&logon_process_name, "sshd"); InitLsaString(&lsa_auth_package_name, lsa_pkg); - InitLsaString(&originName, "sshd"); + InitLsaString(&origin_name, "sshd"); if ((ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode)) != STATUS_SUCCESS) { error("LsaRegisterLogonProcess failed, error:%x", ret); @@ -288,32 +274,17 @@ process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg) goto done; } - memcpy(sourceContext.SourceName, "sshd", sizeof(sourceContext.SourceName)); + strcpy_s(source_context.SourceName, sizeof(source_context.SourceName), "sshd"); - if (!AllocateLocallyUniqueId(&sourceContext.SourceIdentifier)) { + if (!AllocateLocallyUniqueId(&source_context.SourceIdentifier)) { error("AllocateLocallyUniqueId failed, error:%d", GetLastError()); goto done; } - if ((ret = LsaLogonUser(lsa_handle, - &originName, - Network, - auth_package_id, - logon_info_w, - logon_info_size * sizeof(wchar_t), - NULL, - &sourceContext, - &pProfile, - &cbProfile, - &logonId, - &token, - "as, - &subStatus)) != STATUS_SUCCESS) { - if (ret == STATUS_ACCOUNT_RESTRICTION) - error("LsaLogonUser failed, error:%x subStatus:%ld", ret, subStatus); - else - error("LsaLogonUser failed error:%x", ret); - + if ((ret = LsaLogonUser(lsa_handle, &origin_name, Network, auth_package_id, + logon_info, (ULONG)logon_info_size, NULL, &source_context, + (PVOID*)&profile, &profile_size, &logon_id, &token, "as, &subStatus)) != STATUS_SUCCESS) { + debug("%s: LsaLogonUser() failed: %d SubStatus %d.", __FUNCTION__, ret, subStatus); goto done; } @@ -322,12 +293,15 @@ process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg) done: if (lsa_handle) LsaDeregisterLogonProcess(lsa_handle); - if (pProfile) - LsaFreeReturnBuffer(pProfile); - if (logon_info) - free(logon_info); - if (logon_info_w) - free(logon_info_w); + if (profile) + LsaFreeReturnBuffer(profile); + if (user_utf16) + free(user_utf16); + if (pwd_utf16) { + SecureZeroMemory(pwd_utf16, wcslen(pwd_utf16) * sizeof(WCHAR)); + free(pwd_utf16); + } + SecureZeroMemory(logon_info, sizeof(logon_info)); return token; } @@ -335,10 +309,10 @@ done: HANDLE generate_sshd_virtual_token(); HANDLE -get_user_token(char* user) { +get_user_token(char* user, int impersonation) { HANDLE token = NULL; wchar_t *user_utf16 = NULL; - + if ((user_utf16 = utf8_to_utf16(user)) == NULL) { debug("out of memory"); goto done; @@ -350,12 +324,12 @@ get_user_token(char* user) { debug3("unable to generate sshd virtual token, falling back to s4u"); } - if ((token = generate_s4u_user_token(user_utf16)) == 0) { + if ((token = generate_s4u_user_token(user_utf16, impersonation)) == 0) { error("unable to generate token for user %ls", user_utf16); /* work around for https://github.com/PowerShell/Win32-OpenSSH/issues/727 by doing a fake login */ LogonUserExExWHelper(L"FakeUser", L"FakeDomain", L"FakePasswd", LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, NULL, &token, NULL, NULL, NULL, NULL); - if ((token = generate_s4u_user_token(user_utf16)) == 0) { + if ((token = generate_s4u_user_token(user_utf16, impersonation)) == 0) { error("unable to generate token on 2nd attempt for user %ls", user_utf16); goto done; } @@ -368,29 +342,46 @@ done: return token; } -int load_user_profile(HANDLE user_token, char* user) { - int r = 0; - HANDLE profile_handle = NULL; - wchar_t *user_utf16 = NULL, *dom_utf16 = NULL, *tmp; - +int +load_user_profile(HANDLE user_token, char* user) +{ + wchar_t * user_utf16 = NULL; if ((user_utf16 = utf8_to_utf16(user)) == NULL) { - debug("out of memory"); - goto done; + fatal("out of memory"); + return -1; } - /* split user and domain */ - if ((tmp = wcschr(user_utf16, L'@')) != NULL) { - dom_utf16 = tmp + 1; - *tmp = L'\0'; + /* note: user string will normalized form output of get_passwd() */ + wchar_t * user_name = user_utf16; + wchar_t * domain_name = NULL; + wchar_t * seperator = wcschr(user_name, L'\\'); + if (seperator != NULL) { + domain_name = user_name; + *seperator = L'\0'; + user_name = seperator + 1; } - if ((profile_handle = LoadProfile(user_token, user_utf16, dom_utf16)) == NULL) - goto done; + PROFILEINFOW profileInfo = { 0 }; + profileInfo.dwSize = sizeof(profileInfo); + profileInfo.dwFlags = PI_NOUI; + profileInfo.lpProfilePath = NULL; + profileInfo.lpUserName = user_name; + profileInfo.lpDefaultPath = NULL; + profileInfo.lpServerName = domain_name; + profileInfo.lpPolicyPath = NULL; + profileInfo.hProfile = NULL; + EnablePrivilege("SeBackupPrivilege", 1); + EnablePrivilege("SeRestorePrivilege", 1); + if (LoadUserProfileW(user_token, &profileInfo) == FALSE) { + debug3("%s: LoadUserProfileW() failed for user %S with error %d.", __FUNCTION__, GetLastError()); + } + EnablePrivilege("SeBackupPrivilege", 0); + EnablePrivilege("SeRestorePrivilege", 0); -done: if (user_utf16) free(user_utf16); - return r; + + return 0; }