/* * PROJECT: ReactOS Whoami * LICENSE: GPL - See COPYING in the top level directory * FILE: base/applications/cmdutils/whoami/whoami.c * PURPOSE: Displays information about the current local user, groups and privileges. * PROGRAMMERS: Ismael Ferreras Morezuelas (swyterzone+ros@gmail.com) */ #define SECURITY_WIN32 #include #include #include #include #include "resource.h" #define wprintf(...) ConPrintf(StdOut, ##__VA_ARGS__) BOOL NoHeader = FALSE; UINT NoHeaderArgCount = 0; UINT PrintFormatArgCount = 0; enum { undefined, table, list, csv } PrintFormat = undefined; BOOL GetArgument(WCHAR* arg, int argc, WCHAR* argv[]) { int i; if (!arg) return FALSE; for (i = 1; i < argc; i++) { if (wcsicmp(argv[i], arg) == 0) return TRUE; } return FALSE; } /* blanking out the accepted modifiers will make filtering easier later on */ void BlankArgument(int argc, WCHAR* argv[]) { argv[argc] = L""; } /* helper functions; let's keep it tidy to avoid redundancies */ LPWSTR WhoamiGetUser(EXTENDED_NAME_FORMAT NameFormat) { LPWSTR UsrBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PATH); ULONG UsrSiz = MAX_PATH; if (UsrBuf == NULL) return NULL; if (GetUserNameExW(NameFormat, UsrBuf, &UsrSiz)) { CharLowerW(UsrBuf); return UsrBuf; } HeapFree(GetProcessHeap(), 0, UsrBuf); return NULL; } BOOL WhoamiFree(VOID* Buffer) { return HeapFree(GetProcessHeap(), 0, Buffer); } VOID* WhoamiGetTokenInfo(TOKEN_INFORMATION_CLASS TokenType) { HANDLE hToken = 0; DWORD dwLength = 0; VOID* pTokenInfo = 0; if (OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &hToken)) { GetTokenInformation(hToken, TokenType, NULL, dwLength, &dwLength); if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { pTokenInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (pTokenInfo == NULL) { wprintf(L"ERROR: not enough memory to allocate the token structure.\r\n"); exit(1); } } if (!GetTokenInformation(hToken, TokenType, (LPVOID)pTokenInfo, dwLength, &dwLength)) { wprintf(L"ERROR 0x%x: could not get token information.\r\n", GetLastError()); WhoamiFree(pTokenInfo); exit(1); } CloseHandle(hToken); } return pTokenInfo; } LPWSTR WhoamiLoadRcString(INT ResId) { #define RC_STRING_MAX_SIZE 850 static WCHAR TmpBuffer[RC_STRING_MAX_SIZE]; LoadStringW(GetModuleHandleW(NULL), ResId, TmpBuffer, RC_STRING_MAX_SIZE); return TmpBuffer; } void WhoamiPrintHeader(int HeaderId) { PWSTR Header = WhoamiLoadRcString(HeaderId); DWORD Length = wcslen(Header); if (NoHeader || PrintFormat == csv) return; wprintf(L"\n%s\n", Header); while (Length--) wprintf(L"-"); wprintf(L"\n\n"); } typedef struct { UINT Rows; UINT Cols; LPWSTR Content[1]; } WhoamiTable; /* create and prepare a new table for printing */ WhoamiTable *WhoamiAllocTable(UINT Rows, UINT Cols) { WhoamiTable *pTable = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WhoamiTable) + sizeof(LPWSTR) * Rows * Cols); // wprintf(L"DEBUG: Allocating %dx%d elem table for printing.\r\n\r\n", Rows, Cols); if (!pTable) { wprintf(L"ERROR: Not enough memory for displaying the table."); exit(1); } pTable->Rows = Rows; pTable->Cols = Cols; return pTable; } /* allocate and fill a new entry in the table */ void WhoamiSetTable(WhoamiTable *pTable, WCHAR *Entry, UINT Row, UINT Col) { LPWSTR Target = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1 + wcslen(Entry) * sizeof(Entry[0])); // wprintf(L"DEBUG: Setting table value '%lp' '%ls' for %lu %lu.\n", entry, entry, row, col); if (!Target) exit(1); wcscpy(Target, Entry); pTable->Content[Row * pTable->Cols + Col] = Target; } /* fill a new entry in the table */ void WhoamiSetTableDyn(WhoamiTable *pTable, WCHAR *Entry, UINT Row, UINT Col) { pTable->Content[Row * pTable->Cols + Col] = Entry; } /* print and deallocate the table */ void WhoamiPrintTable(WhoamiTable *pTable) { UINT i, j; UINT CurRow, CurCol; UINT *ColLength; if (!pTable) { wprintf(L"ERROR: The table passed for display is empty."); exit(1); } /* if we are going to print a *list* or *table*; take note of the total column size, as we will need it later on when printing them in a tabular fashion, according to their windows counterparts */ if (PrintFormat != csv) { ColLength = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(UINT) * pTable->Cols); if (PrintFormat == list) { for (j = 0; j < pTable->Cols; j++) if (pTable->Content[j]) { UINT ThisLength = wcslen(pTable->Content[j]); /* now that we're here, seize the opportunity and add those pesky ":" */ pTable->Content[j][ThisLength++] = L':'; pTable->Content[j][ThisLength] = UNICODE_NULL; ColLength[0] = max(ThisLength, ColLength[0]); } } else { for (j = 0; j < pTable->Cols; j++) for (i = 0; i < pTable->Rows; i++) if (pTable->Content[i * pTable->Cols + j]) { UINT ThisLength = wcslen(pTable->Content[i * pTable->Cols + j]); ColLength[j] = max(ThisLength, ColLength[j]); } } } switch (PrintFormat) { case csv: { for (i = 0; i < pTable->Rows; i++) { if (!pTable->Content[i * pTable->Cols]) continue; /* if the user especified /nh then skip the column labels */ if (NoHeader && i == 0) continue; for (j = 0; j < pTable->Cols; j++) { if (pTable->Content[i * pTable->Cols + j]) { wprintf(L"\"%s\"%s", pTable->Content[i * pTable->Cols + j], (j+1 < pTable->Cols ? L"," : L"")); } } wprintf(L"\n"); } break; } case list: { UINT FinalRow = 0; /* fixme: we need to do two passes to find out which entry is the last one shown, or not null this is not exactly optimal */ for (CurRow = 1; CurRow < pTable->Rows; CurRow++) { /* if the first member of this row isn't available, then forget it */ if (!pTable->Content[CurRow * pTable->Cols]) continue; FinalRow = CurRow; } for (CurRow = 1; CurRow < pTable->Rows; CurRow++) { /* if the first member of this row isn't available, then forget it */ if (!pTable->Content[CurRow * pTable->Cols]) continue; /* if the user especified /nh then skip the column labels */ if (NoHeader && i == 0) continue; for (CurCol = 0; CurCol < pTable->Cols; CurCol++) { wprintf(L"%-*s %s\n", ColLength[0], pTable->Content[CurCol], pTable->Content[CurRow * pTable->Cols + CurCol]); } /* don't add two carriage returns at the very end */ if (CurRow != FinalRow) wprintf(L"\n"); } break; } case table: default: { for (i = 0; i < pTable->Rows; i++) { /* if the first member of this row isn't available, then forget it */ if (!pTable->Content[i * pTable->Cols]) continue; /* if the user especified /nh then skip the column labels too */ if (NoHeader && i == 0) continue; for (j = 0; j < pTable->Cols; j++) { if (pTable->Content[i * pTable->Cols + j]) { wprintf(L"%-*s ", ColLength[j], pTable->Content[i * pTable->Cols + j]); } } wprintf(L"\n"); /* add the cute underline thingie for the table header */ if (i == 0) { for (j = 0; j < pTable->Cols; j++) { DWORD Length = ColLength[j]; while (Length--) wprintf(L"="); /* a spacing between all the columns except for the last one */ if (pTable->Cols != (i + 1)) wprintf(L" "); } wprintf(L"\n"); } } } } /* fixme: when many tables are displayed in a single run we have to sandwich carriage returns in between. */ // if (!final_entry) wprintf(L"\n"); for (i = 0; i < pTable->Rows; i++) for (j = 0; j < pTable->Cols; j++) WhoamiFree(pTable->Content[i * pTable->Cols + j]); WhoamiFree(pTable); if (PrintFormat != csv) HeapFree(GetProcessHeap(), 0, ColLength); } int WhoamiLogonId(void) { PTOKEN_GROUPS pGroupInfo = (PTOKEN_GROUPS) WhoamiGetTokenInfo(TokenGroups); DWORD dwIndex = 0; LPWSTR pSidStr = 0; PSID pSid = 0; if (pGroupInfo == NULL) return 0; /* lets see if we can find the logon SID in that list, should be there */ for (dwIndex = 0; dwIndex < pGroupInfo->GroupCount; dwIndex++) { if ((pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) { pSid = pGroupInfo->Groups[dwIndex].Sid; } } if (pSid == 0) { WhoamiFree(pGroupInfo); wprintf(L"ERROR: Couldn't find the logon SID.\n"); return 1; } if (!ConvertSidToStringSidW(pSid, &pSidStr)) { WhoamiFree(pGroupInfo); wprintf(L"ERROR: Couldn't convert the logon SID to a string.\n"); return 1; } else { /* let's show our converted logon SID */ wprintf(L"%s\n", pSidStr); } /* cleanup our allocations */ LocalFree(pSidStr); WhoamiFree(pGroupInfo); return 0; } int WhoamiUser(void) { PTOKEN_USER pUserInfo = (PTOKEN_USER) WhoamiGetTokenInfo(TokenUser); LPWSTR pUserStr = NULL; LPWSTR pSidStr = NULL; WhoamiTable *UserTable = NULL; if (pUserInfo == NULL) { return 1; } pUserStr = WhoamiGetUser(NameSamCompatible); if (pUserStr == NULL) { WhoamiFree(pUserInfo); return 1; } UserTable = WhoamiAllocTable(2, 2); WhoamiPrintHeader(IDS_USER_HEADER); /* set the column labels */ WhoamiSetTable(UserTable, WhoamiLoadRcString(IDS_COL_USER_NAME), 0, 0); WhoamiSetTable(UserTable, WhoamiLoadRcString(IDS_COL_SID), 0, 1); ConvertSidToStringSidW(pUserInfo->User.Sid, &pSidStr); /* set the values for our single row of data */ WhoamiSetTable(UserTable, pUserStr, 1, 0); WhoamiSetTable(UserTable, pSidStr, 1, 1); WhoamiPrintTable(UserTable); /* cleanup our allocations */ LocalFree(pSidStr); WhoamiFree(pUserInfo); WhoamiFree(pUserStr); return 0; } int WhoamiGroups(void) { DWORD dwIndex = 0; LPWSTR pSidStr = 0; static WCHAR szGroupName[255] = {0}; static WCHAR szDomainName[255] = {0}; DWORD cchGroupName = _countof(szGroupName); DWORD cchDomainName = _countof(szGroupName); SID_NAME_USE Use = 0; BYTE SidNameUseStr[12] = { /* SidTypeUser */ -1, /* SidTypeGroup */ -1, /* SidTypeDomain */ -1, /* SidTypeUser */ -1, /* SidTypeAlias */ IDS_TP_ALIAS, /* SidTypeWellKnownGroup */ IDS_TP_WELL_KNOWN_GROUP, /* SidTypeDeletedAccount */ -1, /* SidTypeInvalid */ -1, /* SidTypeUnknown */ -1, /* SidTypeComputer */ -1, /* SidTypeLabel */ IDS_TP_LABEL }; PTOKEN_GROUPS pGroupInfo = (PTOKEN_GROUPS)WhoamiGetTokenInfo(TokenGroups); UINT PrintingRow; WhoamiTable *GroupTable = NULL; if (pGroupInfo == NULL) { return 1; } /* the header is the first (0) row, so we start in the second one (1) */ PrintingRow = 1; GroupTable = WhoamiAllocTable(pGroupInfo->GroupCount + 1, 4); WhoamiPrintHeader(IDS_GROU_HEADER); WhoamiSetTable(GroupTable, WhoamiLoadRcString(IDS_COL_GROUP_NAME), 0, 0); WhoamiSetTable(GroupTable, WhoamiLoadRcString(IDS_COL_TYPE), 0, 1); WhoamiSetTable(GroupTable, WhoamiLoadRcString(IDS_COL_SID), 0, 2); WhoamiSetTable(GroupTable, WhoamiLoadRcString(IDS_COL_ATTRIB), 0, 3); for (dwIndex = 0; dwIndex < pGroupInfo->GroupCount; dwIndex++) { LookupAccountSidW(NULL, pGroupInfo->Groups[dwIndex].Sid, (LPWSTR)&szGroupName, &cchGroupName, (LPWSTR)&szDomainName, &cchDomainName, &Use); /* the original tool seems to limit the list to these kind of SID items */ if ((Use == SidTypeWellKnownGroup || Use == SidTypeAlias || Use == SidTypeLabel) && !(pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID)) { wchar_t tmpBuffer[666]; /* looks like windows treats 0x60 as 0x7 for some reason, let's just nod and call it a day: 0x60 is SE_GROUP_INTEGRITY | SE_GROUP_INTEGRITY_ENABLED 0x07 is SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED */ if (pGroupInfo->Groups[dwIndex].Attributes == 0x60) pGroupInfo->Groups[dwIndex].Attributes = 0x07; /* 1- format it as DOMAIN\GROUP if the domain exists, or just GROUP if not */ _snwprintf((LPWSTR)&tmpBuffer, _countof(tmpBuffer), L"%s%s%s", szDomainName, cchDomainName ? L"\\" : L"", szGroupName); WhoamiSetTable(GroupTable, tmpBuffer, PrintingRow, 0); /* 2- let's find out the group type by using a simple lookup table for lack of a better method */ WhoamiSetTable(GroupTable, WhoamiLoadRcString(SidNameUseStr[Use]), PrintingRow, 1); /* 3- turn that SID into text-form */ ConvertSidToStringSidW(pGroupInfo->Groups[dwIndex].Sid, &pSidStr); WhoamiSetTable(GroupTable, pSidStr, PrintingRow, 2); LocalFree(pSidStr); /* 4- reuse that buffer for appending the attributes in text-form at the very end */ ZeroMemory(tmpBuffer, sizeof(tmpBuffer)); if (pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_MANDATORY) StringCchCat(tmpBuffer, _countof(tmpBuffer), WhoamiLoadRcString(IDS_ATTR_GROUP_MANDATORY)); if (pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_ENABLED_BY_DEFAULT) StringCchCat(tmpBuffer, _countof(tmpBuffer), WhoamiLoadRcString(IDS_ATTR_GROUP_ENABLED_BY_DEFAULT)); if (pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_ENABLED) StringCchCat(tmpBuffer, _countof(tmpBuffer), WhoamiLoadRcString(IDS_ATTR_GROUP_ENABLED)); if (pGroupInfo->Groups[dwIndex].Attributes & SE_GROUP_OWNER) StringCchCat(tmpBuffer, _countof(tmpBuffer), WhoamiLoadRcString(IDS_ATTR_GROUP_OWNER)); /* remove the last comma (', ' which is 2 wchars) of the buffer, let's keep it simple */ tmpBuffer[max(wcslen(tmpBuffer) - 2, 0)] = UNICODE_NULL; WhoamiSetTable(GroupTable, tmpBuffer, PrintingRow, 3); PrintingRow++; } /* reset the buffers so that we can reuse them */ ZeroMemory(szGroupName, sizeof(szGroupName)); ZeroMemory(szDomainName, sizeof(szDomainName)); cchGroupName = 255; cchDomainName = 255; } WhoamiPrintTable(GroupTable); /* cleanup our allocations */ WhoamiFree((LPVOID)pGroupInfo); return 0; } int WhoamiPriv(void) { PTOKEN_PRIVILEGES pPrivInfo = (PTOKEN_PRIVILEGES) WhoamiGetTokenInfo(TokenPrivileges); DWORD dwResult = 0, dwIndex = 0; WhoamiTable *PrivTable = NULL; if (pPrivInfo == NULL) { return 1; } PrivTable = WhoamiAllocTable(pPrivInfo->PrivilegeCount + 1, 3); WhoamiPrintHeader(IDS_PRIV_HEADER); WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_COL_PRIV_NAME), 0, 0); WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_COL_DESCRIPTION), 0, 1); WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_COL_STATE), 0, 2); for (dwIndex = 0; dwIndex < pPrivInfo->PrivilegeCount; dwIndex++) { PWSTR PrivName = NULL, DispName = NULL; DWORD PrivNameSize = 0, DispNameSize = 0; BOOL ret = FALSE; ret = LookupPrivilegeNameW(NULL, &pPrivInfo->Privileges[dwIndex].Luid, NULL, &PrivNameSize); PrivName = HeapAlloc(GetProcessHeap(), 0, ++PrivNameSize*sizeof(WCHAR)); LookupPrivilegeNameW(NULL, &pPrivInfo->Privileges[dwIndex].Luid, PrivName, &PrivNameSize); WhoamiSetTableDyn(PrivTable, PrivName, dwIndex + 1, 0); /* try to grab the size of the string, also, beware, as this call is unimplemented in ReactOS/Wine at the moment */ LookupPrivilegeDisplayNameW(NULL, PrivName, NULL, &DispNameSize, &dwResult); DispName = HeapAlloc(GetProcessHeap(), 0, ++DispNameSize * sizeof(WCHAR)); ret = LookupPrivilegeDisplayNameW(NULL, PrivName, DispName, &DispNameSize, &dwResult); if (ret && DispName) { // wprintf(L"DispName: %d %x '%s'\n", DispNameSize, GetLastError(), DispName); WhoamiSetTableDyn(PrivTable, DispName, dwIndex + 1, 1); } else { WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_UNKNOWN_DESCRIPTION), dwIndex + 1, 1); if (DispName != NULL) WhoamiFree(DispName); } if (pPrivInfo->Privileges[dwIndex].Attributes & SE_PRIVILEGE_ENABLED) WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_STATE_ENABLED), dwIndex + 1, 2); else WhoamiSetTable(PrivTable, WhoamiLoadRcString(IDS_STATE_DISABLED), dwIndex + 1, 2); } WhoamiPrintTable(PrivTable); /* cleanup our allocations */ WhoamiFree(pPrivInfo); return 0; } int wmain(int argc, WCHAR* argv[]) { #define WAM_USER 1<<0 #define WAM_GROUPS 1<<1 #define WAM_PRIV 1<<2 INT i; BYTE WamBit = 0; /* Initialize the Console Standard Streams */ ConInitStdStreams(); /* * * * * * * * * * * * * * * * * A: no parameters whatsoever */ if (argc == 1) { /* if there's no arguments just choose the simple path and display the user's identity in lowercase */ LPWSTR UserBuffer = WhoamiGetUser(NameSamCompatible); if (UserBuffer) { wprintf(L"%s\n", UserBuffer); WhoamiFree(UserBuffer); return 0; } else { return 1; } } /* first things first-- let's detect and manage both printing modifiers (/fo and /nh) */ for (i = 1; i < argc; i++) { if (wcsicmp(argv[i], L"/nh") == 0) { NoHeaderArgCount++; if (NoHeader == FALSE) { NoHeader = TRUE; // wprintf(L"Headers disabled!\n"); BlankArgument(i, argv); } } } for (i = 1; i < argc; i++) { if (wcsicmp(argv[i], L"/fo") == 0) { if ((i + 1) < argc) { // wprintf(L"exists another param after /fo\n"); PrintFormatArgCount++; if (wcsicmp(argv[i + 1], L"table") == 0 && PrintFormat != table) { PrintFormat = table; // wprintf(L"Changed to table format\n"); BlankArgument(i, argv); BlankArgument(i + 1, argv); } else if (wcsicmp(argv[i + 1], L"list") == 0 && PrintFormat != list) { PrintFormat = list; // wprintf(L"Changed to list format\n"); BlankArgument(i, argv); BlankArgument(i + 1, argv); /* looks like you can't use the "/fo list /nh" options together for some stupid reason */ if (PrintFormat == list && NoHeader != FALSE) { wprintf(WhoamiLoadRcString(IDS_ERROR_NH_LIST)); return 1; } } else if (wcsicmp(argv[i + 1], L"csv") == 0 && PrintFormat != csv) { PrintFormat = csv; // wprintf(L"Changed to csv format\n"); BlankArgument(i, argv); BlankArgument(i + 1, argv); } /* /nh or /fo after /fo isn't parsed as a value */ else if (wcsicmp(argv[i + 1], L"/nh") == 0 || wcsicmp(argv[i + 1], L"/fo") == 0 /* same goes for the other named options, not ideal, but works */ || wcsicmp(argv[i + 1], L"/priv") == 0 || wcsicmp(argv[i + 1], L"/groups") == 0 || wcsicmp(argv[i + 1], L"/user") == 0 || wcsicmp(argv[i + 1], L"/all") == 0 || wcsicmp(argv[i + 1], L"") == 0) { goto FoValueExpected; } else { wprintf(WhoamiLoadRcString(IDS_ERROR_VALUENOTALLOWED), argv[i + 1]); return 1; } } else { FoValueExpected: wprintf(WhoamiLoadRcString(IDS_ERROR_VALUEXPECTED)); return 1; } } } if (NoHeaderArgCount >= 2) { wprintf(WhoamiLoadRcString(IDS_ERROR_1TIMES), L"/nh"); return 1; } /* special case when there's just a /nh as argument; it outputs nothing */ else if (NoHeaderArgCount == 1 && argc == 2) { return 0; } if (PrintFormatArgCount >= 2) { wprintf(WhoamiLoadRcString(IDS_ERROR_1TIMES), L"/fo"); return 1; } /* if there's just /fo ... call it invalid */ else if (PrintFormatArgCount == 1 && argc == 3) { goto InvalidSyntax; } /* * * * * * * * * * * * * * * B: one single parameter */ if (argc == 2) { /* now let's try to parse the triumvirate of simpler, single (1) arguments... plus help */ if (wcsicmp(argv[1], L"/?") == 0) { wprintf(WhoamiLoadRcString(IDS_HELP)); return 0; } else if (wcsicmp(argv[1], L"/upn") == 0) { LPWSTR UserBuffer = WhoamiGetUser(NameUserPrincipal); if (UserBuffer) { wprintf(L"%s\n", UserBuffer); WhoamiFree(UserBuffer); return 0; } else { wprintf(WhoamiLoadRcString(IDS_ERROR_UPN)); return 1; } } else if (wcsicmp(argv[1], L"/fqdn") == 0) { LPWSTR UserBuffer = WhoamiGetUser(NameFullyQualifiedDN); if (UserBuffer) { wprintf(L"%s\n", UserBuffer); WhoamiFree(UserBuffer); return 0; } else { wprintf(WhoamiLoadRcString(IDS_ERROR_FQDN)); return 1; } } else if (wcsicmp(argv[1], L"/logonid") == 0) { return WhoamiLogonId(); } } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * C: One main parameter with extra tasty modifiers to play with */ /* sometimes is just easier to whitelist for lack of a better method */ for (i=1; i