VirtualBox

source: vbox/trunk/src/VBox/Main/VBoxExtPackHelperApp.cpp@ 35188

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

Use RTERRINFO in SUPLib for hardening APIs and such.

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

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