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"
#ifdef WINDOWS
#define SECURITY_WIN32
#include <security.h>
#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 */

View File

@ -462,8 +462,6 @@
<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\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" />
</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\signal_sigalrm.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\pwd.c" />
<ClCompile Include="$(OpenSSH-Src-Path)\contrib\win32\win32compat\win32_dirent.c" />

View File

@ -3,14 +3,6 @@
#include <Windows.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);
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

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_getpwnam(const char *username);
struct passwd* w32_getpwtoken(HANDLE);
struct passwd *getpwent(void);
void endpwent(void);

View File

@ -1,6 +1,9 @@
/*
* 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.
* 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 <Windows.h>
#include <stdio.h>
#include <time.h>
@ -36,6 +41,9 @@
#include <LM.h>
#include <Sddl.h>
#include <Aclapi.h>
#include <Ntsecapi.h>
#include <security.h>
#include <ntstatus.h>
#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

View File

@ -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 *);

View File

@ -1,6 +1,10 @@
/*
* 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
* 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, &reg_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, &reg_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;
}

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 */
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;

View File

@ -1,6 +1,10 @@
/*
* 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.
* 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 <Windows.h>
#include <UserEnv.h>
@ -36,6 +40,7 @@
#include <ntstatus.h>
#include <Shlobj.h>
#include <LM.h>
#include <security.h>
#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,
&quotas,
&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, &quotas, &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,
&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);
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, &quotas, &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;
}