VirtualBox

source: vbox/trunk/src/VBox/Main/src-helper-apps/VBoxExtPackHelperApp.cpp@ 35691

Last change on this file since 35691 was 35542, checked in by vboxsync, 14 years ago

VBoxExtPackHelperApp.cpp: Fixed issue with non-ascii characters in the argument list on windows that prevent installing extension packs residing in directory with non-ascii names.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 68.9 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 35542 2011-01-13 15:42:16Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "../include/ExtPackUtil.h"
23
24#include <iprt/buildconfig.h>
25#include <iprt/dir.h>
26#include <iprt/env.h>
27#include <iprt/file.h>
28#include <iprt/fs.h>
29#include <iprt/getopt.h>
30#include <iprt/initterm.h>
31#include <iprt/manifest.h>
32#include <iprt/message.h>
33#include <iprt/param.h>
34#include <iprt/path.h>
35#include <iprt/process.h>
36#include <iprt/string.h>
37#include <iprt/stream.h>
38#include <iprt/vfs.h>
39#include <iprt/zip.h>
40#include <iprt/cpp/ministring.h>
41
42#include <VBox/log.h>
43#include <VBox/err.h>
44#include <VBox/sup.h>
45#include <VBox/version.h>
46
47#ifdef RT_OS_WINDOWS
48# define _WIN32_WINNT 0x0501
49# include <Objbase.h> /* CoInitializeEx */
50# include <Windows.h> /* ShellExecuteEx, ++ */
51# ifdef DEBUG
52# include <Sddl.h>
53# endif
54#endif
55
56#ifdef RT_OS_DARWIN
57# include <Security/Authorization.h>
58# include <Security/AuthorizationTags.h>
59# include <CoreFoundation/CoreFoundation.h>
60#endif
61
62#if !defined(RT_OS_OS2)
63# include <stdio.h>
64# include <errno.h>
65# if !defined(RT_OS_WINDOWS)
66# include <sys/types.h>
67# include <unistd.h> /* geteuid */
68# endif
69#endif
70
71
72/*******************************************************************************
73* Defined Constants And Macros *
74*******************************************************************************/
75/** Enable elevation on Windows and Darwin. */
76#if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING)
77# define WITH_ELEVATION
78#endif
79
80
81/** @name Command and option names
82 * @{ */
83#define CMD_INSTALL 1000
84#define CMD_UNINSTALL 1001
85#define CMD_CLEANUP 1002
86#ifdef WITH_ELEVATION
87# define OPT_ELEVATED 1090
88# define OPT_STDOUT 1091
89# define OPT_STDERR 1092
90#endif
91#define OPT_DISP_INFO_HACK 1093
92/** @} */
93
94
95/*******************************************************************************
96* Global Variables *
97*******************************************************************************/
98#ifdef RT_OS_WINDOWS
99static HINSTANCE g_hInstance;
100#endif
101
102#ifdef IN_RT_R3
103/* Override RTAssertShouldPanic to prevent gdb process creation. */
104RTDECL(bool) RTAssertShouldPanic(void)
105{
106 return true;
107}
108#endif
109
110
111
112/**
113 * Handle the special standard options when these are specified after the
114 * command.
115 *
116 * @param ch The option character.
117 */
118static RTEXITCODE DoStandardOption(int ch)
119{
120 switch (ch)
121 {
122 case 'h':
123 {
124 RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n"
125 "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n"
126 "All rights reserved.\n"
127 "\n"
128 "This NOT intended for general use, please use VBoxManage instead\n"
129 "or call the IExtPackManager API directly.\n"
130 "\n"
131 "Usage: %s <command> [options]\n"
132 "Commands:\n"
133 " install --base-dir <dir> --cert-dir <dir> --name <name> \\\n"
134 " --tarball <tarball> --tarball-fd <fd>\n"
135 " uninstall --base-dir <dir> --name <name>\n"
136 " cleanup --base-dir <dir>\n"
137 , RTProcShortName());
138 return RTEXITCODE_SUCCESS;
139 }
140
141 case 'V':
142 RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision());
143 return RTEXITCODE_SUCCESS;
144
145 default:
146 AssertFailedReturn(RTEXITCODE_FAILURE);
147 }
148}
149
150
151/**
152 * Checks if the cerficiate directory is valid.
153 *
154 * @returns true if it is valid, false if it isn't.
155 * @param pszCertDir The certificate directory to validate.
156 */
157static bool IsValidCertificateDir(const char *pszCertDir)
158{
159 /*
160 * Just be darn strict for now.
161 */
162 char szCorrect[RTPATH_MAX];
163 int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect));
164 if (RT_FAILURE(rc))
165 return false;
166 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR);
167 if (RT_FAILURE(rc))
168 return false;
169
170 return RTPathCompare(szCorrect, pszCertDir) == 0;
171}
172
173
174/**
175 * Checks if the base directory is valid.
176 *
177 * @returns true if it is valid, false if it isn't.
178 * @param pszBaesDir The base directory to validate.
179 */
180static bool IsValidBaseDir(const char *pszBaseDir)
181{
182 /*
183 * Just be darn strict for now.
184 */
185 char szCorrect[RTPATH_MAX];
186 int rc = RTPathAppPrivateArchTop(szCorrect, sizeof(szCorrect));
187 if (RT_FAILURE(rc))
188 return false;
189 rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR);
190 if (RT_FAILURE(rc))
191 return false;
192
193 return RTPathCompare(szCorrect, pszBaseDir) == 0;
194}
195
196
197/**
198 * Cleans up a temporary extension pack directory.
199 *
200 * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'.
201 *
202 * @returns The program exit code.
203 * @param pszDir The directory to clean up. The caller is
204 * responsible for making sure this is valid.
205 * @param fTemporary Whether this is a temporary install directory or
206 * not.
207 */
208static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary)
209{
210 /** @todo May have to undo 555 modes here later. */
211 int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR);
212 if (RT_FAILURE(rc))
213 return RTMsgErrorExit(RTEXITCODE_FAILURE,
214 "Failed to delete the %sextension pack directory: %Rrc ('%s')",
215 fTemporary ? "temporary " : "", rc, pszDir);
216 return RTEXITCODE_SUCCESS;
217}
218
219
220/**
221 * Common uninstall worker used by both uninstall and install --replace.
222 *
223 * @returns success or failure, message displayed on failure.
224 * @param pszExtPackDir The extension pack directory name.
225 */
226static RTEXITCODE CommonUninstallWorker(const char *pszExtPackDir)
227{
228 /* Rename the extension pack directory before deleting it to prevent new
229 VM processes from picking it up. */
230 char szExtPackUnInstDir[RTPATH_MAX];
231 int rc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir);
232 if (RT_SUCCESS(rc))
233 rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst");
234 if (RT_FAILURE(rc))
235 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc);
236
237 rc = RTDirRename(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE);
238 if (RT_FAILURE(rc))
239 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc);
240
241 /* Recursively delete the directory content. */
242 return RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/);
243}
244
245
246/**
247 * Wrapper around VBoxExtPackOpenTarFss.
248 *
249 * @returns success or failure, message displayed on failure.
250 * @param hTarballFile The handle to the tarball file.
251 * @param phTarFss Where to return the filesystem stream handle.
252 */
253static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss)
254{
255 char szError[8192];
256 int rc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss);
257 if (RT_FAILURE(rc))
258 {
259 Assert(szError[0]);
260 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
261 }
262 Assert(!szError[0]);
263 return RTEXITCODE_SUCCESS;
264}
265
266
267/**
268 * Sets the permissions of the temporary extension pack directory just before
269 * renaming it.
270 *
271 * By default the temporary directory is only accessible by root, this function
272 * will make it world readable and browseable.
273 *
274 * @returns The program exit code.
275 * @param pszDir The temporary extension pack directory.
276 */
277static RTEXITCODE SetExtPackPermissions(const char *pszDir)
278{
279 RTMsgInfo("Setting permissions...");
280#if !defined(RT_OS_WINDOWS)
281 int rc = RTPathSetMode(pszDir, 0755);
282 if (RT_FAILURE(rc))
283 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir);
284#else
285 /** @todo */
286#endif
287
288 return RTEXITCODE_SUCCESS;
289}
290
291
292/**
293 * Wrapper around VBoxExtPackValidateMember.
294 *
295 * @returns Program exit code, failure with message.
296 * @param pszName The name of the directory.
297 * @param enmType The object type.
298 * @param hVfsObj The VFS object.
299 */
300static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj)
301{
302 char szError[8192];
303 int rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError));
304 if (RT_FAILURE(rc))
305 {
306 Assert(szError[0]);
307 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
308 }
309 Assert(!szError[0]);
310 return RTEXITCODE_SUCCESS;
311}
312
313
314/**
315 * Validates the extension pack tarball prior to unpacking.
316 *
317 * Operations performed:
318 * - Hardening checks.
319 *
320 * @returns The program exit code.
321 * @param pszDir The directory where the extension pack has been
322 * unpacked.
323 * @param pszExtPackName The expected extension pack name.
324 * @param pszTarball The name of the tarball in case we have to
325 * complain about something.
326 */
327static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName)
328{
329 RTMsgInfo("Validating unpacked extension pack...");
330
331 RTERRINFOSTATIC ErrInfo;
332 RTErrInfoInitStatic(&ErrInfo);
333 int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core);
334 if (RT_FAILURE(rc))
335 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, ErrInfo.Core.pszMsg);
336 return RTEXITCODE_SUCCESS;
337}
338
339
340/**
341 * Unpacks a directory from an extension pack tarball.
342 *
343 * @returns Program exit code, failure with message.
344 * @param pszDstDirName The name of the unpacked directory.
345 * @param hVfsObj The source object for the directory.
346 */
347static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj)
348{
349 int rc = RTDirCreate(pszDstDirName, 0755);
350 if (RT_FAILURE(rc))
351 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc);
352 /** @todo Ownership tricks on windows? */
353 return RTEXITCODE_SUCCESS;
354}
355
356
357/**
358 * Unpacks a file from an extension pack tarball.
359 *
360 * @returns Program exit code, failure with message.
361 * @param pszName The name in the tarball.
362 * @param pszDstFilename The name of the unpacked file.
363 * @param hVfsIosSrc The source stream for the file.
364 * @param hUnpackManifest The manifest to add the file digest to.
365 */
366static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename,
367 RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest)
368{
369 /*
370 * Query the object info, we'll need it for buffer sizing as well as
371 * setting the file mode.
372 */
373 RTFSOBJINFO ObjInfo;
374 int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING);
375 if (RT_FAILURE(rc))
376 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename);
377
378 /*
379 * Create the file.
380 */
381 uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT);
382 RTFILE hFile;
383 rc = RTFileOpen(&hFile, pszDstFilename, fFlags);
384 if (RT_FAILURE(rc))
385 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc);
386
387 /*
388 * Create a I/O stream for the destination file, stack a manifest entry
389 * creator on top of it.
390 */
391 RTVFSIOSTREAM hVfsIosDst2;
392 rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2);
393 if (RT_SUCCESS(rc))
394 {
395 RTVFSIOSTREAM hVfsIosDst;
396 rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName,
397 RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256,
398 false /*fReadOrWrite*/, &hVfsIosDst);
399 RTVfsIoStrmRelease(hVfsIosDst2);
400 if (RT_SUCCESS(rc))
401 {
402 /*
403 * Pump the data thru.
404 */
405 rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G));
406 if (RT_SUCCESS(rc))
407 {
408 rc = RTManifestPtIosAddEntryNow(hVfsIosDst);
409 if (RT_SUCCESS(rc))
410 {
411 RTVfsIoStrmRelease(hVfsIosDst);
412 hVfsIosDst = NIL_RTVFSIOSTREAM;
413
414 /*
415 * Set the mode mask.
416 */
417 ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP);
418 rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode);
419 /** @todo Windows needs to do more here, I think. */
420 if (RT_SUCCESS(rc))
421 {
422 RTFileClose(hFile);
423 return RTEXITCODE_SUCCESS;
424 }
425
426 RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc);
427 }
428 else
429 RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc);
430 }
431 else
432 RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc);
433 RTVfsIoStrmRelease(hVfsIosDst);
434 }
435 else
436 RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc);
437 }
438 else
439 RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc);
440 RTFileClose(hFile);
441 return RTEXITCODE_FAILURE;
442}
443
444
445/**
446 * Unpacks the extension pack into the specified directory.
447 *
448 * This will apply ownership and permission changes to all the content, the
449 * exception is @a pszDirDst which will be handled by SetExtPackPermissions.
450 *
451 * @returns The program exit code.
452 * @param hTarballFile The tarball to unpack.
453 * @param pszDirDst Where to unpack it.
454 * @param hValidManifest The manifest we've validated.
455 * @param pszTarball The name of the tarball in case we have to
456 * complain about something.
457 */
458static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest,
459 const char *pszTarball)
460{
461 RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst);
462
463 /*
464 * Set up the destination path.
465 */
466 char szDstPath[RTPATH_MAX];
467 int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2);
468 if (RT_FAILURE(rc))
469 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc);
470 size_t offDstPath = RTPathStripTrailingSlash(szDstPath);
471 szDstPath[offDstPath++] = '/';
472 szDstPath[offDstPath] = '\0';
473
474 /*
475 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
476 */
477 RTVFSFSSTREAM hTarFss;
478 RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss);
479 if (rcExit != RTEXITCODE_SUCCESS)
480 return rcExit;
481
482 RTMANIFEST hUnpackManifest;
483 rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest);
484 if (RT_SUCCESS(rc))
485 {
486 /*
487 * Process the tarball (would be nice to move this to a function).
488 */
489 for (;;)
490 {
491 /*
492 * Get the next stream object.
493 */
494 char *pszName;
495 RTVFSOBJ hVfsObj;
496 RTVFSOBJTYPE enmType;
497 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
498 if (RT_FAILURE(rc))
499 {
500 if (rc != VERR_EOF)
501 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc);
502 break;
503 }
504 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
505
506 /*
507 * Check the type & name validity then unpack it.
508 */
509 rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj);
510 if (rcExit == RTEXITCODE_SUCCESS)
511 {
512 szDstPath[offDstPath] = '\0';
513 rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName);
514 if (RT_SUCCESS(rc))
515 {
516 if ( enmType == RTVFSOBJTYPE_FILE
517 || enmType == RTVFSOBJTYPE_IO_STREAM)
518 {
519 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
520 rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest);
521 RTVfsIoStrmRelease(hVfsIos);
522 }
523 else if (*pszAdjName && strcmp(pszAdjName, "."))
524 rcExit = UnpackExtPackDir(szDstPath, hVfsObj);
525 }
526 else
527 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc);
528 }
529
530 /*
531 * Clean up and break out on failure.
532 */
533 RTVfsObjRelease(hVfsObj);
534 RTStrFree(pszName);
535 if (rcExit != RTEXITCODE_SUCCESS)
536 break;
537 }
538
539 /*
540 * Check that what we just extracted matches the already verified
541 * manifest.
542 */
543 if (rcExit == RTEXITCODE_SUCCESS)
544 {
545 char szError[RTPATH_MAX];
546 rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/,
547 0 /*fFlags*/, szError, sizeof(szError));
548 if (RT_SUCCESS(rc))
549 rc = RTEXITCODE_SUCCESS;
550 else if (rc == VERR_NOT_EQUAL && szError[0])
551 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError);
552 else
553 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc);
554 }
555#if 0
556 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
557 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
558 RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL);
559 RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut);
560 RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL);
561 RTManifestWriteStandard(hValidManifest, hVfsIosStdOut);
562#endif
563 RTManifestRelease(hUnpackManifest);
564 }
565 RTVfsFsStrmRelease(hTarFss);
566
567 return rcExit;
568}
569
570
571
572/**
573 * Wrapper around VBoxExtPackValidateTarball.
574 *
575 * @returns The program exit code.
576 * @param hTarballFile The handle to open the @a pszTarball file.
577 * @param pszExtPackName The name of the extension pack name.
578 * @param pszTarball The name of the tarball in case we have to
579 * complain about something.
580 * @param phValidManifest Where to return the handle to fully validated
581 * the manifest for the extension pack. This
582 * includes all files.
583 */
584static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
585 PRTMANIFEST phValidManifest)
586{
587 *phValidManifest = NIL_RTMANIFEST;
588 RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName);
589
590 char szError[8192];
591 int rc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball,
592 szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/);
593 if (RT_FAILURE(rc))
594 {
595 Assert(szError[0]);
596 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError);
597 }
598 Assert(!szError[0]);
599 return RTEXITCODE_SUCCESS;
600}
601
602
603/**
604 * The 2nd part of the installation process.
605 *
606 * @returns The program exit code.
607 * @param pszBaseDir The base directory.
608 * @param pszCertDir The certificat directory.
609 * @param pszTarball The tarball name.
610 * @param hTarballFile The handle to open the @a pszTarball file.
611 * @param hTarballFileOpt The tarball file handle (optional).
612 * @param pszName The extension pack name.
613 * @param pszMangledName The mangled extension pack name.
614 * @param fReplace Whether to replace any existing ext pack.
615 */
616static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball,
617 RTFILE hTarballFile, RTFILE hTarballFileOpt,
618 const char *pszName, const char *pszMangledName, bool fReplace)
619{
620 /*
621 * Do some basic validation of the tarball file.
622 */
623 RTFSOBJINFO ObjInfo;
624 int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
625 if (RT_FAILURE(rc))
626 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball);
627 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
628 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball);
629
630 if (hTarballFileOpt != NIL_RTFILE)
631 {
632 RTFSOBJINFO ObjInfo2;
633 rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX);
634 if (RT_FAILURE(rc))
635 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc);
636 if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice
637 || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId)
638 return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match");
639 }
640
641 /*
642 * Construct the paths to the two directories we'll be using.
643 */
644 char szFinalPath[RTPATH_MAX];
645 rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName);
646 if (RT_FAILURE(rc))
647 return RTMsgErrorExit(RTEXITCODE_FAILURE,
648 "Failed to construct the path to the final extension pack directory: %Rrc", rc);
649
650 char szTmpPath[RTPATH_MAX];
651 rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName);
652 if (RT_SUCCESS(rc))
653 {
654 size_t cchTmpPath = strlen(szTmpPath);
655 RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf());
656 }
657 if (RT_FAILURE(rc))
658 return RTMsgErrorExit(RTEXITCODE_FAILURE,
659 "Failed to construct the path to the temporary extension pack directory: %Rrc", rc);
660
661 /*
662 * Check that they don't exist at this point in time, unless fReplace=true.
663 */
664 rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
665 if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
666 {
667 if (!fReplace)
668 return RTMsgErrorExit(RTEXITCODE_FAILURE,
669 "The extension pack is already installed. You must uninstall the old one first.");
670 }
671 else if (RT_SUCCESS(rc))
672 return RTMsgErrorExit(RTEXITCODE_FAILURE,
673 "Found non-directory file system object where the extension pack would be installed ('%s')",
674 szFinalPath);
675 else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
676 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
677
678 rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
679 if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)
680 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath);
681
682 /*
683 * Create the temporary directory and prepare the extension pack within it.
684 * If all checks out correctly, rename it to the final directory.
685 */
686 RTDirCreate(pszBaseDir, 0755);
687 rc = RTDirCreate(szTmpPath, 0700);
688 if (RT_FAILURE(rc))
689 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath);
690
691 RTMANIFEST hValidManifest = NIL_RTMANIFEST;
692 RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, &hValidManifest);
693 if (rcExit == RTEXITCODE_SUCCESS)
694 rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball);
695 if (rcExit == RTEXITCODE_SUCCESS)
696 rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName);
697 if (rcExit == RTEXITCODE_SUCCESS)
698 rcExit = SetExtPackPermissions(szTmpPath);
699 RTManifestRelease(hValidManifest);
700
701 if (rcExit == RTEXITCODE_SUCCESS)
702 {
703 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
704 if ( RT_FAILURE(rc)
705 && fReplace
706 && RTDirExists(szFinalPath))
707 {
708 /* Automatic uninstall if --replace was given. */
709 rcExit = CommonUninstallWorker(szFinalPath);
710 if (rcExit == RTEXITCODE_SUCCESS)
711 rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE);
712 }
713 if (RT_SUCCESS(rc))
714 RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball);
715 else if (rcExit == RTEXITCODE_SUCCESS)
716 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE,
717 "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')",
718 rc, szTmpPath, szFinalPath);
719 }
720
721 /*
722 * Clean up the temporary directory on failure.
723 */
724 if (rcExit != RTEXITCODE_SUCCESS)
725 RemoveExtPackDir(szTmpPath, true /*fTemporary*/);
726
727 return rcExit;
728}
729
730
731/**
732 * Implements the 'install' command.
733 *
734 * @returns The program exit code.
735 * @param argc The number of program arguments.
736 * @param argv The program arguments.
737 */
738static RTEXITCODE DoInstall(int argc, char **argv)
739{
740 /*
741 * Parse the parameters.
742 *
743 * Note! The --base-dir and --cert-dir are only for checking that the
744 * caller and this help applications have the same idea of where
745 * things are. Likewise, the --name is for verifying assumptions
746 * the caller made about the name. The optional --tarball-fd option
747 * is just for easing the paranoia on the user side.
748 */
749 static const RTGETOPTDEF s_aOptions[] =
750 {
751 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
752 { "--cert-dir", 'c', RTGETOPT_REQ_STRING },
753 { "--name", 'n', RTGETOPT_REQ_STRING },
754 { "--tarball", 't', RTGETOPT_REQ_STRING },
755 { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 },
756 { "--replace", 'r', RTGETOPT_REQ_NOTHING }
757 };
758 RTGETOPTSTATE GetState;
759 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
760 if (RT_FAILURE(rc))
761 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
762
763 const char *pszBaseDir = NULL;
764 const char *pszCertDir = NULL;
765 const char *pszName = NULL;
766 const char *pszTarball = NULL;
767 RTFILE hTarballFileOpt = NIL_RTFILE;
768 bool fReplace = false;
769 RTGETOPTUNION ValueUnion;
770 int ch;
771 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
772 {
773 switch (ch)
774 {
775 case 'b':
776 if (pszBaseDir)
777 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
778 pszBaseDir = ValueUnion.psz;
779 if (!IsValidBaseDir(pszBaseDir))
780 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
781 break;
782
783 case 'c':
784 if (pszCertDir)
785 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options");
786 pszCertDir = ValueUnion.psz;
787 if (!IsValidCertificateDir(pszCertDir))
788 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir);
789 break;
790
791 case 'n':
792 if (pszName)
793 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
794 pszName = ValueUnion.psz;
795 if (!VBoxExtPackIsValidName(pszName))
796 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
797 break;
798
799 case 't':
800 if (pszTarball)
801 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options");
802 pszTarball = ValueUnion.psz;
803 break;
804
805 case 'd':
806 {
807 if (hTarballFileOpt != NIL_RTFILE)
808 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options");
809 RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64;
810 if (hNative != ValueUnion.u64)
811 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64);
812 rc = RTFileFromNative(&hTarballFileOpt, hNative);
813 if (RT_FAILURE(rc))
814 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc);
815 break;
816 }
817
818 case 'r':
819 fReplace = true;
820 break;
821
822 case 'h':
823 case 'V':
824 return DoStandardOption(ch);
825
826 default:
827 return RTGetOptPrintError(ch, &ValueUnion);
828 }
829 }
830 if (!pszName)
831 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
832 if (!pszBaseDir)
833 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
834 if (!pszCertDir)
835 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option");
836 if (!pszTarball)
837 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option");
838
839 /*
840 * Ok, down to business.
841 */
842 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
843 if (!pstrMangledName)
844 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
845
846 RTEXITCODE rcExit;
847 RTFILE hTarballFile;
848 rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
849 if (RT_SUCCESS(rc))
850 {
851 rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, hTarballFile, hTarballFileOpt,
852 pszName, pstrMangledName->c_str(), fReplace);
853 RTFileClose(hTarballFile);
854 }
855 else
856 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball);
857
858 delete pstrMangledName;
859 return rcExit;
860}
861
862
863/**
864 * Implements the 'uninstall' command.
865 *
866 * @returns The program exit code.
867 * @param argc The number of program arguments.
868 * @param argv The program arguments.
869 */
870static RTEXITCODE DoUninstall(int argc, char **argv)
871{
872 /*
873 * Parse the parameters.
874 *
875 * Note! The --base-dir is only for checking that the caller and this help
876 * applications have the same idea of where things are.
877 */
878 static const RTGETOPTDEF s_aOptions[] =
879 {
880 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
881 { "--name", 'n', RTGETOPT_REQ_STRING }
882 };
883 RTGETOPTSTATE GetState;
884 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
885 if (RT_FAILURE(rc))
886 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
887
888 const char *pszBaseDir = NULL;
889 const char *pszName = NULL;
890 RTGETOPTUNION ValueUnion;
891 int ch;
892 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
893 {
894 switch (ch)
895 {
896 case 'b':
897 if (pszBaseDir)
898 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
899 pszBaseDir = ValueUnion.psz;
900 if (!IsValidBaseDir(pszBaseDir))
901 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
902 break;
903
904 case 'n':
905 if (pszName)
906 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
907 pszName = ValueUnion.psz;
908 if (!VBoxExtPackIsValidName(pszName))
909 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
910 break;
911
912 case 'h':
913 case 'V':
914 return DoStandardOption(ch);
915
916 default:
917 return RTGetOptPrintError(ch, &ValueUnion);
918 }
919 }
920 if (!pszName)
921 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
922 if (!pszBaseDir)
923 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
924
925 /*
926 * Mangle the name so we can construct the directory names.
927 */
928 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
929 if (!pstrMangledName)
930 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
931 iprt::MiniString strMangledName(*pstrMangledName);
932 delete pstrMangledName;
933
934 /*
935 * Ok, down to business.
936 */
937 /* Check that it exists. */
938 char szExtPackDir[RTPATH_MAX];
939 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str());
940 if (RT_FAILURE(rc))
941 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
942
943 if (!RTDirExists(szExtPackDir))
944 {
945 RTMsgInfo("Extension pack not installed. Nothing to do.");
946 return RTEXITCODE_SUCCESS;
947 }
948
949 RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir);
950 if (rcExit == RTEXITCODE_SUCCESS)
951 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
952
953 return rcExit;
954}
955
956/**
957 * Implements the 'cleanup' command.
958 *
959 * @returns The program exit code.
960 * @param argc The number of program arguments.
961 * @param argv The program arguments.
962 */
963static RTEXITCODE DoCleanup(int argc, char **argv)
964{
965 /*
966 * Parse the parameters.
967 *
968 * Note! The --base-dir is only for checking that the caller and this help
969 * applications have the same idea of where things are.
970 */
971 static const RTGETOPTDEF s_aOptions[] =
972 {
973 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
974 };
975 RTGETOPTSTATE GetState;
976 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
977 if (RT_FAILURE(rc))
978 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
979
980 const char *pszBaseDir = NULL;
981 RTGETOPTUNION ValueUnion;
982 int ch;
983 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
984 {
985 switch (ch)
986 {
987 case 'b':
988 if (pszBaseDir)
989 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
990 pszBaseDir = ValueUnion.psz;
991 if (!IsValidBaseDir(pszBaseDir))
992 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
993 break;
994
995 case 'h':
996 case 'V':
997 return DoStandardOption(ch);
998
999 default:
1000 return RTGetOptPrintError(ch, &ValueUnion);
1001 }
1002 }
1003 if (!pszBaseDir)
1004 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1005
1006 /*
1007 * Ok, down to business.
1008 */
1009 PRTDIR pDir;
1010 rc = RTDirOpen(&pDir, pszBaseDir);
1011 if (RT_FAILURE(rc))
1012 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
1013
1014 uint32_t cCleaned = 0;
1015 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1016 for (;;)
1017 {
1018 RTDIRENTRYEX Entry;
1019 rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1020 if (RT_FAILURE(rc))
1021 {
1022 if (rc != VERR_NO_MORE_FILES)
1023 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
1024 break;
1025 }
1026
1027 /*
1028 * Only directories which conform with our temporary install/uninstall
1029 * naming scheme are candidates for cleaning.
1030 */
1031 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
1032 && strcmp(Entry.szName, ".") != 0
1033 && strcmp(Entry.szName, "..") != 0)
1034 {
1035 bool fCandidate = false;
1036 char *pszMarker = strstr(Entry.szName, "-_-");
1037 if ( pszMarker
1038 && ( !strcmp(pszMarker, "-_-uninst")
1039 || !strncmp(pszMarker, "-_-inst", sizeof("-_-inst") - 1)))
1040 fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]);
1041 if (fCandidate)
1042 {
1043 /*
1044 * Recursive delete, safe.
1045 */
1046 char szPath[RTPATH_MAX];
1047 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
1048 if (RT_SUCCESS(rc))
1049 {
1050 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
1051 if (rcExit2 == RTEXITCODE_SUCCESS)
1052 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
1053 else if (rcExit == RTEXITCODE_SUCCESS)
1054 rcExit = rcExit2;
1055 }
1056 else
1057 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
1058 cCleaned++;
1059 }
1060 }
1061 }
1062 RTDirClose(pDir);
1063 if (!cCleaned)
1064 RTMsgInfo("Nothing to clean.");
1065 return rcExit;
1066}
1067
1068#ifdef WITH_ELEVATION
1069
1070#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
1071/**
1072 * Looks in standard locations for a suitable exec tool.
1073 *
1074 * @returns true if found, false if not.
1075 * @param pszPath Where to store the path to the tool on
1076 * successs.
1077 * @param cbPath The size of the buffer @a pszPath points to.
1078 * @param pszName The name of the tool we're looking for.
1079 */
1080static bool FindExecTool(char *pszPath, size_t cbPath, const char *pszName)
1081{
1082 static const char * const s_apszPaths[] =
1083 {
1084 "/bin",
1085 "/usr/bin",
1086 "/usr/local/bin",
1087 "/sbin",
1088 "/usr/sbin",
1089 "/usr/local/sbin",
1090#ifdef RT_OS_SOLARIS
1091 "/usr/sfw/bin",
1092 "/usr/gnu/bin",
1093 "/usr/xpg4/bin",
1094 "/usr/xpg6/bin",
1095 "/usr/openwin/bin",
1096 "/usr/ucb"
1097#endif
1098 };
1099
1100 for (unsigned i = 0; i < RT_ELEMENTS(s_apszPaths); i++)
1101 {
1102 int rc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName);
1103 if (RT_SUCCESS(rc))
1104 {
1105 RTFSOBJINFO ObjInfo;
1106 rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
1107 if (RT_SUCCESS(rc))
1108 {
1109 if (!(ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH))
1110 return true;
1111 }
1112 }
1113 }
1114 return false;
1115}
1116#endif
1117
1118
1119/**
1120 * Copies the content of a file to a stream.
1121 *
1122 * @param hSrc The source file.
1123 * @param pDst The destination stream.
1124 * @param fComplain Whether to complain about errors (i.e. is this
1125 * stderr, if not keep the trap shut because it
1126 * may be missing when running under VBoxSVC.)
1127 */
1128static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain)
1129{
1130 int rc;
1131 for (;;)
1132 {
1133 char abBuf[0x1000];
1134 size_t cbRead;
1135 rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead);
1136 if (RT_FAILURE(rc))
1137 {
1138 RTMsgError("RTFileRead failed: %Rrc", rc);
1139 break;
1140 }
1141 if (!cbRead)
1142 break;
1143 rc = RTStrmWrite(pDst, abBuf, cbRead);
1144 if (RT_FAILURE(rc))
1145 {
1146 if (fComplain)
1147 RTMsgError("RTStrmWrite failed: %Rrc", rc);
1148 break;
1149 }
1150 }
1151 rc = RTStrmFlush(pDst);
1152 if (RT_FAILURE(rc) && fComplain)
1153 RTMsgError("RTStrmFlush failed: %Rrc", rc);
1154}
1155
1156
1157/**
1158 * Relaunches ourselves as a elevated process using platform specific facilities.
1159 *
1160 * @returns Program exit code.
1161 * @param pszExecPath The executable path.
1162 * @param papszArgs The arguments.
1163 * @param cSuArgs The number of argument entries reserved for the
1164 * 'su' like programs at the start of papszArgs.
1165 * @param cMyArgs The number of arguments following @a cSuArgs.
1166 * @param iCmd The command that is being executed. (For
1167 * selecting messages.)
1168 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1169 */
1170static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, const char **papszArgs, int cSuArgs, int cMyArgs,
1171 int iCmd, const char *pszDisplayInfoHack)
1172{
1173 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1174#ifdef RT_OS_WINDOWS
1175 NOREF(iCmd);
1176
1177 MSG Msg;
1178 PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE);
1179 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1180
1181 SHELLEXECUTEINFOW Info;
1182
1183 Info.cbSize = sizeof(Info);
1184 Info.fMask = SEE_MASK_NOCLOSEPROCESS;
1185 Info.hwnd = NULL;
1186 Info.lpVerb = L"runas";
1187 int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile);
1188 if (RT_SUCCESS(rc))
1189 {
1190 char *pszCmdLine;
1191 rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT);
1192 if (RT_SUCCESS(rc))
1193 {
1194 rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters);
1195 if (RT_SUCCESS(rc))
1196 {
1197 Info.lpDirectory = NULL;
1198 Info.nShow = SW_SHOWMAXIMIZED;
1199 Info.hInstApp = NULL;
1200 Info.lpIDList = NULL;
1201 Info.lpClass = NULL;
1202 Info.hkeyClass = NULL;
1203 Info.dwHotKey = 0;
1204 Info.hMonitor = NULL;
1205 Info.hProcess = INVALID_HANDLE_VALUE;
1206
1207 /* Apply display hacks. */
1208 if (pszDisplayInfoHack)
1209 {
1210 const char *pszArg = strstr(pszDisplayInfoHack, "hwnd=");
1211 if (pszArg)
1212 {
1213 uint64_t u64Hwnd;
1214 rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd);
1215 if (RT_SUCCESS(rc))
1216 {
1217 HWND hwnd = (HWND)(uintptr_t)u64Hwnd;
1218 Info.hwnd = hwnd;
1219 Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
1220 }
1221 }
1222 }
1223 if (Info.hMonitor == NULL)
1224 {
1225 POINT Pt = {0,0};
1226 Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
1227 }
1228 if (Info.hMonitor != NULL)
1229 Info.fMask |= SEE_MASK_HMONITOR;
1230
1231 if (ShellExecuteExW(&Info))
1232 {
1233 if (Info.hProcess != INVALID_HANDLE_VALUE)
1234 {
1235 /*
1236 * Wait for the process, make sure the deal with messages.
1237 */
1238 for (;;)
1239 {
1240 DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS);
1241 if (dwRc == WAIT_OBJECT_0)
1242 break;
1243 if ( dwRc != WAIT_TIMEOUT
1244 && dwRc != WAIT_OBJECT_0 + 1)
1245 {
1246 RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError());
1247 break;
1248 }
1249 MSG Msg;
1250 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1251 {
1252 TranslateMessage(&Msg);
1253 DispatchMessageW(&Msg);
1254 }
1255 }
1256
1257 DWORD dwExitCode;
1258 if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
1259 {
1260 if (dwExitCode >= 0 && dwExitCode < 128)
1261 rcExit = (RTEXITCODE)dwExitCode;
1262 else
1263 rcExit = RTEXITCODE_FAILURE;
1264 }
1265 CloseHandle(Info.hProcess);
1266 }
1267 else
1268 RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
1269 }
1270 else
1271 RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
1272
1273
1274 RTUtf16Free((PRTUTF16)Info.lpParameters);
1275 }
1276 RTStrFree(pszCmdLine);
1277 }
1278
1279 RTUtf16Free((PRTUTF16)Info.lpFile);
1280 }
1281 else
1282 RTMsgError("RTStrToUtf16 failed: %Rc", rc);
1283
1284#elif defined(RT_OS_DARWIN)
1285 char szIconName[RTPATH_MAX];
1286 int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
1287 if (RT_SUCCESS(rc))
1288 rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
1289 if (RT_FAILURE(rc))
1290 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
1291
1292 AuthorizationRef AuthRef;
1293 OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
1294 if (orc == errAuthorizationSuccess)
1295 {
1296 /*
1297 * Preautorize the privileged execution of ourselves.
1298 */
1299 AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
1300 AuthorizationRights AuthRights = { 1, &AuthItem };
1301
1302 NOREF(iCmd);
1303 static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
1304 AuthorizationItem aAuthEnvItems[] =
1305 {
1306 { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
1307 { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
1308 };
1309 AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
1310
1311 orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
1312 kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
1313 | kAuthorizationFlagExtendRights,
1314 NULL);
1315 if (orc == errAuthorizationSuccess)
1316 {
1317 /*
1318 * Execute with extra permissions
1319 */
1320 FILE *pSocketStrm;
1321 orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
1322 (char * const *)&papszArgs[cSuArgs + 3],
1323 &pSocketStrm);
1324 if (orc == errAuthorizationSuccess)
1325 {
1326 /*
1327 * Read the output of the tool, the read will fail when it quits.
1328 */
1329 for (;;)
1330 {
1331 char achBuf[1024];
1332 size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
1333 if (!cbRead)
1334 break;
1335 fwrite(achBuf, 1, cbRead, stdout);
1336 }
1337 rcExit = RTEXITCODE_SUCCESS;
1338 fclose(pSocketStrm);
1339 }
1340 else
1341 RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
1342 }
1343 else if (orc == errAuthorizationCanceled)
1344 RTMsgError("Authorization canceled by the user");
1345 else
1346 RTMsgError("AuthorizationCopyRights failed: %d", orc);
1347 AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
1348 }
1349 else
1350 RTMsgError("AuthorizationCreate failed: %d", orc);
1351
1352#else
1353
1354 /*
1355 * Several of the alternatives below will require a command line.
1356 */
1357 char *pszCmdLine;
1358 int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1359 if (RT_FAILURE(rc))
1360 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc");
1361
1362 /*
1363 * Look for various standard stuff for executing a program as root.
1364 *
1365 * N.B. When adding new arguments, please make 100% sure RelaunchElevated
1366 * allocates enough array entries.
1367 *
1368 * TODO: Feel free to contribute code for using PolicyKit directly.
1369 */
1370 bool fHaveDisplayVar = RTEnvExist("DISPLAY");
1371 int iSuArg = cSuArgs;
1372 char szExecTool[260];
1373 char szXterm[260];
1374
1375 /*
1376 * kdesudo is available on KDE3/KDE4
1377 */
1378 if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo"))
1379 {
1380 iSuArg = cSuArgs - 4;
1381 papszArgs[cSuArgs - 4] = szExecTool;
1382 papszArgs[cSuArgs - 3] = "--comment";
1383 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1384 ? "VirtualBox extension pack installer"
1385 : iCmd == CMD_UNINSTALL
1386 ? "VirtualBox extension pack uninstaller"
1387 : "VirtualBox extension pack maintainer";
1388 papszArgs[cSuArgs - 1] = "--";
1389 }
1390 /*
1391 * gksu is our favorite as it is very well integrated.
1392 */
1393 else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu"))
1394 {
1395#if 0 /* older gksu does not grok --description nor '--' and multiple args. */
1396 iSuArg = cSuArgs - 4;
1397 papszArgs[cSuArgs - 4] = szExecTool;
1398 papszArgs[cSuArgs - 3] = "--description";
1399 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1400 ? "VirtualBox extension pack installer"
1401 : iCmd == CMD_UNINSTALL
1402 ? "VirtualBox extension pack uninstaller"
1403 : "VirtualBox extension pack maintainer";
1404 papszArgs[cSuArgs - 1] = "--";
1405#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */
1406 iSuArg = cSuArgs - 4;
1407 papszArgs[cSuArgs - 4] = szExecTool;
1408 papszArgs[cSuArgs - 3] = "-au";
1409 papszArgs[cSuArgs - 2] = "root";
1410 papszArgs[cSuArgs - 1] = pszCmdLine;
1411 papszArgs[cSuArgs] = NULL;
1412#else
1413 iSuArg = cSuArgs - 2;
1414 papszArgs[cSuArgs - 2] = szExecTool;
1415 papszArgs[cSuArgs - 1] = pszCmdLine;
1416 papszArgs[cSuArgs] = NULL;
1417#endif
1418 }
1419 /*
1420 * pkexec may work for ssh console sessions as well if the right agents
1421 * are installed. However it is very generic and does not allow for any
1422 * custom messages. Thus it comes after gksu.
1423 */
1424 else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec"))
1425 {
1426 iSuArg = cSuArgs - 1;
1427 papszArgs[cSuArgs - 1] = szExecTool;
1428 }
1429 /*
1430 * The ultimate fallback is running 'su -' within an xterm. We use the
1431 * title of the xterm to tell what is going on.
1432 */
1433 else if ( fHaveDisplayVar
1434 && FindExecTool(szExecTool, sizeof(szExecTool), "su")
1435 && FindExecTool(szXterm, sizeof(szXterm), "xterm"))
1436 {
1437 iSuArg = cSuArgs - 9;
1438 papszArgs[cSuArgs - 9] = szXterm;
1439 papszArgs[cSuArgs - 8] = "-T";
1440 papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL
1441 ? "VirtualBox extension pack installer - su"
1442 : iCmd == CMD_UNINSTALL
1443 ? "VirtualBox extension pack uninstaller - su"
1444 : "VirtualBox extension pack maintainer - su";
1445 papszArgs[cSuArgs - 6] = "-e";
1446 papszArgs[cSuArgs - 5] = szExecTool;
1447 papszArgs[cSuArgs - 4] = "-";
1448 papszArgs[cSuArgs - 3] = "root";
1449 papszArgs[cSuArgs - 2] = "-c";
1450 papszArgs[cSuArgs - 1] = pszCmdLine;
1451 papszArgs[cSuArgs] = NULL;
1452 }
1453 else if (fHaveDisplayVar)
1454 RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root");
1455 else
1456 RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root");
1457 if (iSuArg != cSuArgs)
1458 {
1459 AssertRelease(iSuArg >= 0);
1460
1461 /*
1462 * Argument list constructed, execute it and wait for the exec
1463 * program to complete.
1464 */
1465 RTPROCESS hProcess;
1466 rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/,
1467 NULL /*phStdIn*/, NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/,
1468 &hProcess);
1469 if (RT_SUCCESS(rc))
1470 {
1471 RTPROCSTATUS Status;
1472 rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status);
1473 if (RT_SUCCESS(rc))
1474 {
1475 if (Status.enmReason == RTPROCEXITREASON_NORMAL)
1476 rcExit = (RTEXITCODE)Status.iStatus;
1477 else
1478 rcExit = RTEXITCODE_FAILURE;
1479 }
1480 else
1481 RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc);
1482 }
1483 else
1484 RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc);
1485 }
1486 RTStrFree(pszCmdLine);
1487
1488#endif
1489 return rcExit;
1490}
1491
1492
1493/**
1494 * Relaunches ourselves as a elevated process using platform specific facilities.
1495 *
1496 * @returns Program exit code.
1497 * @param argc The number of arguments.
1498 * @param argv The arguments.
1499 * @param iCmd The command that is being executed.
1500 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1501 */
1502static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack)
1503{
1504 /*
1505 * We need the executable name later, so get it now when it's easy to quit.
1506 */
1507 char szExecPath[RTPATH_MAX];
1508 if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
1509 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1510
1511 /*
1512 * Create a couple of temporary files for stderr and stdout.
1513 */
1514 char szTempDir[RTPATH_MAX - sizeof("/stderr")];
1515 int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
1516 if (RT_FAILURE(rc))
1517 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
1518 rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
1519 if (RT_FAILURE(rc))
1520 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
1521 rc = RTDirCreateTemp(szTempDir);
1522 if (RT_FAILURE(rc))
1523 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
1524
1525 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1526 char szStdOut[RTPATH_MAX];
1527 char szStdErr[RTPATH_MAX];
1528 rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
1529 if (RT_SUCCESS(rc))
1530 rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
1531 if (RT_SUCCESS(rc))
1532 {
1533 RTFILE hStdOut;
1534 rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1535 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1536 if (RT_SUCCESS(rc))
1537 {
1538 RTFILE hStdErr;
1539 rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1540 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1541 if (RT_SUCCESS(rc))
1542 {
1543 /*
1544 * Insert the --elevated and stdout/err names into the argument
1545 * list. Note that darwin skips the --stdout bit, so don't
1546 * change the order here.
1547 */
1548 int const cSuArgs = 12;
1549 int cArgs = argc + 5 + 1;
1550 char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *));
1551 if (papszArgs)
1552 {
1553 int iDst = cSuArgs;
1554 papszArgs[iDst++] = argv[0];
1555 papszArgs[iDst++] = "--stdout";
1556 papszArgs[iDst++] = szStdOut;
1557 papszArgs[iDst++] = "--stderr";
1558 papszArgs[iDst++] = szStdErr;
1559 papszArgs[iDst++] = "--elevated";
1560 for (int iSrc = 1; iSrc <= argc; iSrc++)
1561 papszArgs[iDst++] = argv[iSrc];
1562
1563 /*
1564 * Do the platform specific process execution (waiting included).
1565 */
1566 rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack);
1567
1568 /*
1569 * Copy the standard files to our standard handles.
1570 */
1571 CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
1572 CopyFileToStdXxx(hStdOut, g_pStdOut, false);
1573
1574 RTMemTmpFree(papszArgs);
1575 }
1576
1577 RTFileClose(hStdErr);
1578 RTFileDelete(szStdErr);
1579 }
1580 RTFileClose(hStdOut);
1581 RTFileDelete(szStdOut);
1582 }
1583 }
1584 RTDirRemove(szTempDir);
1585
1586 return rcExit;
1587}
1588
1589
1590/**
1591 * Checks if the process is elevated or not.
1592 *
1593 * @returns RTEXITCODE_SUCCESS if preconditions are fine,
1594 * otherwise error message + RTEXITCODE_FAILURE.
1595 * @param pfElevated Where to store the elevation indicator.
1596 */
1597static RTEXITCODE ElevationCheck(bool *pfElevated)
1598{
1599 *pfElevated = false;
1600
1601# if defined(RT_OS_WINDOWS)
1602 /** @todo This should probably check if UAC is diabled and if we are
1603 * Administrator first. Also needs to check for Vista+ first, probably.
1604 */
1605 DWORD cb;
1606 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1607 HANDLE hToken;
1608 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
1609 return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
1610
1611 /*
1612 * Check if we're member of the Administrators group. If we aren't, there
1613 * is no way to elevate ourselves to system admin.
1614 * N.B. CheckTokenMembership does not do the job here (due to attributes?).
1615 */
1616 BOOL fIsAdmin = FALSE;
1617 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
1618 PSID pAdminGrpSid;
1619 if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
1620 {
1621# ifdef DEBUG
1622 char *pszAdminGrpSid = NULL;
1623 ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
1624# endif
1625
1626 if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
1627 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1628 {
1629 PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
1630 if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
1631 {
1632 for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
1633 {
1634# ifdef DEBUG
1635 char *pszGrpSid = NULL;
1636 ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
1637# endif
1638 if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
1639 {
1640 /* That it's listed is enough I think, ignore attributes. */
1641 fIsAdmin = TRUE;
1642 break;
1643 }
1644 }
1645 }
1646 else
1647 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
1648 RTMemFree(pTokenGroups);
1649 }
1650 else
1651 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
1652
1653 FreeSid(pAdminGrpSid);
1654 }
1655 else
1656 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
1657 if (fIsAdmin)
1658 {
1659 /*
1660 * Check the integrity level (Vista / UAC).
1661 */
1662# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
1663# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
1664 if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
1665 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1666 {
1667 PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
1668 if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
1669 {
1670 DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
1671
1672 if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
1673 *pfElevated = true;
1674 }
1675 else
1676 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1677 RTMemFree(pSidAndAttr);
1678 }
1679 else if ( GetLastError() == ERROR_INVALID_PARAMETER
1680 || GetLastError() == ERROR_NOT_SUPPORTED)
1681 *pfElevated = true; /* Older Windows version. */
1682 else
1683 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1684 }
1685 else
1686 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
1687
1688 CloseHandle(hToken);
1689 return rcExit;
1690
1691# else
1692 /*
1693 * On Unixy systems, we check if the executable and the current user is
1694 * the same. This heuristic works fine for both hardened and development
1695 * builds.
1696 */
1697 char szExecPath[RTPATH_MAX];
1698 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
1699 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1700
1701 RTFSOBJINFO ObjInfo;
1702 int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
1703 if (RT_FAILURE(rc))
1704 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
1705
1706 *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
1707 || ObjInfo.Attr.u.Unix.uid == getuid();
1708 return RTEXITCODE_SUCCESS;
1709# endif
1710}
1711
1712#endif /* WITH_ELEVATION */
1713
1714int main(int argc, char **argv)
1715{
1716 /*
1717 * Initialize IPRT and check that we're correctly installed.
1718 */
1719 int rc = RTR3Init();
1720 if (RT_FAILURE(rc))
1721 return RTMsgInitFailure(rc);
1722
1723 RTERRINFOSTATIC ErrInfo;
1724 RTErrInfoInitStatic(&ErrInfo);
1725 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core);
1726 if (RT_FAILURE(rc))
1727 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg);
1728
1729 /*
1730 * Elevation check.
1731 */
1732 const char *pszDisplayInfoHack = NULL;
1733 RTEXITCODE rcExit;
1734#ifdef WITH_ELEVATION
1735 bool fElevated;
1736 rcExit = ElevationCheck(&fElevated);
1737 if (rcExit != RTEXITCODE_SUCCESS)
1738 return rcExit;
1739#endif
1740
1741 /*
1742 * Parse the top level arguments until we find a command.
1743 */
1744 static const RTGETOPTDEF s_aOptions[] =
1745 {
1746 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1747 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1748 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1749#ifdef WITH_ELEVATION
1750 { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
1751 { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
1752 { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
1753#endif
1754 { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING },
1755 };
1756 RTGETOPTSTATE GetState;
1757 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1758 if (RT_FAILURE(rc))
1759 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1760 for (;;)
1761 {
1762 RTGETOPTUNION ValueUnion;
1763 int ch = RTGetOpt(&GetState, &ValueUnion);
1764 switch (ch)
1765 {
1766 case 0:
1767 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1768
1769 case CMD_INSTALL:
1770 case CMD_UNINSTALL:
1771 case CMD_CLEANUP:
1772 {
1773#ifdef WITH_ELEVATION
1774 if (!fElevated)
1775 return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack);
1776#endif
1777 int cCmdargs = argc - GetState.iNext;
1778 char **papszCmdArgs = argv + GetState.iNext;
1779 switch (ch)
1780 {
1781 case CMD_INSTALL:
1782 rcExit = DoInstall( cCmdargs, papszCmdArgs);
1783 break;
1784 case CMD_UNINSTALL:
1785 rcExit = DoUninstall(cCmdargs, papszCmdArgs);
1786 break;
1787 case CMD_CLEANUP:
1788 rcExit = DoCleanup( cCmdargs, papszCmdArgs);
1789 break;
1790 default:
1791 AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
1792 }
1793
1794 /*
1795 * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
1796 * success since the exit code may otherwise get lost in the
1797 * process elevation fun.
1798 */
1799 RTStrmFlush(g_pStdOut);
1800 RTStrmFlush(g_pStdErr);
1801 switch (rcExit)
1802 {
1803 case RTEXITCODE_SUCCESS:
1804 RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
1805 break;
1806 default:
1807 RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
1808 break;
1809 }
1810 RTStrmFlush(g_pStdErr);
1811 RTStrmFlush(g_pStdOut);
1812 return rcExit;
1813 }
1814
1815#ifdef WITH_ELEVATION
1816 case OPT_ELEVATED:
1817 fElevated = true;
1818 break;
1819
1820 case OPT_STDERR:
1821 case OPT_STDOUT:
1822 {
1823 FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
1824 if (!pFile)
1825 {
1826 rc = RTErrConvertFromErrno(errno);
1827 return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
1828 }
1829 break;
1830 }
1831#endif
1832
1833 case OPT_DISP_INFO_HACK:
1834 if (pszDisplayInfoHack)
1835 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once");
1836 pszDisplayInfoHack = ValueUnion.psz;
1837 break;
1838
1839 case 'h':
1840 case 'V':
1841 return DoStandardOption(ch);
1842
1843 default:
1844 return RTGetOptPrintError(ch, &ValueUnion);
1845 }
1846 /* not currently reached */
1847 }
1848 /* not reached */
1849}
1850
1851
1852#ifdef RT_OS_WINDOWS
1853extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1854{
1855 g_hInstance = hInstance;
1856 NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine);
1857
1858 int rc = RTR3Init();
1859 if (RT_FAILURE(rc))
1860 return RTMsgInitFailure(rc);
1861
1862 LPWSTR pwszCmdLine = GetCommandLineW();
1863 if (!pwszCmdLine)
1864 return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed");
1865
1866 char *pszCmdLine;
1867 rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */
1868 if (RT_FAILURE(rc))
1869 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc);
1870
1871 int cArgs;
1872 char **papszArgs;
1873 rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, NULL);
1874 if (RT_SUCCESS(rc))
1875 {
1876
1877 rc = main(cArgs, papszArgs);
1878
1879 RTGetOptArgvFree(papszArgs);
1880 }
1881 else
1882 rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc);
1883 RTStrFree(pszCmdLine);
1884
1885 return rc;
1886}
1887#endif
1888
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette