VirtualBox

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

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

Main,VBoxManage,FE/Qt: Implemented IExtPackFile and dropped IExtPackManager::Install.

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