├── vendor ├── .gitignore └── signtool.exe ├── .gitignore ├── resources ├── elevate.ico └── elevate.rc ├── scripts ├── ci-deploy.sh ├── ci-sign.sh ├── ci-test.sh └── ci-build.sh ├── Makefile ├── README.md ├── .gitlab-ci.yml ├── LICENSE └── src └── elevate.c /vendor/.gitignore: -------------------------------------------------------------------------------- 1 | !signtool.exe 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.[ao] 2 | *.exe 3 | *.res 4 | *.swp 5 | -------------------------------------------------------------------------------- /vendor/signtool.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itchio/elevate/HEAD/vendor/signtool.exe -------------------------------------------------------------------------------- /resources/elevate.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itchio/elevate/HEAD/resources/elevate.ico -------------------------------------------------------------------------------- /scripts/ci-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # upload all artifacts from a single worker 4 | gsutil -m cp -r -a public-read binaries/* gs://dl.itch.ovh/elevate/ 5 | -------------------------------------------------------------------------------- /scripts/ci-sign.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | WIN_SIGN_KEY="itch corp." 4 | WIN_SIGN_URL="http://timestamp.comodoca.com/" 5 | 6 | vendor/signtool.exe sign //v //s MY //n "$WIN_SIGN_KEY" //fd sha256 //tr "$WIN_SIGN_URL?td=sha256" //td sha256 "$1" 7 | -------------------------------------------------------------------------------- /scripts/ci-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -xe 2 | 3 | if [ "$CI_ARCH" = "amd64" ]; then 4 | export PATH=/mingw64/bin:$PATH 5 | else 6 | export PATH=/mingw32/bin:$PATH 7 | fi 8 | 9 | 7za | head -2 10 | gcc -v 11 | cppcheck --error-exitcode=1 src 12 | 13 | export ELEVATE_VERSION=head 14 | if [ -n "$CI_BUILD_TAG" ]; then 15 | export ELEVATE_VERSIOn=$CI_BUILD_TAG 16 | fi 17 | export ELEVATE_CFLAGS="-DELEVATE_VERSION=\\\"$ELEVATE_VERSION\\\"" 18 | 19 | make 20 | file elevate.exe 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS:=-std=gnu99 -Wall -Os 3 | LDFLAGS:=-luserenv 4 | GCC:=gcc 5 | WINDRES:=windres 6 | STRIP:=strip 7 | 8 | ELEVATE_CFLAGS?=-DELEVATE_VERSION=\"head\" 9 | 10 | ifneq (${TRIPLET},) 11 | GCC:=${TRIPLET}-${GCC} 12 | WINDRES:=${TRIPLET}-${WINDRES} 13 | STRIP:=${TRIPLET}-${STRIP} 14 | endif 15 | 16 | all: 17 | ${WINDRES} resources/elevate.rc -O coff -o elevate.res 18 | ${GCC} ${CFLAGS} ${ELEVATE_CFLAGS} -static src/elevate.c elevate.res -o elevate.exe ${LDFLAGS} 19 | ${STRIP} elevate.exe 20 | -------------------------------------------------------------------------------- /resources/elevate.rc: -------------------------------------------------------------------------------- 1 | id ICON "elevate.ico" 2 | 3 | 1 VERSIONINFO 4 | FILEVERSION 1,0,0,0 5 | PRODUCTVERSION 1,0,0,0 6 | BEGIN 7 | BLOCK "StringFileInfo" 8 | BEGIN 9 | BLOCK "080904E4" 10 | BEGIN 11 | VALUE "CompanyName", "itch corp." 12 | VALUE "FileDescription", "Administrative helper for itch" 13 | VALUE "FileVersion", "1.0" 14 | VALUE "InternalName", "elevate" 15 | VALUE "LegalCopyright", "itch corp." 16 | VALUE "OriginalFilename", "elevate.exe" 17 | VALUE "ProductName", "elevate" 18 | VALUE "ProductVersion", "1.0" 19 | END 20 | END 21 | 22 | BLOCK "VarFileInfo" 23 | BEGIN 24 | VALUE "Translation", 0x809, 1252 25 | END 26 | END 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # elevate 3 | 4 | ![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg) 5 | [![Build Status](https://git.itch.ovh/itchio/elevate/badges/master/build.svg)](https://git.itch.ovh/itchio/elevate/builds) 6 | [![cppcheck badge](https://img.shields.io/badge/cppcheck-vigilant-ff69b4.svg)](https://github.com/itchio/elevate/blob/master/scripts/ci-test.sh) 7 | 8 | Simple command-line tool to run executables that require elevated 9 | privileges on Windows. 10 | 11 | Think [node-runas][] but as a standalone tool, or [jpassing/elevate][] 12 | but that compiles on MinGW and is continuously getting built by our CI server. 13 | 14 | [node-runas]: https://github.com/atom/node-runas 15 | [jpassing/elevate]: https://github.com/jpassing/elevate 16 | 17 | Used by [itch][] to run some installers on 18 | Windows. 19 | 20 | [itch]: https://github.com/itchio/itch 21 | 22 | ### License 23 | 24 | elevate is released under the MIT license, see `LICENSE.txt` for details 25 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | 2 | stages: 3 | - test 4 | - build 5 | - deploy 6 | 7 | test:windows:386: 8 | stage: test 9 | tags: 10 | - windows 11 | script: 12 | - export CI_ARCH=386 13 | - scripts/ci-test.sh 14 | 15 | test:windows:amd64: 16 | stage: test 17 | tags: 18 | - windows 19 | script: 20 | - export CI_ARCH=amd64 21 | - scripts/ci-test.sh 22 | 23 | build:windows:386: 24 | stage: build 25 | tags: 26 | - windows 27 | script: 28 | - export CI_ARCH=386 29 | - scripts/ci-build.sh 30 | artifacts: 31 | paths: 32 | - binaries 33 | 34 | build:windows:amd64: 35 | stage: build 36 | tags: 37 | - windows 38 | script: 39 | - export CI_ARCH=amd64 40 | - scripts/ci-build.sh 41 | artifacts: 42 | paths: 43 | - binaries 44 | 45 | deploy: 46 | stage: deploy 47 | tags: 48 | - dropsy 49 | script: 50 | - scripts/ci-deploy.sh 51 | dependencies: 52 | - :build:windows:386 53 | - :build:windows:amd64 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT LICENSE 2 | 3 | Copyright (c) 2016 Amos Wenger, https://itch.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /scripts/ci-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -xe 2 | 3 | if [ "$CI_ARCH" = "amd64" ]; then 4 | export PATH=/mingw64/bin:$PATH 5 | else 6 | export PATH=/mingw32/bin:$PATH 7 | fi 8 | 9 | 7za | head -2 10 | gcc -v 11 | cppcheck --error-exitcode=1 src 12 | 13 | export ELEVATE_VERSION=head 14 | if [ -n "$CI_BUILD_TAG" ]; then 15 | export ELEVATE_VERSION=$CI_BUILD_TAG 16 | fi 17 | export CI_VERSION=$CI_BUILD_REF_NAME 18 | export ELEVATE_CFLAGS="-DELEVATE_VERSION=\\\"$ELEVATE_VERSION\\\"" 19 | 20 | make 21 | file elevate.exe 22 | 23 | export CI_OS="windows" 24 | 25 | # sign (win) 26 | if [ "$CI_OS" = "windows" ]; then 27 | scripts/ci-sign.sh "elevate.exe" 28 | fi 29 | 30 | # verify 31 | 7za a elevate.7z elevate.exe 32 | 33 | # set up a file hierarchy that ibrew can consume, ie: 34 | # 35 | # - dl.itch.ovh 36 | # - elevate 37 | # - windows-amd64 38 | # - LATEST 39 | # - v0.3.0 40 | # - elevate.7z 41 | # - elevate.exe 42 | # - SHA1SUMS 43 | 44 | BINARIES_DIR="binaries/$CI_OS-$CI_ARCH" 45 | mkdir -p $BINARIES_DIR/$CI_VERSION 46 | mv elevate.7z $BINARIES_DIR/$CI_VERSION 47 | mv elevate.exe $BINARIES_DIR/$CI_VERSION 48 | 49 | (cd $BINARIES_DIR/$CI_VERSION && sha1sum * > SHA1SUMS && sha256sum * > SHA256SUMS) 50 | 51 | if [ -n "$CI_BUILD_TAG" ]; then 52 | echo $CI_BUILD_TAG > $BINARIES_DIR/LATEST 53 | fi 54 | -------------------------------------------------------------------------------- /src/elevate.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef SEE_MASK_NOASYNC 9 | #define SEE_MASK_NOASYNC 0x00000100 10 | #endif 11 | 12 | #define SAFE_APPEND(fmt, arg) \ 13 | { \ 14 | int written = snprintf(tmp, MAX_ARGUMENTS_LENGTH, (fmt), parameters, (arg)); \ 15 | if (written < 0 || written >= MAX_ARGUMENTS_LENGTH) { \ 16 | bail(1, "argument string too long"); \ 17 | } \ 18 | \ 19 | strncpy(parameters, tmp, MAX_ARGUMENTS_LENGTH); \ 20 | } 21 | 22 | // cf. https://www.msigeek.com/442/windows-os-version-numbers - Vista is 6.0 23 | #define VISTA_MAJOR_VERSION 6 24 | 25 | static void bail(int code, char *msg) { 26 | fprintf(stderr, "%s\n", msg); 27 | exit(code); 28 | } 29 | 30 | static void wbail(int code, char *msg) { 31 | LPVOID lpvMessageBuffer; 32 | 33 | FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 34 | FORMAT_MESSAGE_FROM_SYSTEM, 35 | NULL, GetLastError(), 36 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 37 | (LPWSTR)&lpvMessageBuffer, 0, NULL); 38 | 39 | printf("API = %s.\n", msg); 40 | wprintf(L"error code = %d.\n", GetLastError()); 41 | wprintf(L"message = %s.\n", (LPWSTR)lpvMessageBuffer); 42 | 43 | LocalFree(lpvMessageBuffer); 44 | 45 | fprintf(stderr, "%s\n", msg); 46 | exit(code); 47 | } 48 | 49 | static void ebail(int code, char *msg, HRESULT err) { 50 | LPVOID lpvMessageBuffer; 51 | 52 | FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | 53 | FORMAT_MESSAGE_FROM_SYSTEM, 54 | NULL, err, 55 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 56 | (LPWSTR)&lpvMessageBuffer, 0, NULL); 57 | 58 | printf("API = %s.\n", msg); 59 | wprintf(L"error code = %d.\n", err); 60 | wprintf(L"message = %s.\n", (LPWSTR)lpvMessageBuffer); 61 | 62 | LocalFree(lpvMessageBuffer); 63 | 64 | fprintf(stderr, "%s\n", msg); 65 | exit(code); 66 | } 67 | 68 | 69 | static int getMajorOSVersion() { 70 | OSVERSIONINFO ovi; 71 | 72 | ZeroMemory(&ovi, sizeof(OSVERSIONINFO)); 73 | ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 74 | GetVersionEx(&ovi); 75 | 76 | return ovi.dwMajorVersion; 77 | } 78 | 79 | int exec(const char *verb, const char *file, const char *parameters) { 80 | SHELLEXECUTEINFO sei; 81 | 82 | fprintf(stderr, "[elevate] %s %s %s\n", verb, file, parameters); 83 | fflush(stderr); 84 | 85 | ZeroMemory(&sei, sizeof(SHELLEXECUTEINFO)); 86 | sei.cbSize = sizeof(SHELLEXECUTEINFO); 87 | // we want to wait until the program exits to exit with its code 88 | sei.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS; 89 | sei.lpVerb = verb; 90 | sei.lpFile = file; 91 | sei.lpParameters = parameters; 92 | sei.nShow = SW_HIDE; 93 | 94 | if (ShellExecuteEx(&sei) == FALSE || sei.hProcess == NULL) { 95 | // This happens if user denied permission in UAC dialog, for example 96 | bail(127, "failed ShellExecuteEx call / null hProcess"); 97 | } 98 | 99 | WaitForSingleObject(sei.hProcess, INFINITE); 100 | 101 | DWORD code; 102 | if (GetExitCodeProcess(sei.hProcess, &code) == 0) { 103 | // Not sure when this could ever happen. 104 | bail(127, "failed GetExitCodeProcess call"); 105 | } 106 | 107 | return code; 108 | } 109 | 110 | int elevate(int argc, char** argv) { 111 | const int MAX_ARGUMENTS_LENGTH = 65536; 112 | char parameters[MAX_ARGUMENTS_LENGTH]; 113 | char tmp[MAX_ARGUMENTS_LENGTH]; 114 | 115 | parameters[0] = '\0'; 116 | 117 | for (int i = 2; i < argc; i++) { 118 | SAFE_APPEND("%s%s ", argv[i]) 119 | } 120 | 121 | fprintf(stderr, "[elevate] %s %s\n", argv[1], parameters); 122 | fflush(stderr); 123 | 124 | return exec("runas", argv[1], parameters); 125 | } 126 | 127 | void msibail() { 128 | bail(1, "Usage: elevate --msiexec [--install/--uninstall] msiPath installPath logPath"); 129 | } 130 | 131 | int msiexec(int argc, char** argv) { 132 | const int MAX_ARGUMENTS_LENGTH = 65536; 133 | char parameters[MAX_ARGUMENTS_LENGTH]; 134 | char tmp[MAX_ARGUMENTS_LENGTH]; 135 | 136 | parameters[0] = '\0'; 137 | 138 | if (argc < 6) { 139 | fprintf(stderr, "Not enough arguments, expected 6, got %d\n", argc); 140 | msibail(); 141 | } 142 | 143 | const char* command = argv[2]; 144 | const char* msiPath = argv[3]; 145 | const char* installPath = argv[4]; 146 | const char* logPath = argv[5]; 147 | 148 | SAFE_APPEND("%s%s ", "ALLUSERS=2"); 149 | SAFE_APPEND("%s%s ", "MSIINSTALLPERUSER=1"); 150 | SAFE_APPEND("%sTARGETDIR=\"%s\" ", installPath); 151 | SAFE_APPEND("%sINSTALLDIR=\"%s\" ", installPath); 152 | SAFE_APPEND("%sAPPDIR=\"%s\" ", installPath); 153 | 154 | SAFE_APPEND("%s%s ", "/norestart"); 155 | SAFE_APPEND("%s%s ", "/quiet"); 156 | SAFE_APPEND("%s%s ", "/l*v"); 157 | SAFE_APPEND("%s%s ", logPath); 158 | 159 | char *verb = "open"; 160 | if (strcmp(command, "--install") == 0) { 161 | SAFE_APPEND("%s%s ", "/i"); 162 | } else if (strcmp(command, "--uninstall") == 0) { 163 | SAFE_APPEND("%s%s ", "/x"); 164 | } else if (strcmp(command, "--elevated-install") == 0) { 165 | SAFE_APPEND("%s%s ", "/i"); 166 | verb = "runas"; 167 | } else if (strcmp(command, "--elevated-uninstall") == 0) { 168 | SAFE_APPEND("%s%s ", "/x"); 169 | verb = "runas"; 170 | } else { 171 | fprintf(stderr, "Command must be --install or --uninstall, got %s\n", command); 172 | msibail(); 173 | } 174 | 175 | SAFE_APPEND("%s%s ", msiPath); 176 | 177 | return exec(verb, "msiexec", parameters); 178 | } 179 | 180 | void toWideChar (const char *s, wchar_t **ws) { 181 | int wchars_num = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0); 182 | *ws = malloc(wchars_num * sizeof(wchar_t)); 183 | MultiByteToWideChar(CP_UTF8, 0, s, -1, *ws, wchars_num); 184 | } 185 | 186 | int runas(int argc, char** argv) { 187 | const int MAX_ARGUMENTS_LENGTH = 65536; 188 | char parameters[MAX_ARGUMENTS_LENGTH]; 189 | char tmp[MAX_ARGUMENTS_LENGTH]; 190 | 191 | parameters[0] = '\0'; 192 | 193 | if (argc < 5) { 194 | fprintf(stderr, "Not enough arguments, expected 6, got %d\n", argc); 195 | msibail(); 196 | } 197 | 198 | const char* user = argv[2]; 199 | const char* password = argv[3]; 200 | const char* command = argv[4]; 201 | 202 | for (int i = 4; i < argc; i++) { 203 | SAFE_APPEND("%s\"%s\" ", argv[i]); 204 | } 205 | 206 | WCHAR* wuser; 207 | WCHAR* wpassword; 208 | WCHAR* wcommand; 209 | WCHAR* wparameters; 210 | toWideChar(user, &wuser); 211 | toWideChar(password, &wpassword); 212 | toWideChar(command, &wcommand); 213 | toWideChar(parameters, &wparameters); 214 | 215 | HANDLE hToken; 216 | LPVOID lpvEnv; 217 | 218 | PROCESS_INFORMATION pi; 219 | ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); 220 | 221 | STARTUPINFOW si; 222 | ZeroMemory(&si, sizeof(STARTUPINFOW)); 223 | si.cb = sizeof(STARTUPINFOW); 224 | 225 | if (!LogonUserW(wuser, L".", wpassword, 226 | LOGON32_LOGON_INTERACTIVE, 227 | LOGON32_PROVIDER_DEFAULT, 228 | &hToken)) { 229 | wbail(127, "LogonUser"); 230 | } 231 | 232 | if (!CreateEnvironmentBlock(&lpvEnv, hToken, TRUE)) { 233 | CloseHandle(hToken); 234 | wbail(127, "CreateEnvironmentBlock"); 235 | } 236 | 237 | PROFILEINFOW profileInfo; 238 | ZeroMemory(&profileInfo, sizeof(profileInfo)); 239 | profileInfo.dwSize = sizeof(profileInfo); 240 | profileInfo.lpUserName = L"itch-player"; 241 | 242 | DWORD shBufSize = 2048; 243 | wchar_t *shBuf = malloc(sizeof(wchar_t) * shBufSize); 244 | 245 | if (!ImpersonateLoggedOnUser(hToken)) { 246 | CloseHandle(hToken); 247 | wbail(127, "ImpersonateLoggedOnUser"); 248 | } 249 | 250 | HRESULT shRes = SHGetFolderPathW(NULL, CSIDL_PROFILE | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, shBuf); 251 | if (FAILED(shRes)) { 252 | wprintf(L"SHGetFolderPath failed: error %d\n", shRes); 253 | 254 | if (!RevertToSelf()) { 255 | CloseHandle(hToken); 256 | wbail(127, "ImpersonateLoggedOnUser"); 257 | } 258 | 259 | 260 | wchar_t *cmdLine = wcsdup(L"cmd.exe /c whoami"); 261 | if (!CreateProcessWithLogonW(wuser, L".", wpassword, 262 | LOGON_WITH_PROFILE, L"cmd.exe", cmdLine, 263 | CREATE_UNICODE_ENVIRONMENT, 264 | lpvEnv, 265 | NULL, 266 | &si, &pi)) { 267 | wbail(127, "CreateProcessWithLogonW"); 268 | } 269 | free(cmdLine); 270 | 271 | WaitForSingleObject(pi.hProcess, INFINITE); 272 | 273 | CloseHandle(pi.hProcess); 274 | CloseHandle(pi.hThread); 275 | 276 | if (!ImpersonateLoggedOnUser(hToken)) { 277 | CloseHandle(hToken); 278 | wbail(127, "ImpersonateLoggedOnUser"); 279 | } 280 | 281 | shRes = SHGetFolderPathW(NULL, CSIDL_PROFILE | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, shBuf); 282 | if (FAILED(shRes)) { 283 | CloseHandle(hToken); 284 | ebail(127, "SHGetFolderPathW (1)", shRes); 285 | } 286 | 287 | } 288 | wprintf(L"User profile dir = %s\n", shBuf); 289 | 290 | if (!RevertToSelf()) { 291 | CloseHandle(hToken); 292 | wbail(127, "ImpersonateLoggedOnUser"); 293 | } 294 | 295 | CloseHandle(hToken); 296 | 297 | DWORD envLen; 298 | wchar_t *ptr = lpvEnv; 299 | while (1) { 300 | #ifdef DEBUG 301 | printf(L"[ENV] %s\n", ptr); 302 | #endif 303 | while ((*ptr) != '\0') { 304 | ptr++; 305 | } 306 | ptr++; 307 | 308 | if (*ptr == '\0') { 309 | envLen = (DWORD) ((LPVOID) ptr - (LPVOID) lpvEnv); 310 | wprintf(L"Total environment length: %d\n", envLen); 311 | break; 312 | } 313 | } 314 | 315 | wchar_t *envAddition = L"USERPROFILE="; 316 | wchar_t *terminator = L"\0"; 317 | 318 | DWORD envBufSize = envLen + (sizeof(wchar_t) * (wcslen(envAddition) + wcslen(shBuf) + 1)); 319 | wprintf(L"Environment buffer size: %d\n", envBufSize); 320 | 321 | LPVOID lpvManipEnv = malloc(sizeof(wchar_t) * envBufSize); 322 | ZeroMemory(lpvManipEnv, sizeof(wchar_t) * envBufSize); 323 | 324 | BYTE *currentManipEnvSz = lpvManipEnv; 325 | 326 | memcpy(currentManipEnvSz, envAddition, sizeof(wchar_t) * (wcslen(envAddition))); 327 | currentManipEnvSz += sizeof(wchar_t) * (wcslen(envAddition)); 328 | 329 | memcpy(currentManipEnvSz, shBuf, sizeof(wchar_t) * (wcslen(shBuf))); 330 | currentManipEnvSz += sizeof(wchar_t) * (wcslen(shBuf)); 331 | free(shBuf); 332 | 333 | memcpy(currentManipEnvSz, terminator, sizeof(wchar_t)); 334 | currentManipEnvSz += sizeof(wchar_t) * 1; 335 | 336 | memcpy(currentManipEnvSz, lpvEnv, envLen); 337 | currentManipEnvSz += envLen; 338 | 339 | if (!DestroyEnvironmentBlock(lpvEnv)) { 340 | wbail(127, "failed DestroyEnvironmentBlock call"); 341 | } 342 | 343 | memcpy(currentManipEnvSz, terminator, sizeof(wchar_t)); 344 | 345 | ptr = lpvManipEnv; 346 | while (1) { 347 | #ifdef DEBUG 348 | wprintf(L"[MENV] %s\n", ptr); 349 | #endif 350 | while ((*ptr) != '\0') { 351 | ptr++; 352 | } 353 | ptr++; 354 | 355 | if (*ptr == '\0') { 356 | break; 357 | } 358 | } 359 | 360 | wchar_t *ExePath; 361 | toWideChar(argv[4], &ExePath); 362 | wprintf(L"ExePath = '%s'\n", ExePath); 363 | 364 | wchar_t *Drive = malloc(sizeof(wchar_t) * MAX_PATH); 365 | Drive[0] = (wchar_t) '\0'; 366 | 367 | wchar_t *DirName = malloc(sizeof(wchar_t) * MAX_PATH); 368 | DirName[0] = (wchar_t) '\0'; 369 | 370 | wchar_t *DirPath = malloc(sizeof(wchar_t) * MAX_PATH); 371 | DirPath[0] = (wchar_t) '\0'; 372 | 373 | _wsplitpath(ExePath, Drive, DirName, NULL, NULL); 374 | 375 | if (wcslen(DirName) == 0) { 376 | GetCurrentDirectoryW(MAX_PATH, DirPath); 377 | } else { 378 | wsprintfW(DirPath, L"%s%s", Drive, DirName); 379 | } 380 | 381 | int LenDirPath = wcslen(DirPath); 382 | if (DirPath[LenDirPath - 1] == '\\') { 383 | DirPath[LenDirPath - 1] = '\0'; 384 | } 385 | 386 | wprintf(L"ExePath = '%s'\n", ExePath); 387 | wprintf(L"DirPath = '%s'\n", DirPath); 388 | 389 | if (!CreateProcessWithLogonW(wuser, L".", wpassword, 390 | LOGON_WITH_PROFILE, wcommand, wparameters, 391 | CREATE_UNICODE_ENVIRONMENT, 392 | lpvManipEnv, 393 | DirPath, 394 | &si, &pi)) { 395 | wbail(127, "CreateProcessWithLogonW"); 396 | } 397 | 398 | WaitForSingleObject(pi.hProcess, INFINITE); 399 | 400 | DWORD code; 401 | if (GetExitCodeProcess(pi.hProcess, &code) == 0) { 402 | // Not sure when this could ever happen. 403 | wbail(127, "failed GetExitCodeProcess call"); 404 | } 405 | 406 | CloseHandle(pi.hProcess); 407 | CloseHandle(pi.hThread); 408 | 409 | return code; 410 | } 411 | 412 | int printUsersSid () { 413 | DWORD arbitrarySize = 2048; 414 | 415 | DWORD sidSize = arbitrarySize; 416 | PSID sid = malloc(sidSize); 417 | memset(sid, 0, sidSize); 418 | 419 | int ret = CreateWellKnownSid(WinBuiltinUsersSid, NULL, sid, &sidSize); 420 | if (!ret) { 421 | wbail(127, "failed CreateWellKnownSid call"); 422 | } 423 | 424 | DWORD cchName = arbitrarySize; 425 | wchar_t *lpName = malloc(sizeof(wchar_t) * cchName); 426 | 427 | DWORD cchReferencedDomainName = arbitrarySize; 428 | wchar_t *lpReferencedDomainName = malloc(sizeof(wchar_t) * cchReferencedDomainName); 429 | 430 | SID_NAME_USE sidUse; 431 | 432 | ret = LookupAccountSidW(NULL, sid, lpName, &cchName, lpReferencedDomainName, &cchReferencedDomainName, &sidUse); 433 | if (!ret) { 434 | wbail(127, "failed LookupAccountSid call"); 435 | } 436 | 437 | wprintf(L"%s\n", lpName); 438 | 439 | return 0; 440 | } 441 | 442 | /** 443 | * Inspired by / with some code from the following MIT-licensed projects: 444 | * - https://github.com/atom/node-runas 445 | * - https://github.com/jpassing/elevate 446 | */ 447 | int main(int argc, char** argv) { 448 | setvbuf(stdout, NULL, _IONBF, BUFSIZ); 449 | 450 | if (getMajorOSVersion() < VISTA_MAJOR_VERSION) { 451 | bail(1, "elevate requires Windows Vista or above"); 452 | } 453 | 454 | if (argc < 2) { 455 | bail(1, "Usage: elevate PROGRAM ARGS"); 456 | } 457 | 458 | if (strcmp("-V", argv[1]) == 0) { 459 | printf("%s\n", ELEVATE_VERSION); 460 | return 0; 461 | } 462 | 463 | if (strcmp("--msiexec", argv[1]) == 0) { 464 | return msiexec(argc, argv); 465 | } else if (strcmp("--runas", argv[1]) == 0) { 466 | return runas(argc, argv); 467 | } else if (strcmp("--print-users-sid", argv[1]) == 0) { 468 | return printUsersSid(argc, argv); 469 | } else { 470 | return elevate(argc, argv); 471 | } 472 | } 473 | --------------------------------------------------------------------------------