diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.security.auth/windows/native/libjaas/nt.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.security.auth/windows/native/libjaas/nt.c Tue Sep 12 19:03:39 2017 +0200 @@ -0,0 +1,811 @@ +/* + * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include "jni_util.h" +#include "com_sun_security_auth_module_NTSystem.h" + +#include +#include +#include +#include +#include + +static BOOL debug = FALSE; + +BOOL getToken(PHANDLE); +BOOL getUser(HANDLE tokenHandle, LPTSTR *userName, + LPTSTR *domainName, LPTSTR *userSid, LPTSTR *domainSid); +BOOL getPrimaryGroup(HANDLE tokenHandle, LPTSTR *primaryGroup); +BOOL getGroups(HANDLE tokenHandle, PDWORD numGroups, LPTSTR **groups); +BOOL getImpersonationToken(PHANDLE impersonationToken); +BOOL getTextualSid(PSID pSid, LPTSTR TextualSid, LPDWORD lpdwBufferLen); +void DisplayErrorText(DWORD dwLastError); + +static void throwIllegalArgumentException(JNIEnv *env, const char *msg) { + jclass clazz = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); + if (clazz != NULL) + (*env)->ThrowNew(env, clazz, msg); +} + +/* + * Declare library specific JNI_Onload entry if static build + */ +DEF_STATIC_JNI_OnLoad + +JNIEXPORT jlong JNICALL +Java_com_sun_security_auth_module_NTSystem_getImpersonationToken0 + (JNIEnv *env, jobject obj) { + HANDLE impersonationToken = 0; // impersonation token + if (debug) { + printf("getting impersonation token\n"); + } + if (getImpersonationToken(&impersonationToken) == FALSE) { + return 0; + } + return (jlong)impersonationToken; +} + +JNIEXPORT void JNICALL +Java_com_sun_security_auth_module_NTSystem_getCurrent + (JNIEnv *env, jobject obj, jboolean debugNative) { + + long i, j = 0; + HANDLE tokenHandle = INVALID_HANDLE_VALUE; + + LPTSTR userName = NULL; // user name + LPTSTR userSid = NULL; // user sid + LPTSTR domainName = NULL; // domain name + LPTSTR domainSid = NULL; // domain sid + LPTSTR primaryGroup = NULL; // primary group sid + DWORD numGroups = 0; // num groups + LPTSTR *groups = NULL; // groups array + long pIndex = -1; // index of primaryGroup in groups array + + jfieldID fid; + jstring jstr; + jobjectArray jgroups; + jclass stringClass = 0; + jclass cls = (*env)->GetObjectClass(env, obj); + + debug = debugNative; + + // get NT information first + + if (debug) { + printf("getting access token\n"); + } + if (getToken(&tokenHandle) == FALSE) { + return; + } + + if (debug) { + printf("getting user info\n"); + } + if (getUser + (tokenHandle, &userName, &domainName, &userSid, &domainSid) == FALSE) { + return; + } + + if (debug) { + printf("getting primary group\n"); + } + if (getPrimaryGroup(tokenHandle, &primaryGroup) == FALSE) { + return; + } + + if (debug) { + printf("getting supplementary groups\n"); + } + if (getGroups(tokenHandle, &numGroups, &groups) == FALSE) { + return; + } + + // then set values into NTSystem + + fid = (*env)->GetFieldID(env, cls, "userName", "Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "invalid field: userName"); + goto cleanup; + } + jstr = (*env)->NewStringUTF(env, userName); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectField(env, obj, fid, jstr); + + fid = (*env)->GetFieldID(env, cls, "userSID", "Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "invalid field: userSID"); + goto cleanup; + } + jstr = (*env)->NewStringUTF(env, userSid); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectField(env, obj, fid, jstr); + + fid = (*env)->GetFieldID(env, cls, "domain", "Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "invalid field: domain"); + goto cleanup; + } + jstr = (*env)->NewStringUTF(env, domainName); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectField(env, obj, fid, jstr); + + if (domainSid != NULL) { + fid = (*env)->GetFieldID(env, cls, "domainSID", "Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "invalid field: domainSID"); + goto cleanup; + } + jstr = (*env)->NewStringUTF(env, domainSid); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectField(env, obj, fid, jstr); + } + + fid = (*env)->GetFieldID(env, cls, "primaryGroupID", "Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "invalid field: PrimaryGroupID"); + goto cleanup; + } + jstr = (*env)->NewStringUTF(env, primaryGroup); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectField(env, obj, fid, jstr); + + // primary group may or may not be part of supplementary groups + for (i = 0; i < (long)numGroups; i++) { + if (strcmp(primaryGroup, groups[i]) == 0) { + // found primary group in groups array + pIndex = i; + break; + } + } + + if (numGroups == 0 || (pIndex == 0 && numGroups == 1)) { + // primary group is only group in groups array + + if (debug) { + printf("no secondary groups\n"); + } + } else { + + // the groups array is non-empty, + // and may or may not contain the primary group + + fid = (*env)->GetFieldID(env, cls, "groupIDs", "[Ljava/lang/String;"); + if (fid == 0) { + (*env)->ExceptionClear(env); + throwIllegalArgumentException(env, "groupIDs"); + goto cleanup; + } + + stringClass = (*env)->FindClass(env, "java/lang/String"); + if (stringClass == NULL) + goto cleanup; + + if (pIndex == -1) { + // primary group not in groups array + jgroups = (*env)->NewObjectArray(env, numGroups, stringClass, 0); + } else { + // primary group in groups array - + // allocate one less array entry and do not add into new array + jgroups = (*env)->NewObjectArray(env, numGroups-1, stringClass, 0); + } + if (jgroups == NULL) + goto cleanup; + + for (i = 0, j = 0; i < (long)numGroups; i++) { + if (pIndex == i) { + // continue if equal to primary group + continue; + } + jstr = (*env)->NewStringUTF(env, groups[i]); + if (jstr == NULL) + goto cleanup; + (*env)->SetObjectArrayElement(env, jgroups, j++, jstr); + } + (*env)->SetObjectField(env, obj, fid, jgroups); + } + +cleanup: + if (userName != NULL) { + HeapFree(GetProcessHeap(), 0, userName); + } + if (domainName != NULL) { + HeapFree(GetProcessHeap(), 0, domainName); + } + if (userSid != NULL) { + HeapFree(GetProcessHeap(), 0, userSid); + } + if (domainSid != NULL) { + HeapFree(GetProcessHeap(), 0, domainSid); + } + if (primaryGroup != NULL) { + HeapFree(GetProcessHeap(), 0, primaryGroup); + } + if (groups != NULL) { + for (i = 0; i < (long)numGroups; i++) { + if (groups[i] != NULL) { + HeapFree(GetProcessHeap(), 0, groups[i]); + } + } + HeapFree(GetProcessHeap(), 0, groups); + } + CloseHandle(tokenHandle); + + return; +} + +BOOL getToken(PHANDLE tokenHandle) { + + // first try the thread token + if (OpenThreadToken(GetCurrentThread(), + TOKEN_READ, + FALSE, + tokenHandle) == 0) { + if (debug) { + printf(" [getToken] OpenThreadToken error [%d]: ", GetLastError()); + DisplayErrorText(GetLastError()); + } + + // next try the process token + if (OpenProcessToken(GetCurrentProcess(), + TOKEN_READ, + tokenHandle) == 0) { + if (debug) { + printf(" [getToken] OpenProcessToken error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + return FALSE; + } + } + + if (debug) { + printf(" [getToken] got user access token\n"); + } + + return TRUE; +} + +BOOL getUser(HANDLE tokenHandle, LPTSTR *userName, + LPTSTR *domainName, LPTSTR *userSid, LPTSTR *domainSid) { + + BOOL error = FALSE; + DWORD bufSize = 0; + DWORD buf2Size = 0; + DWORD retBufSize = 0; + PTOKEN_USER tokenUserInfo = NULL; // getTokenInformation + SID_NAME_USE nameUse; // LookupAccountSid + + PSID dSid = NULL; + LPTSTR domainSidName = NULL; + + // get token information + GetTokenInformation(tokenHandle, + TokenUser, + NULL, // TokenInformation - if NULL get buffer size + 0, // since TokenInformation is NULL + &bufSize); + + tokenUserInfo = (PTOKEN_USER)HeapAlloc(GetProcessHeap(), 0, bufSize); + if (GetTokenInformation(tokenHandle, + TokenUser, + tokenUserInfo, + bufSize, + &retBufSize) == 0) { + if (debug) { + printf(" [getUser] GetTokenInformation error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + error = TRUE; + goto cleanup; + } + + if (debug) { + printf(" [getUser] Got TokenUser info\n"); + } + + // get userName + bufSize = 0; + buf2Size = 0; + LookupAccountSid(NULL, // local host + tokenUserInfo->User.Sid, + NULL, + &bufSize, + NULL, + &buf2Size, + &nameUse); + + *userName = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, bufSize); + *domainName = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, buf2Size); + if (LookupAccountSid(NULL, // local host + tokenUserInfo->User.Sid, + *userName, + &bufSize, + *domainName, + &buf2Size, + &nameUse) == 0) { + if (debug) { + printf(" [getUser] LookupAccountSid error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + error = TRUE; + goto cleanup; + } + + if (debug) { + printf(" [getUser] userName: %s, domainName = %s\n", + *userName, *domainName); + } + + bufSize = 0; + getTextualSid(tokenUserInfo->User.Sid, NULL, &bufSize); + *userSid = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, bufSize); + getTextualSid(tokenUserInfo->User.Sid, *userSid, &bufSize); + if (debug) { + printf(" [getUser] userSid: %s\n", *userSid); + } + + // get domainSid + bufSize = 0; + buf2Size = 0; + LookupAccountName(NULL, // local host + *domainName, + NULL, + &bufSize, + NULL, + &buf2Size, + &nameUse); + + dSid = (PSID)HeapAlloc(GetProcessHeap(), 0, bufSize); + domainSidName = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, buf2Size); + if (LookupAccountName(NULL, // local host + *domainName, + dSid, + &bufSize, + domainSidName, + &buf2Size, + &nameUse) == 0) { + if (debug) { + printf(" [getUser] LookupAccountName error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + // ok not to have a domain SID (no error) + goto cleanup; + } + + bufSize = 0; + getTextualSid(dSid, NULL, &bufSize); + *domainSid = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, bufSize); + getTextualSid(dSid, *domainSid, &bufSize); + if (debug) { + printf(" [getUser] domainSid: %s\n", *domainSid); + } + +cleanup: + if (tokenUserInfo != NULL) { + HeapFree(GetProcessHeap(), 0, tokenUserInfo); + } + if (dSid != NULL) { + HeapFree(GetProcessHeap(), 0, dSid); + } + if (domainSidName != NULL) { + HeapFree(GetProcessHeap(), 0, domainSidName); + } + if (error) { + return FALSE; + } + return TRUE; +} + +BOOL getPrimaryGroup(HANDLE tokenHandle, LPTSTR *primaryGroup) { + + BOOL error = FALSE; + DWORD bufSize = 0; + DWORD retBufSize = 0; + + PTOKEN_PRIMARY_GROUP tokenGroupInfo = NULL; + + // get token information + GetTokenInformation(tokenHandle, + TokenPrimaryGroup, + NULL, // TokenInformation - if NULL get buffer size + 0, // since TokenInformation is NULL + &bufSize); + + tokenGroupInfo = (PTOKEN_PRIMARY_GROUP)HeapAlloc + (GetProcessHeap(), 0, bufSize); + if (GetTokenInformation(tokenHandle, + TokenPrimaryGroup, + tokenGroupInfo, + bufSize, + &retBufSize) == 0) { + if (debug) { + printf(" [getPrimaryGroup] GetTokenInformation error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + error = TRUE; + goto cleanup; + } + + if (debug) { + printf(" [getPrimaryGroup] Got TokenPrimaryGroup info\n"); + } + + bufSize = 0; + getTextualSid(tokenGroupInfo->PrimaryGroup, NULL, &bufSize); + *primaryGroup = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, bufSize); + getTextualSid(tokenGroupInfo->PrimaryGroup, *primaryGroup, &bufSize); + if (debug) { + printf(" [getPrimaryGroup] primaryGroup: %s\n", *primaryGroup); + } + +cleanup: + if (tokenGroupInfo != NULL) { + HeapFree(GetProcessHeap(), 0, tokenGroupInfo); + } + if (error) { + return FALSE; + } + return TRUE; +} + +BOOL getGroups(HANDLE tokenHandle, PDWORD numGroups, LPTSTR **groups) { + + BOOL error = FALSE; + DWORD bufSize = 0; + DWORD retBufSize = 0; + long i = 0; + + PTOKEN_GROUPS tokenGroupInfo = NULL; + + // get token information + GetTokenInformation(tokenHandle, + TokenGroups, + NULL, // TokenInformation - if NULL get buffer size + 0, // since TokenInformation is NULL + &bufSize); + + tokenGroupInfo = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(), 0, bufSize); + if (GetTokenInformation(tokenHandle, + TokenGroups, + tokenGroupInfo, + bufSize, + &retBufSize) == 0) { + if (debug) { + printf(" [getGroups] GetTokenInformation error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + error = TRUE; + goto cleanup; + } + + if (debug) { + printf(" [getGroups] Got TokenGroups info\n"); + } + + if (tokenGroupInfo->GroupCount == 0) { + // no groups + goto cleanup; + } + + // return group info + *numGroups = tokenGroupInfo->GroupCount; + *groups = (LPTSTR *)HeapAlloc + (GetProcessHeap(), 0, (*numGroups) * sizeof(LPTSTR)); + for (i = 0; i < (long)*numGroups; i++) { + bufSize = 0; + getTextualSid(tokenGroupInfo->Groups[i].Sid, NULL, &bufSize); + (*groups)[i] = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, bufSize); + getTextualSid(tokenGroupInfo->Groups[i].Sid, (*groups)[i], &bufSize); + if (debug) { + printf(" [getGroups] group %d: %s\n", i, (*groups)[i]); + } + } + +cleanup: + if (tokenGroupInfo != NULL) { + HeapFree(GetProcessHeap(), 0, tokenGroupInfo); + } + if (error) { + return FALSE; + } + return TRUE; +} + +BOOL getImpersonationToken(PHANDLE impersonationToken) { + + HANDLE dupToken; + + if (OpenThreadToken(GetCurrentThread(), + TOKEN_DUPLICATE, + FALSE, + &dupToken) == 0) { + if (OpenProcessToken(GetCurrentProcess(), + TOKEN_DUPLICATE, + &dupToken) == 0) { + if (debug) { + printf + (" [getImpersonationToken] OpenProcessToken error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + return FALSE; + } + } + + if (DuplicateToken(dupToken, + SecurityImpersonation, + impersonationToken) == 0) { + if (debug) { + printf(" [getImpersonationToken] DuplicateToken error [%d]: ", + GetLastError()); + DisplayErrorText(GetLastError()); + } + return FALSE; + } + CloseHandle(dupToken); + + if (debug) { + printf(" [getImpersonationToken] token = %p\n", + (void *)*impersonationToken); + } + return TRUE; +} + +BOOL getTextualSid + (PSID pSid, // binary SID + LPTSTR TextualSid, // buffer for Textual representation of SID + LPDWORD lpdwBufferLen) { // required/provided TextualSid buffersize + + PSID_IDENTIFIER_AUTHORITY psia; + DWORD dwSubAuthorities; + DWORD dwSidRev=SID_REVISION; + DWORD dwCounter; + DWORD dwSidSize; + + // Validate the binary SID. + if(!IsValidSid(pSid)) return FALSE; + + // Get the identifier authority value from the SID. + psia = GetSidIdentifierAuthority(pSid); + + // Get the number of subauthorities in the SID. + dwSubAuthorities = *GetSidSubAuthorityCount(pSid); + + // Compute the buffer length. + // S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL + dwSidSize=(15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR); + + // Check input buffer length. + // If too small, indicate the proper size and set last error. + if (*lpdwBufferLen < dwSidSize) { + *lpdwBufferLen = dwSidSize; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + // Add 'S' prefix and revision number to the string. + dwSidSize=wsprintf(TextualSid, TEXT("S-%lu-"), dwSidRev ); + + // Add SID identifier authority to the string. + if ((psia->Value[0] != 0) || (psia->Value[1] != 0)) { + dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid), + TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"), + (USHORT)psia->Value[0], + (USHORT)psia->Value[1], + (USHORT)psia->Value[2], + (USHORT)psia->Value[3], + (USHORT)psia->Value[4], + (USHORT)psia->Value[5]); + } else { + dwSidSize+=wsprintf(TextualSid + lstrlen(TextualSid), + TEXT("%lu"), + (ULONG)(psia->Value[5] ) + + (ULONG)(psia->Value[4] << 8) + + (ULONG)(psia->Value[3] << 16) + + (ULONG)(psia->Value[2] << 24) ); + } + + // Add SID subauthorities to the string. + for (dwCounter=0 ; dwCounter < dwSubAuthorities ; dwCounter++) { + dwSidSize+=wsprintf(TextualSid + dwSidSize, TEXT("-%lu"), + *GetSidSubAuthority(pSid, dwCounter) ); + } + + return TRUE; +} + +void DisplayErrorText(DWORD dwLastError) { + HMODULE hModule = NULL; // default to system source + LPSTR MessageBuffer; + DWORD dwBufferLength; + + DWORD dwFormatFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM ; + + // + // If dwLastError is in the network range, + // load the message source. + // + + if(dwLastError >= NERR_BASE && dwLastError <= MAX_NERR) { + hModule = LoadLibraryEx(TEXT("netmsg.dll"), + NULL, + LOAD_LIBRARY_AS_DATAFILE); + + if(hModule != NULL) + dwFormatFlags |= FORMAT_MESSAGE_FROM_HMODULE; + } + + // + // Call FormatMessage() to allow for message + // text to be acquired from the system + // or from the supplied module handle. + // + + if(dwBufferLength = FormatMessageA(dwFormatFlags, + hModule, // module to get message from (NULL == system) + dwLastError, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // default language + (LPSTR) &MessageBuffer, + 0, + NULL)) { + DWORD dwBytesWritten; + + // + // Output message string on stderr. + // + WriteFile(GetStdHandle(STD_ERROR_HANDLE), + MessageBuffer, + dwBufferLength, + &dwBytesWritten, + NULL); + + // + // Free the buffer allocated by the system. + // + LocalFree(MessageBuffer); + } + + // + // If we loaded a message source, unload it. + // + if(hModule != NULL) + FreeLibrary(hModule); +} + +/** + * 1. comment out first two #includes + * 2. set 'debug' to TRUE + * 3. comment out 'getCurrent' + * 4. uncomment 'main' + * 5. cc -c nt.c + * 6. link nt.obj user32.lib advapi32.lib /out:nt.exe + */ +/* +void main(int argc, char *argv[]) { + + long i = 0; + HANDLE tokenHandle = INVALID_HANDLE_VALUE; + + LPTSTR userName = NULL; + LPTSTR userSid = NULL; + LPTSTR domainName = NULL; + LPTSTR domainSid = NULL; + LPTSTR primaryGroup = NULL; + DWORD numGroups = 0; + LPTSTR *groups = NULL; + HANDLE impersonationToken = 0; + + printf("getting access token\n"); + if (getToken(&tokenHandle) == FALSE) { + exit(1); + } + + printf("getting user info\n"); + if (getUser + (tokenHandle, &userName, &domainName, &userSid, &domainSid) == FALSE) { + exit(1); + } + + printf("getting primary group\n"); + if (getPrimaryGroup(tokenHandle, &primaryGroup) == FALSE) { + exit(1); + } + + printf("getting supplementary groups\n"); + if (getGroups(tokenHandle, &numGroups, &groups) == FALSE) { + exit(1); + } + + printf("getting impersonation token\n"); + if (getImpersonationToken(&impersonationToken) == FALSE) { + exit(1); + } + + printf("userName = %s, userSid = %s, domainName = %s, domainSid = %s\n", + userName, userSid, domainName, domainSid); + printf("primaryGroup = %s\n", primaryGroup); + for (i = 0; i < numGroups; i++) { + printf("Group[%d] = %s\n", i, groups[i]); + } + printf("impersonationToken = %ld\n", impersonationToken); + + if (userName != NULL) { + HeapFree(GetProcessHeap(), 0, userName); + } + if (userSid != NULL) { + HeapFree(GetProcessHeap(), 0, userSid); + } + if (domainName != NULL) { + HeapFree(GetProcessHeap(), 0, domainName); + } + if (domainSid != NULL) { + HeapFree(GetProcessHeap(), 0, domainSid); + } + if (primaryGroup != NULL) { + HeapFree(GetProcessHeap(), 0, primaryGroup); + } + if (groups != NULL) { + for (i = 0; i < numGroups; i++) { + if (groups[i] != NULL) { + HeapFree(GetProcessHeap(), 0, groups[i]); + } + } + HeapFree(GetProcessHeap(), 0, groups); + } + CloseHandle(impersonationToken); + CloseHandle(tokenHandle); +} +*/ + +/** + * extra main method for testing debug printing + */ +/* +void main(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr,"Usage: %s \n", argv[0]); + } + + DisplayErrorText(atoi(argv[1])); +} +*/