Enhanced Group Discovery / Normalized Names (#286)

Modified getusergroups() to use s4u tokens to discover nested groups and return them in NetBiosName\GroupName format.
Modified get_passwd() to internally normalize names to NetBiosName\SamAccountName format and changed functions that use it to translate to UPN where necessary.
Removed unnecessary support functions used by previous version of getusergroups().
Various refactoring and function consolidation / simplification.
Addressed several buffer over-read issues.

PowerShell/Win32-OpenSSH#553
This commit is contained in:
Bryan Berns 2018-05-02 02:20:42 -04:00 committed by Manoj Ampalam
parent 77999d2f4d
commit fe422e5c15
10 changed files with 475 additions and 595 deletions

View File

@ -57,7 +57,10 @@
#include "authfd.h" #include "authfd.h"
#ifdef WINDOWS #ifdef WINDOWS
#define SECURITY_WIN32
#include <security.h>
#include "logonuser.h" #include "logonuser.h"
#include "misc_internal.h"
#include "monitor_wrap.h" #include "monitor_wrap.h"
#endif #endif
@ -271,23 +274,48 @@ done:
int int
sys_auth_passwd(struct ssh *ssh, const char *password) 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; Authctxt *authctxt = ssh->authctxt;
wchar_t *user_utf16 = NULL, *udom_utf16 = NULL, *pwd_utf16 = NULL, *tmp;
HANDLE token = NULL; 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 || user_utf16 = utf8_to_utf16(authctxt->pw->pw_name);
(pwd_utf16 = utf8_to_utf16(password)) == NULL) { pwd_utf16 = utf8_to_utf16(password);
fatal("out of memory"); if (user_utf16 == NULL || pwd_utf16 == NULL) {
debug("out of memory");
goto done; 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) { /* translate to domain user if format contains a backslash */
udom_utf16 = tmp + 1; wchar_t * backslash = wcschr(user_utf16, L'\\');
*tmp = L'\0'; 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) LOGON32_PROVIDER_DEFAULT, NULL, &token, NULL, NULL, NULL, NULL) == TRUE)
password_auth_token = token; password_auth_token = token;
else { else {
@ -304,17 +332,14 @@ sys_auth_passwd(struct ssh *ssh, const char *password)
sys_auth_passwd_lsa(authctxt, password); sys_auth_passwd_lsa(authctxt, password);
} }
} }
done: done:
if (password_auth_token)
r = 1;
if (user_utf16) if (user_utf16)
free(user_utf16); free(user_utf16);
if (pwd_utf16) if (pwd_utf16)
SecureZeroMemory(pwd_utf16, sizeof(wchar_t) * wcslen(pwd_utf16)); SecureZeroMemory(pwd_utf16, sizeof(wchar_t) * wcslen(pwd_utf16));
return r; return (password_auth_token) ? 1 : 0;
} }
#endif /* WINDOWS */ #endif /* WINDOWS */

View File

@ -462,8 +462,6 @@
<ClCompile Include="$(OpenSSH-Src-Path)sshlogin.c" /> <ClCompile Include="$(OpenSSH-Src-Path)sshlogin.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32_sshpty.c" /> <ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32_sshpty.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_sshd.c" /> <ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\wmain_sshd.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\logonuser.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\win32_usertoken_utils.c" />
<ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\spawn-ext.c" /> <ClCompile Include="$(OpenSSH-Src-Path)contrib\win32\win32compat\spawn-ext.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -290,6 +290,8 @@
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\misc.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\misc.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\signal_sigalrm.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\signal_sigalrm.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\signal_sigchld.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\signal_sigchld.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\logonuser.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32_usertoken_utils.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\w32log.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\w32log.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\pwd.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\pwd.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32_dirent.c" /> <ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32_dirent.c" />

View File

@ -3,14 +3,6 @@
#include <Windows.h> #include <Windows.h>
#include "sys/types.h" #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); 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 #endif

View File

@ -38,7 +38,6 @@ char *user_from_uid(uid_t uid, int nouser);
struct passwd *w32_getpwuid(uid_t uid); struct passwd *w32_getpwuid(uid_t uid);
struct passwd *w32_getpwnam(const char *username); struct passwd *w32_getpwnam(const char *username);
struct passwd* w32_getpwtoken(HANDLE);
struct passwd *getpwent(void); struct passwd *getpwent(void);
void endpwent(void); void endpwent(void);

View File

@ -1,6 +1,9 @@
/* /*
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com> * Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
* *
* Author: Bryan Berns <berns@uwalumni.com>
* Modified group detection use s4u token information
*
* Copyright(c) 2016 Microsoft Corp. * Copyright(c) 2016 Microsoft Corp.
* All rights reserved * All rights reserved
* *
@ -28,6 +31,8 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#define UMDF_USING_NTSTATUS
#define SECURITY_WIN32
#include <Windows.h> #include <Windows.h>
#include <stdio.h> #include <stdio.h>
#include <time.h> #include <time.h>
@ -36,6 +41,9 @@
#include <LM.h> #include <LM.h>
#include <Sddl.h> #include <Sddl.h>
#include <Aclapi.h> #include <Aclapi.h>
#include <Ntsecapi.h>
#include <security.h>
#include <ntstatus.h>
#include "inc\unistd.h" #include "inc\unistd.h"
#include "inc\sys\stat.h" #include "inc\sys\stat.h"
@ -49,6 +57,7 @@
#include "inc\fcntl.h" #include "inc\fcntl.h"
#include "inc\utf.h" #include "inc\utf.h"
#include "signal_internal.h" #include "signal_internal.h"
#include "misc_internal.h"
#include "debug.h" #include "debug.h"
#include "w32fd.h" #include "w32fd.h"
#include "inc\string.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); 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. * This method will fetch all the groups (listed below) even if the user is indirectly a member.
* - Local machine groups * - Local machine groups
@ -1117,216 +1104,164 @@ get_machine_domain_name(wchar_t *domain, int size)
*/ */
char ** char **
getusergroups(const char *user, int *ngroups) 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; /* initialize return values */
char *groupname_with_domain = malloc(len); errno = 0;
if(!groupname_with_domain) { *ngroups = 0;
error("failed to allocate memory!"); 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; return NULL;
} }
strcpy_s(groupname_with_domain, len, groupname); /* downsize the array to the actual size and return */
strcat_s(groupname_with_domain, len, "@"); return (char**)realloc(user_groups, sizeof(char*) * (*ngroups));
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]);
} }
void void

View File

@ -22,6 +22,9 @@
#define errno_from_Win32LastError() errno_from_Win32Error(GetLastError()) #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; static char *machine_domain_name;
/* removes first '/' for Windows paths that are unix styled. Ex: /c:/ab.cd */ /* 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); 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 invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t);
void to_lower_case(char *s); void to_lower_case(char *s);
int get_machine_domain_name(wchar_t *domain, int size);
wchar_t* get_program_data_path(); 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 load_user_profile(HANDLE user_token, char* user);
int create_directory_withsddl(wchar_t *path, wchar_t *sddl); int create_directory_withsddl(wchar_t *path, wchar_t *sddl);
int is_absolute_path(const char *); int is_absolute_path(const char *);

View File

@ -1,6 +1,10 @@
/* /*
* Author: NoMachine <developers@nomachine.com> * Author: NoMachine <developers@nomachine.com>
* *
* Author: Bryan Berns <berns@uwalumni.com>
* Normalized and optimized login routines and added support for
* internet-linked accounts.
*
* Copyright (c) 2009, 2011 NoMachine * Copyright (c) 2009, 2011 NoMachine
* All rights reserved * All rights reserved
* *
@ -99,213 +103,145 @@ reset_pw()
} }
static struct passwd* 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; struct passwd *ret = NULL;
wchar_t *user_utf16 = NULL, *uname_utf16, *udom_utf16, *tmp; wchar_t *sid_string = NULL;
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 reg_path[PATH_MAX], profile_home[PATH_MAX], profile_home_exp[PATH_MAX]; wchar_t reg_path[PATH_MAX], profile_home[PATH_MAX], profile_home_exp[PATH_MAX];
HKEY reg_key = 0; DWORD reg_path_len = PATH_MAX;
int tmp_len = PATH_MAX; HKEY reg_key = 0;
PDOMAIN_CONTROLLER_INFOW pdc = NULL;
DWORD dsStatus, uname_upn_len = 0, uname_len = 0, udom_len = 0; BYTE binary_sid[SECURITY_MAX_SID_SIZE];
wchar_t wmachine_name[MAX_COMPUTERNAME_LENGTH + 1]; DWORD sid_size = ARRAYSIZE(binary_sid);
DWORD wmachine_name_len = MAX_COMPUTERNAME_LENGTH + 1; WCHAR domain_name[DNLEN + 1] = L"";
errno_t r = 0; DWORD domain_name_size = DNLEN + 1;
SID_NAME_USE account_type = 0;
errno = 0; errno = 0;
reset_pw(); reset_pw();
if ((user_utf16 = utf8_to_utf16(user_utf8)) == NULL) {
errno = ENOMEM; /* skip forward lookup on name if sid was passed in */
goto done; 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*/ /* convert the binary string to a string */
if ((tmp = wcschr(user_utf16, L'\\')) != NULL) { if (ConvertSidToStringSidW((PSID) binary_sid, &sid_string) == FALSE) {
udom_utf16 = user_utf16; errno = ENOENT;
uname_utf16 = tmp + 1; goto cleanup;
*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;
} }
if (udom_utf16) { /* lookup the account name from the sid */
/* this should never fail */ WCHAR user_name[UNLEN + 1];
GetComputerNameW(wmachine_name, &wmachine_name_len); DWORD user_name_length = ARRAYSIZE(user_name);
/* If this is a local account (domain part and computer name are the same), strip out domain */ domain_name_size = DNLEN + 1;
if (_wcsicmp(udom_utf16, wmachine_name) == 0) if (LookupAccountSidW(NULL, binary_sid, user_name, &user_name_length,
udom_utf16 = NULL; domain_name, &domain_name_size, &account_type) == 0) {
errno = ENOENT;
debug("%s: LookupAccountSid() failed: %d.", __FUNCTION__, GetLastError());
goto cleanup;
} }
if (user_sid == NULL) { /* verify passed account is actually a user account */
NET_API_STATUS status; if (account_type != SidTypeUser) {
if ((status = NetUserGetInfo(udom_utf16, uname_utf16, 23, &user_info)) != NERR_Success) { errno = ENOENT;
debug3("NetUserGetInfo() failed with error: %d for user: %ls and domain: %ls \n", status, uname_utf16, udom_utf16); debug3("%s: Invalid account type: %d.", __FUNCTION__, account_type);
goto cleanup;
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;
} }
/* 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 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, &reg_key) != 0 || RegOpenKeyExW(HKEY_LOCAL_MACHINE, reg_path, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_WOW64_64KEY, &reg_key) != 0 ||
RegQueryValueExW(reg_key, L"ProfileImagePath", 0, NULL, (LPBYTE)profile_home, &tmp_len) != 0 || RegQueryValueExW(reg_key, L"ProfileImagePath", 0, NULL, (LPBYTE)profile_home, &reg_path_len) != 0 ||
ExpandEnvironmentStringsW(profile_home, NULL, 0) > PATH_MAX || ExpandEnvironmentStringsW(profile_home, NULL, 0) > PATH_MAX ||
ExpandEnvironmentStringsW(profile_home, profile_home_exp, PATH_MAX) == 0) ExpandEnvironmentStringsW(profile_home, profile_home_exp, PATH_MAX) == 0)
if (GetWindowsDirectoryW(profile_home_exp, PATH_MAX) == 0) { if (GetWindowsDirectoryW(profile_home_exp, PATH_MAX) == 0) {
debug3("GetWindowsDirectoryW failed with %d", GetLastError()); debug3("GetWindowsDirectoryW failed with %d", GetLastError());
errno = EOTHER; errno = EOTHER;
goto done; goto cleanup;
} }
if ((uname_utf8 = utf16_to_utf8(uname_utf16)) == NULL || /* convert to utf8, make name lowercase, and assign to output structure*/
(udom_utf16 && (udom_utf8 = utf16_to_utf8(udom_utf16)) == NULL) || _wcslwr_s(user_resolved, wcslen(user_resolved) + 1);
(pw_home_utf8 = utf16_to_utf8(profile_home_exp)) == NULL || if ((pw.pw_name = utf16_to_utf8(user_resolved)) == NULL ||
(user_sid_utf8 = utf16_to_utf8(user_sid)) == NULL) { (pw.pw_dir = utf16_to_utf8(profile_home_exp)) == NULL ||
(pw.pw_sid = utf16_to_utf8(sid_string)) == NULL) {
reset_pw();
errno = ENOMEM; errno = ENOMEM;
goto done; goto cleanup;
}
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;
} }
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; ret = &pw;
done: cleanup:
if (user_utf16)
free(user_utf16); if (sid_string)
if (uname_utf8) LocalFree(sid_string);
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);
if (reg_key) if (reg_key)
RegCloseKey(reg_key); RegCloseKey(reg_key);
if (pdc)
NetApiBufferFree(pdc);
return ret; return ret;
} }
struct passwd* struct passwd*
w32_getpwnam(const char *user_utf8) w32_getpwnam(const char *user_utf8)
{ {
return get_passwd(user_utf8, NULL); wchar_t * user_utf16 = utf8_to_utf16(user_utf8);
} if (user_utf16 == 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) {
errno = ENOMEM; errno = ENOMEM;
goto done; return NULL;
} }
ret = get_passwd(user_utf8, user_sid);
done: return get_passwd(user_utf16, NULL);
if (wuser)
free(wuser);
if (user_utf8)
free(user_utf8);
if (info)
free(info);
if (user_sid)
LocalFree(user_sid);
return ret;
} }
struct passwd* struct passwd*
w32_getpwuid(uid_t uid) w32_getpwuid(uid_t uid)
{ {
HANDLE token; struct passwd* ret = NULL;
struct passwd* ret; HANDLE token = NULL;
if ((OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) == FALSE) { TOKEN_USER* info = NULL;
debug("unable to get process token"); DWORD info_len = 0;
errno = EOTHER;
return NULL; 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; return ret;
} }

View File

@ -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 */ /* use token generated from password auth if already present */
HANDLE user_token = password_auth_token; 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); error("unable to get security token for user %s", user);
errno = EOTHER; errno = EOTHER;
return -1; return -1;

View File

@ -1,6 +1,10 @@
/* /*
* Author: Manoj Ampalam <manoj.ampalam@microsoft.com> * Author: Manoj Ampalam <manoj.ampalam@microsoft.com>
* Utitilites to generate user tokens * Utilities to generate user tokens
*
* Author: Bryan Berns <berns@uwalumni.com>
* Updated s4u, logon, and profile loading routines to use
* normalized login names.
* *
* Copyright (c) 2015 Microsoft Corp. * Copyright (c) 2015 Microsoft Corp.
* All rights reserved * All rights reserved
@ -28,7 +32,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
#define SECURITY_WIN32
#define UMDF_USING_NTSTATUS #define UMDF_USING_NTSTATUS
#include <Windows.h> #include <Windows.h>
#include <UserEnv.h> #include <UserEnv.h>
@ -36,6 +40,7 @@
#include <ntstatus.h> #include <ntstatus.h>
#include <Shlobj.h> #include <Shlobj.h>
#include <LM.h> #include <LM.h>
#include <security.h>
#include "inc\utf.h" #include "inc\utf.h"
#include "logonuser.h" #include "logonuser.h"
@ -87,142 +92,131 @@ done:
return; return;
} }
HANDLE
static HANDLE generate_s4u_user_token(wchar_t* user_cpn, int impersonation) {
LoadProfile(HANDLE user_token, wchar_t* user, wchar_t* domain) { HANDLE lsa_handle = NULL, token = NULL;
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;
ULONG auth_package_id; ULONG auth_package_id;
NTSTATUS ret, subStatus; NTSTATUS ret, subStatus;
void * logon_info = NULL; void * logon_info = NULL;
size_t logon_info_size; size_t logon_info_size;
LSA_STRING logon_process_name, auth_package_name, originName; LSA_STRING logon_process_name, auth_package_name, origin_name;
TOKEN_SOURCE sourceContext; TOKEN_SOURCE source_context;
PKERB_INTERACTIVE_PROFILE pProfile = NULL; PKERB_INTERACTIVE_PROFILE profile = NULL;
LUID logonId; LUID logon_id = { 0, 0 };
QUOTA_LIMITS quotas; QUOTA_LIMITS quotas;
DWORD cbProfile; DWORD profile_size;
BOOL domain_user;
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"); /* trusted mode - used for impersonation */
if (domain_user) LSA_OPERATIONAL_MODE mode;
InitLsaString(&auth_package_name, MICROSOFT_KERBEROS_NAME_A); InitLsaString(&logon_process_name, "sshd");
else if ((ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode)) != STATUS_SUCCESS)
InitLsaString(&auth_package_name, MSV1_0_PACKAGE_NAME); goto done;
}
InitLsaString(&originName, "sshd"); else {
if (ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode) != STATUS_SUCCESS) /* untrusted mode - used for information lookup */
goto done; 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) if (ret = LsaLookupAuthenticationPackage(lsa_handle, &auth_package_name, &auth_package_id) != STATUS_SUCCESS)
goto done; goto done;
if (domain_user) { 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; KERB_S4U_LOGON *s4u_logon;
logon_info_size = sizeof(KERB_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) if (logon_info == NULL)
goto done; goto done;
s4u_logon = (KERB_S4U_LOGON*)logon_info; s4u_logon = (KERB_S4U_LOGON*)logon_info;
s4u_logon->MessageType = KerbS4ULogon; s4u_logon->MessageType = KerbS4ULogon;
s4u_logon->Flags = 0; s4u_logon->Flags = (impersonation) ? 0x0 : 0x8;
s4u_logon->ClientUpn.Length = (USHORT)wcslen(user_cpn) * 2;
/* 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.MaximumLength = s4u_logon->ClientUpn.Length;
s4u_logon->ClientUpn.Buffer = (WCHAR*)(s4u_logon + 1); s4u_logon->ClientUpn.Buffer = (PWSTR)(s4u_logon + 1);
if (memcpy_s(s4u_logon->ClientUpn.Buffer, s4u_logon->ClientUpn.Length + 2, user_cpn, s4u_logon->ClientUpn.Length + 2)) if (memcpy_s(s4u_logon->ClientUpn.Buffer, s4u_logon->ClientUpn.Length,
domain_upn, s4u_logon->ClientUpn.Length))
goto done; goto done;
s4u_logon->ClientRealm.Length = 0; }
s4u_logon->ClientRealm.MaximumLength = 0; else {
s4u_logon->ClientRealm.Buffer = 0;
} else {
MSV1_0_S4U_LOGON *s4u_logon; MSV1_0_S4U_LOGON *s4u_logon;
logon_info_size = sizeof(MSV1_0_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; /* additional buffer is necessary at end to hold user and computer name */
logon_info = malloc(logon_info_size); logon_info_size += (wcslen(user_cpn) + wcslen(L".")) * sizeof(wchar_t);
logon_info = calloc(1, logon_info_size);
if (logon_info == NULL) if (logon_info == NULL)
goto done; goto done;
s4u_logon = (MSV1_0_S4U_LOGON*)logon_info; s4u_logon = (MSV1_0_S4U_LOGON*)logon_info;
s4u_logon->MessageType = MsV1_0S4ULogon; s4u_logon->MessageType = MsV1_0S4ULogon;
s4u_logon->Flags = 0; s4u_logon->Flags = 0x0;
s4u_logon->UserPrincipalName.Length = (USHORT)wcslen(user_cpn) * 2;
/* 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.MaximumLength = s4u_logon->UserPrincipalName.Length;
s4u_logon->UserPrincipalName.Buffer = (WCHAR*)(s4u_logon + 1); 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; goto done;
s4u_logon->DomainName.Length = 2;
s4u_logon->DomainName.MaximumLength = 2; /* copy the computer name immediately after the user name */
s4u_logon->DomainName.Buffer = ((WCHAR*)s4u_logon->UserPrincipalName.Buffer) + wcslen(user_cpn) + 1; s4u_logon->DomainName.Length = (USHORT)wcslen(L".") * sizeof(wchar_t);
if(memcpy_s(s4u_logon->DomainName.Buffer, 4, L".", 4)) 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; 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; goto done;
if (AllocateLocallyUniqueId(&sourceContext.SourceIdentifier) != TRUE) InitLsaString(&origin_name, "sshd");
goto done; if ((ret = LsaLogonUser(lsa_handle, &origin_name, Network, auth_package_id,
logon_info, (ULONG)logon_info_size, NULL, &source_context,
if (ret = LsaLogonUser(lsa_handle, (PVOID*)&profile, &profile_size, &logon_id, &token, &quotas, &subStatus)) != STATUS_SUCCESS) {
&originName, debug("%s: LsaLogonUser() failed: %d SubStatus %d.", __FUNCTION__, ret, subStatus);
Network,
auth_package_id,
logon_info,
(ULONG)logon_info_size,
NULL,
&sourceContext,
(PVOID*)&pProfile,
&cbProfile,
&logonId,
&token,
&quotas,
&subStatus) != STATUS_SUCCESS) {
debug("LsaLogonUser failed NTSTATUS: %d", ret);
goto done; goto done;
} }
debug3("LsaLogonUser succeeded");
debug3("LsaLogonUser Succeeded (Impersonation: %d)", impersonation);
done: done:
if (lsa_handle) if (lsa_handle)
LsaDeregisterLogonProcess(lsa_handle); LsaDeregisterLogonProcess(lsa_handle);
if (logon_info) if (logon_info)
free(logon_info); free(logon_info);
if (pProfile) if (profile)
LsaFreeReturnBuffer(pProfile); LsaFreeReturnBuffer(profile);
return token; return token;
} }
@ -230,53 +224,45 @@ done:
HANDLE HANDLE
process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg) process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg)
{ {
wchar_t *providerw = NULL;
HANDLE token = NULL, lsa_handle = NULL; HANDLE token = NULL, lsa_handle = NULL;
LSA_OPERATIONAL_MODE mode; LSA_OPERATIONAL_MODE mode;
ULONG auth_package_id, logon_info_size = 0; ULONG auth_package_id;
NTSTATUS ret, subStatus; NTSTATUS ret, subStatus;
wchar_t *logon_info_w = NULL; LSA_STRING logon_process_name, lsa_auth_package_name, origin_name;
LSA_STRING logon_process_name, lsa_auth_package_name, originName; TOKEN_SOURCE source_context;
TOKEN_SOURCE sourceContext; PVOID profile = NULL;
PVOID pProfile = NULL; LUID logon_id = { 0, 0 };
LUID logonId;
QUOTA_LIMITS quotas; QUOTA_LIMITS quotas;
DWORD cbProfile; DWORD profile_size;
int retVal = -1; 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); debug3("LSA auth request, user:%s lsa_pkg:%s ", user, lsa_pkg);
logon_info_size = (ULONG)(strlen(user) + strlen(pwd) + 2); // 1 - ";", 1 - "\0" if ((user_utf16 = utf8_to_utf16(user)) == NULL ||
strcpy_s(user_name, _countof(user_name), user); (pwd_utf16 = utf8_to_utf16(pwd)) == NULL)
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);
goto done; 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(&logon_process_name, "sshd");
InitLsaString(&lsa_auth_package_name, lsa_pkg); 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) { if ((ret = LsaRegisterLogonProcess(&logon_process_name, &lsa_handle, &mode)) != STATUS_SUCCESS) {
error("LsaRegisterLogonProcess failed, error:%x", ret); 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; 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()); error("AllocateLocallyUniqueId failed, error:%d", GetLastError());
goto done; goto done;
} }
if ((ret = LsaLogonUser(lsa_handle, if ((ret = LsaLogonUser(lsa_handle, &origin_name, Network, auth_package_id,
&originName, logon_info, (ULONG)logon_info_size, NULL, &source_context,
Network, (PVOID*)&profile, &profile_size, &logon_id, &token, &quotas, &subStatus)) != STATUS_SUCCESS) {
auth_package_id, debug("%s: LsaLogonUser() failed: %d SubStatus %d.", __FUNCTION__, ret, subStatus);
logon_info_w,
logon_info_size * sizeof(wchar_t),
NULL,
&sourceContext,
&pProfile,
&cbProfile,
&logonId,
&token,
&quotas,
&subStatus)) != STATUS_SUCCESS) {
if (ret == STATUS_ACCOUNT_RESTRICTION)
error("LsaLogonUser failed, error:%x subStatus:%ld", ret, subStatus);
else
error("LsaLogonUser failed error:%x", ret);
goto done; goto done;
} }
@ -322,12 +293,15 @@ process_custom_lsa_auth(const char* user, const char* pwd, const char* lsa_pkg)
done: done:
if (lsa_handle) if (lsa_handle)
LsaDeregisterLogonProcess(lsa_handle); LsaDeregisterLogonProcess(lsa_handle);
if (pProfile) if (profile)
LsaFreeReturnBuffer(pProfile); LsaFreeReturnBuffer(profile);
if (logon_info) if (user_utf16)
free(logon_info); free(user_utf16);
if (logon_info_w) if (pwd_utf16) {
free(logon_info_w); SecureZeroMemory(pwd_utf16, wcslen(pwd_utf16) * sizeof(WCHAR));
free(pwd_utf16);
}
SecureZeroMemory(logon_info, sizeof(logon_info));
return token; return token;
} }
@ -335,10 +309,10 @@ done:
HANDLE generate_sshd_virtual_token(); HANDLE generate_sshd_virtual_token();
HANDLE HANDLE
get_user_token(char* user) { get_user_token(char* user, int impersonation) {
HANDLE token = NULL; HANDLE token = NULL;
wchar_t *user_utf16 = NULL; wchar_t *user_utf16 = NULL;
if ((user_utf16 = utf8_to_utf16(user)) == NULL) { if ((user_utf16 = utf8_to_utf16(user)) == NULL) {
debug("out of memory"); debug("out of memory");
goto done; goto done;
@ -350,12 +324,12 @@ get_user_token(char* user) {
debug3("unable to generate sshd virtual token, falling back to s4u"); 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); 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 */ /* work around for https://github.com/PowerShell/Win32-OpenSSH/issues/727 by doing a fake login */
LogonUserExExWHelper(L"FakeUser", L"FakeDomain", L"FakePasswd", LogonUserExExWHelper(L"FakeUser", L"FakeDomain", L"FakePasswd",
LOGON32_LOGON_NETWORK_CLEARTEXT, LOGON32_PROVIDER_DEFAULT, NULL, &token, NULL, NULL, NULL, NULL); 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); error("unable to generate token on 2nd attempt for user %ls", user_utf16);
goto done; goto done;
} }
@ -368,29 +342,46 @@ done:
return token; return token;
} }
int load_user_profile(HANDLE user_token, char* user) { int
int r = 0; load_user_profile(HANDLE user_token, char* user)
HANDLE profile_handle = NULL; {
wchar_t *user_utf16 = NULL, *dom_utf16 = NULL, *tmp; wchar_t * user_utf16 = NULL;
if ((user_utf16 = utf8_to_utf16(user)) == NULL) { if ((user_utf16 = utf8_to_utf16(user)) == NULL) {
debug("out of memory"); fatal("out of memory");
goto done; return -1;
} }
/* split user and domain */ /* note: user string will normalized form output of get_passwd() */
if ((tmp = wcschr(user_utf16, L'@')) != NULL) { wchar_t * user_name = user_utf16;
dom_utf16 = tmp + 1; wchar_t * domain_name = NULL;
*tmp = L'\0'; 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) PROFILEINFOW profileInfo = { 0 };
goto done; 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) if (user_utf16)
free(user_utf16); free(user_utf16);
return r;
return 0;
} }