VirtualBox

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

Last change on this file since 35479 was 35378, checked in by vboxsync, 14 years ago

export

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 68.3 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 35378 2010-12-30 14:58:37Z 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#if 0 /* This deadlocks with the GUI because the GUI thread is stuck in the API call :/ */
1208 /* Apply display hacks. */
1209 if (pszDisplayInfoHack)
1210 {
1211 const char *pszArg = strstr(pszDisplayInfoHack, "hwnd=");
1212 if (pszArg)
1213 {
1214 uint64_t u64Hwnd;
1215 rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd);
1216 if (RT_SUCCESS(rc))
1217 {
1218 HWND hwnd = (HWND)(uintptr_t)u64Hwnd;
1219 Info.hwnd = hwnd;
1220 Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
1221 }
1222 }
1223 }
1224 if (Info.hMonitor == NULL)
1225 {
1226 POINT Pt = {0,0};
1227 Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
1228 }
1229 if (Info.hMonitor != NULL)
1230 Info.fMask |= SEE_MASK_HMONITOR;
1231 #endif
1232
1233 if (ShellExecuteExW(&Info))
1234 {
1235 if (Info.hProcess != INVALID_HANDLE_VALUE)
1236 {
1237 /*
1238 * Wait for the process, make sure the deal with messages.
1239 */
1240 for (;;)
1241 {
1242 DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS);
1243 if (dwRc == WAIT_OBJECT_0)
1244 break;
1245 if ( dwRc != WAIT_TIMEOUT
1246 && dwRc != WAIT_OBJECT_0 + 1)
1247 {
1248 RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError());
1249 break;
1250 }
1251 MSG Msg;
1252 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1253 {
1254 TranslateMessage(&Msg);
1255 DispatchMessageW(&Msg);
1256 }
1257 }
1258
1259 DWORD dwExitCode;
1260 if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
1261 {
1262 if (dwExitCode >= 0 && dwExitCode < 128)
1263 rcExit = (RTEXITCODE)dwExitCode;
1264 else
1265 rcExit = RTEXITCODE_FAILURE;
1266 }
1267 CloseHandle(Info.hProcess);
1268 }
1269 else
1270 RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
1271 }
1272 else
1273 RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
1274
1275
1276 RTUtf16Free((PRTUTF16)Info.lpParameters);
1277 }
1278 RTStrFree(pszCmdLine);
1279 }
1280
1281 RTUtf16Free((PRTUTF16)Info.lpFile);
1282 }
1283 else
1284 RTMsgError("RTStrToUtf16 failed: %Rc", rc);
1285
1286#elif defined(RT_OS_DARWIN)
1287 char szIconName[RTPATH_MAX];
1288 int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
1289 if (RT_SUCCESS(rc))
1290 rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
1291 if (RT_FAILURE(rc))
1292 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
1293
1294 AuthorizationRef AuthRef;
1295 OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
1296 if (orc == errAuthorizationSuccess)
1297 {
1298 /*
1299 * Preautorize the privileged execution of ourselves.
1300 */
1301 AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
1302 AuthorizationRights AuthRights = { 1, &AuthItem };
1303
1304 NOREF(iCmd);
1305 static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
1306 AuthorizationItem aAuthEnvItems[] =
1307 {
1308 { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
1309 { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
1310 };
1311 AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
1312
1313 orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
1314 kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
1315 | kAuthorizationFlagExtendRights,
1316 NULL);
1317 if (orc == errAuthorizationSuccess)
1318 {
1319 /*
1320 * Execute with extra permissions
1321 */
1322 FILE *pSocketStrm;
1323 orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
1324 (char * const *)&papszArgs[cSuArgs + 3],
1325 &pSocketStrm);
1326 if (orc == errAuthorizationSuccess)
1327 {
1328 /*
1329 * Read the output of the tool, the read will fail when it quits.
1330 */
1331 for (;;)
1332 {
1333 char achBuf[1024];
1334 size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
1335 if (!cbRead)
1336 break;
1337 fwrite(achBuf, 1, cbRead, stdout);
1338 }
1339 rcExit = RTEXITCODE_SUCCESS;
1340 fclose(pSocketStrm);
1341 }
1342 else
1343 RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
1344 }
1345 else if (orc == errAuthorizationCanceled)
1346 RTMsgError("Authorization canceled by the user");
1347 else
1348 RTMsgError("AuthorizationCopyRights failed: %d", orc);
1349 AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
1350 }
1351 else
1352 RTMsgError("AuthorizationCreate failed: %d", orc);
1353
1354#else
1355
1356 /*
1357 * Several of the alternatives below will require a command line.
1358 */
1359 char *pszCmdLine;
1360 int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1361 if (RT_FAILURE(rc))
1362 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc");
1363
1364 /*
1365 * Look for various standard stuff for executing a program as root.
1366 *
1367 * N.B. When adding new arguments, please make 100% sure RelaunchElevated
1368 * allocates enough array entries.
1369 *
1370 * TODO: Feel free to contribute code for using PolicyKit directly.
1371 */
1372 bool fHaveDisplayVar = RTEnvExist("DISPLAY");
1373 int iSuArg = cSuArgs;
1374 char szExecTool[260];
1375 char szXterm[260];
1376
1377 /*
1378 * kdesudo is available on KDE3/KDE4
1379 */
1380 if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo"))
1381 {
1382 iSuArg = cSuArgs - 4;
1383 papszArgs[cSuArgs - 4] = szExecTool;
1384 papszArgs[cSuArgs - 3] = "--comment";
1385 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1386 ? "VirtualBox extension pack installer"
1387 : iCmd == CMD_UNINSTALL
1388 ? "VirtualBox extension pack uninstaller"
1389 : "VirtualBox extension pack maintainer";
1390 papszArgs[cSuArgs - 1] = "--";
1391 }
1392 /*
1393 * gksu is our favorite as it is very well integrated.
1394 */
1395 else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu"))
1396 {
1397#if 0 /* older gksu does not grok --description nor '--' and multiple args. */
1398 iSuArg = cSuArgs - 4;
1399 papszArgs[cSuArgs - 4] = szExecTool;
1400 papszArgs[cSuArgs - 3] = "--description";
1401 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1402 ? "VirtualBox extension pack installer"
1403 : iCmd == CMD_UNINSTALL
1404 ? "VirtualBox extension pack uninstaller"
1405 : "VirtualBox extension pack maintainer";
1406 papszArgs[cSuArgs - 1] = "--";
1407#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */
1408 iSuArg = cSuArgs - 4;
1409 papszArgs[cSuArgs - 4] = szExecTool;
1410 papszArgs[cSuArgs - 3] = "-au";
1411 papszArgs[cSuArgs - 2] = "root";
1412 papszArgs[cSuArgs - 1] = pszCmdLine;
1413 papszArgs[cSuArgs] = NULL;
1414#else
1415 iSuArg = cSuArgs - 2;
1416 papszArgs[cSuArgs - 2] = szExecTool;
1417 papszArgs[cSuArgs - 1] = pszCmdLine;
1418 papszArgs[cSuArgs] = NULL;
1419#endif
1420 }
1421 /*
1422 * pkexec may work for ssh console sessions as well if the right agents
1423 * are installed. However it is very generic and does not allow for any
1424 * custom messages. Thus it comes after gksu.
1425 */
1426 else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec"))
1427 {
1428 iSuArg = cSuArgs - 1;
1429 papszArgs[cSuArgs - 1] = szExecTool;
1430 }
1431 /*
1432 * The ultimate fallback is running 'su -' within an xterm. We use the
1433 * title of the xterm to tell what is going on.
1434 */
1435 else if ( fHaveDisplayVar
1436 && FindExecTool(szExecTool, sizeof(szExecTool), "su")
1437 && FindExecTool(szXterm, sizeof(szXterm), "xterm"))
1438 {
1439 iSuArg = cSuArgs - 9;
1440 papszArgs[cSuArgs - 9] = szXterm;
1441 papszArgs[cSuArgs - 8] = "-T";
1442 papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL
1443 ? "VirtualBox extension pack installer - su"
1444 : iCmd == CMD_UNINSTALL
1445 ? "VirtualBox extension pack uninstaller - su"
1446 : "VirtualBox extension pack maintainer - su";
1447 papszArgs[cSuArgs - 6] = "-e";
1448 papszArgs[cSuArgs - 5] = szExecTool;
1449 papszArgs[cSuArgs - 4] = "-";
1450 papszArgs[cSuArgs - 3] = "root";
1451 papszArgs[cSuArgs - 2] = "-c";
1452 papszArgs[cSuArgs - 1] = pszCmdLine;
1453 papszArgs[cSuArgs] = NULL;
1454 }
1455 else if (fHaveDisplayVar)
1456 RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root");
1457 else
1458 RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root");
1459 if (iSuArg != cSuArgs)
1460 {
1461 AssertRelease(iSuArg >= 0);
1462
1463 /*
1464 * Argument list constructed, execute it and wait for the exec
1465 * program to complete.
1466 */
1467 RTPROCESS hProcess;
1468 rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/,
1469 NULL /*phStdIn*/, NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/,
1470 &hProcess);
1471 if (RT_SUCCESS(rc))
1472 {
1473 RTPROCSTATUS Status;
1474 rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status);
1475 if (RT_SUCCESS(rc))
1476 {
1477 if (Status.enmReason == RTPROCEXITREASON_NORMAL)
1478 rcExit = (RTEXITCODE)Status.iStatus;
1479 else
1480 rcExit = RTEXITCODE_FAILURE;
1481 }
1482 else
1483 RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc);
1484 }
1485 else
1486 RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc);
1487 }
1488 RTStrFree(pszCmdLine);
1489
1490#endif
1491 return rcExit;
1492}
1493
1494
1495/**
1496 * Relaunches ourselves as a elevated process using platform specific facilities.
1497 *
1498 * @returns Program exit code.
1499 * @param argc The number of arguments.
1500 * @param argv The arguments.
1501 * @param iCmd The command that is being executed.
1502 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1503 */
1504static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack)
1505{
1506 /*
1507 * We need the executable name later, so get it now when it's easy to quit.
1508 */
1509 char szExecPath[RTPATH_MAX];
1510 if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
1511 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1512
1513 /*
1514 * Create a couple of temporary files for stderr and stdout.
1515 */
1516 char szTempDir[RTPATH_MAX - sizeof("/stderr")];
1517 int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
1518 if (RT_FAILURE(rc))
1519 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
1520 rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
1521 if (RT_FAILURE(rc))
1522 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
1523 rc = RTDirCreateTemp(szTempDir);
1524 if (RT_FAILURE(rc))
1525 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
1526
1527 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1528 char szStdOut[RTPATH_MAX];
1529 char szStdErr[RTPATH_MAX];
1530 rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
1531 if (RT_SUCCESS(rc))
1532 rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
1533 if (RT_SUCCESS(rc))
1534 {
1535 RTFILE hStdOut;
1536 rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1537 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1538 if (RT_SUCCESS(rc))
1539 {
1540 RTFILE hStdErr;
1541 rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1542 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1543 if (RT_SUCCESS(rc))
1544 {
1545 /*
1546 * Insert the --elevated and stdout/err names into the argument
1547 * list. Note that darwin skips the --stdout bit, so don't
1548 * change the order here.
1549 */
1550 int const cSuArgs = 12;
1551 int cArgs = argc + 5 + 1;
1552 char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *));
1553 if (papszArgs)
1554 {
1555 int iDst = cSuArgs;
1556 papszArgs[iDst++] = argv[0];
1557 papszArgs[iDst++] = "--stdout";
1558 papszArgs[iDst++] = szStdOut;
1559 papszArgs[iDst++] = "--stderr";
1560 papszArgs[iDst++] = szStdErr;
1561 papszArgs[iDst++] = "--elevated";
1562 for (int iSrc = 1; iSrc <= argc; iSrc++)
1563 papszArgs[iDst++] = argv[iSrc];
1564
1565 /*
1566 * Do the platform specific process execution (waiting included).
1567 */
1568 rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack);
1569
1570 /*
1571 * Copy the standard files to our standard handles.
1572 */
1573 CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
1574 CopyFileToStdXxx(hStdOut, g_pStdOut, false);
1575
1576 RTMemTmpFree(papszArgs);
1577 }
1578
1579 RTFileClose(hStdErr);
1580 RTFileDelete(szStdErr);
1581 }
1582 RTFileClose(hStdOut);
1583 RTFileDelete(szStdOut);
1584 }
1585 }
1586 RTDirRemove(szTempDir);
1587
1588 return rcExit;
1589}
1590
1591
1592/**
1593 * Checks if the process is elevated or not.
1594 *
1595 * @returns RTEXITCODE_SUCCESS if preconditions are fine,
1596 * otherwise error message + RTEXITCODE_FAILURE.
1597 * @param pfElevated Where to store the elevation indicator.
1598 */
1599static RTEXITCODE ElevationCheck(bool *pfElevated)
1600{
1601 *pfElevated = false;
1602
1603# if defined(RT_OS_WINDOWS)
1604 /** @todo This should probably check if UAC is diabled and if we are
1605 * Administrator first. Also needs to check for Vista+ first, probably.
1606 */
1607 DWORD cb;
1608 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1609 HANDLE hToken;
1610 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
1611 return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
1612
1613 /*
1614 * Check if we're member of the Administrators group. If we aren't, there
1615 * is no way to elevate ourselves to system admin.
1616 * N.B. CheckTokenMembership does not do the job here (due to attributes?).
1617 */
1618 BOOL fIsAdmin = FALSE;
1619 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
1620 PSID pAdminGrpSid;
1621 if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
1622 {
1623# ifdef DEBUG
1624 char *pszAdminGrpSid = NULL;
1625 ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
1626# endif
1627
1628 if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
1629 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1630 {
1631 PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
1632 if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
1633 {
1634 for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
1635 {
1636# ifdef DEBUG
1637 char *pszGrpSid = NULL;
1638 ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
1639# endif
1640 if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
1641 {
1642 /* That it's listed is enough I think, ignore attributes. */
1643 fIsAdmin = TRUE;
1644 break;
1645 }
1646 }
1647 }
1648 else
1649 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
1650 RTMemFree(pTokenGroups);
1651 }
1652 else
1653 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
1654
1655 FreeSid(pAdminGrpSid);
1656 }
1657 else
1658 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
1659 if (fIsAdmin)
1660 {
1661 /*
1662 * Check the integrity level (Vista / UAC).
1663 */
1664# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
1665# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
1666 if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
1667 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1668 {
1669 PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
1670 if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
1671 {
1672 DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
1673
1674 if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
1675 *pfElevated = true;
1676 }
1677 else
1678 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1679 RTMemFree(pSidAndAttr);
1680 }
1681 else if ( GetLastError() == ERROR_INVALID_PARAMETER
1682 || GetLastError() == ERROR_NOT_SUPPORTED)
1683 *pfElevated = true; /* Older Windows version. */
1684 else
1685 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1686 }
1687 else
1688 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
1689
1690 CloseHandle(hToken);
1691 return rcExit;
1692
1693# else
1694 /*
1695 * On Unixy systems, we check if the executable and the current user is
1696 * the same. This heuristic works fine for both hardened and development
1697 * builds.
1698 */
1699 char szExecPath[RTPATH_MAX];
1700 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
1701 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1702
1703 RTFSOBJINFO ObjInfo;
1704 int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
1705 if (RT_FAILURE(rc))
1706 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
1707
1708 *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
1709 || ObjInfo.Attr.u.Unix.uid == getuid();
1710 return RTEXITCODE_SUCCESS;
1711# endif
1712}
1713
1714#endif /* WITH_ELEVATION */
1715
1716int main(int argc, char **argv)
1717{
1718 /*
1719 * Initialize IPRT and check that we're correctly installed.
1720 */
1721 int rc = RTR3Init();
1722 if (RT_FAILURE(rc))
1723 return RTMsgInitFailure(rc);
1724
1725 RTERRINFOSTATIC ErrInfo;
1726 RTErrInfoInitStatic(&ErrInfo);
1727 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core);
1728 if (RT_FAILURE(rc))
1729 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg);
1730
1731 /*
1732 * Elevation check.
1733 */
1734 const char *pszDisplayInfoHack = NULL;
1735 RTEXITCODE rcExit;
1736#ifdef WITH_ELEVATION
1737 bool fElevated;
1738 rcExit = ElevationCheck(&fElevated);
1739 if (rcExit != RTEXITCODE_SUCCESS)
1740 return rcExit;
1741#endif
1742
1743 /*
1744 * Parse the top level arguments until we find a command.
1745 */
1746 static const RTGETOPTDEF s_aOptions[] =
1747 {
1748 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1749 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1750 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1751#ifdef WITH_ELEVATION
1752 { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
1753 { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
1754 { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
1755#endif
1756 { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING },
1757 };
1758 RTGETOPTSTATE GetState;
1759 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1760 if (RT_FAILURE(rc))
1761 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1762 for (;;)
1763 {
1764 RTGETOPTUNION ValueUnion;
1765 int ch = RTGetOpt(&GetState, &ValueUnion);
1766 switch (ch)
1767 {
1768 case 0:
1769 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1770
1771 case CMD_INSTALL:
1772 case CMD_UNINSTALL:
1773 case CMD_CLEANUP:
1774 {
1775#ifdef WITH_ELEVATION
1776 if (!fElevated)
1777 return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack);
1778#endif
1779 int cCmdargs = argc - GetState.iNext;
1780 char **papszCmdArgs = argv + GetState.iNext;
1781 switch (ch)
1782 {
1783 case CMD_INSTALL:
1784 rcExit = DoInstall( cCmdargs, papszCmdArgs);
1785 break;
1786 case CMD_UNINSTALL:
1787 rcExit = DoUninstall(cCmdargs, papszCmdArgs);
1788 break;
1789 case CMD_CLEANUP:
1790 rcExit = DoCleanup( cCmdargs, papszCmdArgs);
1791 break;
1792 default:
1793 AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
1794 }
1795
1796 /*
1797 * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
1798 * success since the exit code may otherwise get lost in the
1799 * process elevation fun.
1800 */
1801 RTStrmFlush(g_pStdOut);
1802 RTStrmFlush(g_pStdErr);
1803 switch (rcExit)
1804 {
1805 case RTEXITCODE_SUCCESS:
1806 RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
1807 break;
1808 default:
1809 RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
1810 break;
1811 }
1812 RTStrmFlush(g_pStdErr);
1813 RTStrmFlush(g_pStdOut);
1814 return rcExit;
1815 }
1816
1817#ifdef WITH_ELEVATION
1818 case OPT_ELEVATED:
1819 fElevated = true;
1820 break;
1821
1822 case OPT_STDERR:
1823 case OPT_STDOUT:
1824 {
1825 FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
1826 if (!pFile)
1827 {
1828 rc = RTErrConvertFromErrno(errno);
1829 return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
1830 }
1831 break;
1832 }
1833#endif
1834
1835 case OPT_DISP_INFO_HACK:
1836 if (pszDisplayInfoHack)
1837 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once");
1838 pszDisplayInfoHack = ValueUnion.psz;
1839 break;
1840
1841 case 'h':
1842 case 'V':
1843 return DoStandardOption(ch);
1844
1845 default:
1846 return RTGetOptPrintError(ch, &ValueUnion);
1847 }
1848 /* not currently reached */
1849 }
1850 /* not reached */
1851}
1852
1853
1854#ifdef RT_OS_WINDOWS
1855extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1856{
1857 g_hInstance = hInstance;
1858 NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine);
1859 return main(__argc, __argv);
1860}
1861#endif
1862
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