#include "pch.h"
#include "System/Security/Principal/UserPrincipal.h"
#include "System/ArgumentException.h"
#include "System/UnauthorizedAccessException.h"

#include <vector>
#include <string>

#if defined(_WIN32)
#include <windows.h>
#include <lm.h>
#include <sddl.h>
#include "Win32Internal.h"
#include "System/ComponentModel/Win32Exception.h"
#pragma comment(lib, "netapi32.lib")
#pragma comment(lib, "advapi32.lib")
using namespace DotNetDupe::System::Internal;
#else
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <unistd.h>
#include <cerrno>
#include "System/SystemException.h"
#endif

namespace DotNetDupe {
    namespace System {
        namespace Security {
            namespace Principal {

                UserPrincipal::UserPrincipal() {
                }

                UserPrincipal::~UserPrincipal() {
                }

#if defined(_WIN32)
                static UserClass ClassifyWindowsUser(DWORD dwPriv, DWORD dwFlags) {
                    if (dwFlags & UF_ACCOUNTDISABLE) return UserClass::Guest;
                    if (dwPriv == USER_PRIV_ADMIN) return UserClass::Admin;
                    if (dwPriv == USER_PRIV_GUEST) return UserClass::Guest;
                    return UserClass::Normal;
                }

                static void PopulateWin32UserGroups(LPCWSTR pwszUser, Collections::Generic::List<String>& lstGroups, Collections::Generic::List<String>& lstPermissions) {
                    LPGROUP_USERS_INFO_0 pGroups = NULL;
                    DWORD dwEntriesRead = 0, dwTotalEntries = 0;

                    NET_API_STATUS nStatus = ::NetUserGetGroups(NULL, pwszUser, 0, (LPBYTE*)&pGroups, MAX_PREFERRED_LENGTH, &dwEntriesRead, &dwTotalEntries);
                    if (nStatus == ERROR_ACCESS_DENIED) {
                        throw UnauthorizedAccessException("Access denied querying user groups. Administrator privileges required.");
                    }

                    if (nStatus == NERR_Success) {
                        for (DWORD i = 0; i < dwEntriesRead; i++) {
                            std::string sGroup = StringConvertInternal::WCharToUtf8(pGroups[i].grui0_name);
                            lstPermissions.Add(String("GroupMember:") + sGroup.c_str());
                            lstGroups.Add(sGroup.c_str());
                        }
                        ::NetApiBufferFree(pGroups);
                    }
                }

                static void PopulateWin32SidAndDomain(LPCWSTR pwszUser, UserInfo& info) {
                    BYTE sidBuffer[SECURITY_MAX_SID_SIZE];
                    DWORD cbSid = sizeof(sidBuffer);
                    WCHAR szDomain[256] = { 0 };
                    DWORD cchDomain = 256;
                    SID_NAME_USE peUse;

                    if (::LookupAccountNameW(NULL, pwszUser, (PSID)sidBuffer, &cbSid, szDomain, &cchDomain, &peUse)) {
                        LPWSTR pszStringSid = NULL;
                        if (::ConvertSidToStringSidW((PSID)sidBuffer, &pszStringSid)) {
                            info.sSidOrUid = StringConvertInternal::WCharToUtf8(pszStringSid).c_str();
                            ::LocalFree(pszStringSid);
                        }
                        if (cchDomain > 0) {
                            info.sDomain = StringConvertInternal::WCharToUtf8(szDomain).c_str();
                        }
                    }
                }

                static UserInfo BuildWin32UserInfo(const USER_INFO_1* pUi) {
                    UserInfo info;
                    std::string sName = StringConvertInternal::WCharToUtf8(pUi->usri1_name);
                    info.sUsername = sName.c_str();

                    info.sDomain = "LOCAL";
                    info.sSidOrUid = "S-1-5-21-USER";
                    info.eUserClass = ClassifyWindowsUser(pUi->usri1_priv, pUi->usri1_flags);
                    info.bIsDisabled = (pUi->usri1_flags & UF_ACCOUNTDISABLE) != 0;
                    info.bIsPasswordRequired = (pUi->usri1_flags & UF_PASSWD_NOTREQD) == 0;
                    info.bIsAccountLocked = (pUi->usri1_flags & UF_LOCKOUT) != 0;

                    if (info.eUserClass == UserClass::Admin) {
                        info.lstPermissions.Add("AdministratorRights");
                        info.lstPermissions.Add("FullControl");
                    } else {
                        info.lstPermissions.Add("StandardUserRights");
                    }

                    PopulateWin32SidAndDomain(pUi->usri1_name, info);
                    PopulateWin32UserGroups(pUi->usri1_name, info.lstGroups, info.lstPermissions);
                    return info;
                }

                static void EnumerateWin32Users(Collections::Generic::List<UserInfo>& lstUsers) {
                    LPUSER_INFO_1 pBuf = NULL;
                    DWORD dwEntriesRead = 0, dwTotalEntries = 0, dwResumeHandle = 0;

                    NET_API_STATUS nStatus = ::NetUserEnum(NULL, 1, FILTER_NORMAL_ACCOUNT, (LPBYTE*)&pBuf, MAX_PREFERRED_LENGTH, &dwEntriesRead, &dwTotalEntries, &dwResumeHandle);
                    if (nStatus == ERROR_ACCESS_DENIED) {
                        throw UnauthorizedAccessException("Access denied enumerating user accounts. Administrator privileges required.");
                    }

                    if (nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) {
                        for (DWORD i = 0; i < dwEntriesRead; i++) {
                            lstUsers.Add(BuildWin32UserInfo(&pBuf[i]));
                        }
                    }

                    if (pBuf != NULL) {
                        ::NetApiBufferFree(pBuf);
                    }
                }
#else
                static UserInfo BuildLinuxUserInfo(const struct passwd* pw) {
                    UserInfo info;
                    info.sUsername = pw->pw_name;
                    info.sDomain = "LOCAL";
                    info.sSidOrUid = std::to_string(pw->pw_uid).c_str();
                    info.bIsDisabled = false;
                    info.bIsPasswordRequired = true;
                    info.bIsAccountLocked = false;

                    if (pw->pw_uid == 0) {
                        info.eUserClass = UserClass::Admin;
                        info.lstPermissions.Add("RootPrivileges");
                        info.lstPermissions.Add("FullControl");
                    } else if (pw->pw_uid < 1000) {
                        info.eUserClass = UserClass::System;
                        info.lstPermissions.Add("SystemDaemonRights");
                    } else {
                        info.eUserClass = UserClass::Normal;
                        info.lstPermissions.Add("StandardUserRights");
                    }

                    return info;
                }

                static void EnumerateLinuxUsers(Collections::Generic::List<UserInfo>& lstUsers) {
                    errno = 0;
                    setpwent();
                    struct passwd* pw;
                    while ((pw = getpwent()) != NULL) {
                        lstUsers.Add(BuildLinuxUserInfo(pw));
                    }
                    if (errno == EACCES || errno == EPERM) {
                        endpwent();
                        throw UnauthorizedAccessException("Access denied enumerating user accounts. Root privileges required.");
                    }
                    endpwent();
                }
#endif

                Collections::Generic::List<UserInfo> UserPrincipal::EnumerateUsers() {
                    Collections::Generic::List<UserInfo> lstUsers;

#if defined(_WIN32)
                    EnumerateWin32Users(lstUsers);
#else
                    EnumerateLinuxUsers(lstUsers);
#endif

                    return lstUsers;
                }

#if defined(_WIN32)
                static UserInfo QueryWin32User(const String& sUsername) {
                    std::wstring wUsername = StringConvertInternal::Utf8ToWChar(sUsername.GetRawString() ? sUsername.GetRawString() : "");
                    LPUSER_INFO_1 pBuf = NULL;
                    NET_API_STATUS nStatus = ::NetUserGetInfo(NULL, wUsername.c_str(), 1, (LPBYTE*)&pBuf);
                    if (nStatus == NERR_Success && pBuf != NULL) {
                        UserInfo info = BuildWin32UserInfo(pBuf);
                        ::NetApiBufferFree(pBuf);
                        return info;
                    }
                    if (nStatus == ERROR_ACCESS_DENIED) throw UnauthorizedAccessException("Access denied querying user information for: " + sUsername);
                    if (nStatus == NERR_UserNotFound || nStatus == ERROR_NO_SUCH_USER) throw ArgumentException("User not found: " + sUsername);
                    throw ComponentModel::Win32Exception(nStatus, "Failed to query user information for: " + sUsername);
                }

                static std::string GetCurrentWin32UserName() {
                    WCHAR szName[256] = { 0 };
                    DWORD dwSize = 256;
                    if (!::GetUserNameW(szName, &dwSize)) {
                        DWORD dwErr = ::GetLastError();
                        if (dwErr == ERROR_ACCESS_DENIED) throw UnauthorizedAccessException("Access denied querying current user name.");
                        throw ComponentModel::Win32Exception(dwErr, "Failed to get current user name.");
                    }
                    return StringConvertInternal::WCharToUtf8(szName);
                }
#else
                static UserInfo QueryLinuxUser(const String& sUsername) {
                    errno = 0;
                    struct passwd* pw = getpwnam(sUsername.GetRawString());
                    if (pw == NULL) {
                        if (errno == EACCES || errno == EPERM) throw UnauthorizedAccessException("Access denied querying user information for: " + sUsername);
                        throw ArgumentException("User not found: " + sUsername);
                    }
                    return BuildLinuxUserInfo(pw);
                }

                static UserInfo QueryCurrentLinuxUser() {
                    errno = 0;
                    struct passwd* pw = getpwuid(getuid());
                    if (pw == NULL) {
                        if (errno == EACCES || errno == EPERM) throw UnauthorizedAccessException("Access denied querying current user information.");
                        throw SystemException("Failed to query current user information.");
                    }
                    return BuildLinuxUserInfo(pw);
                }
#endif

                UserInfo UserPrincipal::GetUser(const String& sUsername) {
                    if (sUsername.IsEmpty()) throw ArgumentException("Username cannot be empty.");
#if defined(_WIN32)
                    return QueryWin32User(sUsername);
#else
                    return QueryLinuxUser(sUsername);
#endif
                }

                UserInfo UserPrincipal::GetCurrent() {
#if defined(_WIN32)
                    return GetUser(GetCurrentWin32UserName().c_str());
#else
                    return QueryCurrentLinuxUser();
#endif
                }

            }
        }
    }
}

Generated by OpenCppCoverage (Version: 0.9.9.0)