VirtualBox

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

Last change on this file since 83640 was 82968, checked in by vboxsync, 5 years ago

Copyright year updates by scm.

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