VirtualBox

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

Last change on this file since 92154 was 83820, checked in by vboxsync, 4 years ago

Main: VC++ 14.1 adjustments and warnings. bugref:8489

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 74.0 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 83820 2020-04-19 01:08:36Z 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 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1347 {
1348 TranslateMessage(&Msg);
1349 DispatchMessageW(&Msg);
1350 }
1351 }
1352
1353 DWORD dwExitCode;
1354 if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
1355 {
1356 if (dwExitCode < 128)
1357 rcExit = (RTEXITCODE)dwExitCode;
1358 else
1359 rcExit = RTEXITCODE_FAILURE;
1360 }
1361 CloseHandle(Info.hProcess);
1362 }
1363 else
1364 RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
1365 }
1366 else
1367 RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
1368
1369
1370 RTUtf16Free((PRTUTF16)Info.lpParameters);
1371 }
1372 RTStrFree(pszCmdLine);
1373 }
1374
1375 RTUtf16Free((PRTUTF16)Info.lpFile);
1376 }
1377 else
1378 RTMsgError("RTStrToUtf16 failed: %Rc", rc);
1379
1380#elif defined(RT_OS_DARWIN)
1381 RT_NOREF(pszDisplayInfoHack);
1382 char szIconName[RTPATH_MAX];
1383 int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
1384 if (RT_SUCCESS(rc))
1385 rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
1386 if (RT_FAILURE(rc))
1387 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
1388
1389 AuthorizationRef AuthRef;
1390 OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
1391 if (orc == errAuthorizationSuccess)
1392 {
1393 /*
1394 * Preautorize the privileged execution of ourselves.
1395 */
1396 AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
1397 AuthorizationRights AuthRights = { 1, &AuthItem };
1398
1399 NOREF(iCmd);
1400 static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
1401 AuthorizationItem aAuthEnvItems[] =
1402 {
1403 { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
1404 { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
1405 };
1406 AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
1407
1408 orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
1409 kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
1410 | kAuthorizationFlagExtendRights,
1411 NULL);
1412 if (orc == errAuthorizationSuccess)
1413 {
1414 /*
1415 * Execute with extra permissions
1416 */
1417 FILE *pSocketStrm;
1418#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
1419# pragma GCC diagnostic push
1420# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1421#endif
1422 orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
1423 (char * const *)&papszArgs[cSuArgs + 3],
1424 &pSocketStrm);
1425#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
1426# pragma GCC diagnostic pop
1427#endif
1428 if (orc == errAuthorizationSuccess)
1429 {
1430 /*
1431 * Read the output of the tool, the read will fail when it quits.
1432 */
1433 for (;;)
1434 {
1435 char achBuf[1024];
1436 size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
1437 if (!cbRead)
1438 break;
1439 fwrite(achBuf, 1, cbRead, stdout);
1440 }
1441 rcExit = RTEXITCODE_SUCCESS;
1442 fclose(pSocketStrm);
1443 }
1444 else
1445 RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
1446 }
1447 else if (orc == errAuthorizationCanceled)
1448 RTMsgError("Authorization canceled by the user");
1449 else
1450 RTMsgError("AuthorizationCopyRights failed: %d", orc);
1451 AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
1452 }
1453 else
1454 RTMsgError("AuthorizationCreate failed: %d", orc);
1455
1456#else
1457
1458 RT_NOREF2(pszExecPath, pszDisplayInfoHack);
1459
1460 /*
1461 * Several of the alternatives below will require a command line.
1462 */
1463 char *pszCmdLine;
1464 int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1465 if (RT_FAILURE(rc))
1466 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc", rc);
1467
1468 /*
1469 * Look for various standard stuff for executing a program as root.
1470 *
1471 * N.B. When adding new arguments, please make 100% sure RelaunchElevated
1472 * allocates enough array entries.
1473 *
1474 * TODO: Feel free to contribute code for using PolicyKit directly.
1475 */
1476 bool fHaveDisplayVar = RTEnvExist("DISPLAY");
1477 int iSuArg = cSuArgs;
1478 char szExecTool[260];
1479 char szXterm[260];
1480
1481 /*
1482 * kdesudo is available on KDE3/KDE4
1483 */
1484 if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo"))
1485 {
1486 iSuArg = cSuArgs - 4;
1487 papszArgs[cSuArgs - 4] = szExecTool;
1488 papszArgs[cSuArgs - 3] = "--comment";
1489 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1490 ? "VirtualBox extension pack installer"
1491 : iCmd == CMD_UNINSTALL
1492 ? "VirtualBox extension pack uninstaller"
1493 : "VirtualBox extension pack maintainer";
1494 papszArgs[cSuArgs - 1] = "--";
1495 }
1496 /*
1497 * gksu is our favorite as it is very well integrated.
1498 */
1499 else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu"))
1500 {
1501#if 0 /* older gksu does not grok --description nor '--' and multiple args. */
1502 iSuArg = cSuArgs - 4;
1503 papszArgs[cSuArgs - 4] = szExecTool;
1504 papszArgs[cSuArgs - 3] = "--description";
1505 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1506 ? "VirtualBox extension pack installer"
1507 : iCmd == CMD_UNINSTALL
1508 ? "VirtualBox extension pack uninstaller"
1509 : "VirtualBox extension pack maintainer";
1510 papszArgs[cSuArgs - 1] = "--";
1511#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */
1512 iSuArg = cSuArgs - 4;
1513 papszArgs[cSuArgs - 4] = szExecTool;
1514 papszArgs[cSuArgs - 3] = "-au";
1515 papszArgs[cSuArgs - 2] = "root";
1516 papszArgs[cSuArgs - 1] = pszCmdLine;
1517 papszArgs[cSuArgs] = NULL;
1518#else
1519 iSuArg = cSuArgs - 2;
1520 papszArgs[cSuArgs - 2] = szExecTool;
1521 papszArgs[cSuArgs - 1] = pszCmdLine;
1522 papszArgs[cSuArgs] = NULL;
1523#endif
1524 }
1525 /*
1526 * pkexec may work for ssh console sessions as well if the right agents
1527 * are installed. However it is very generic and does not allow for any
1528 * custom messages. Thus it comes after gksu.
1529 */
1530 else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec"))
1531 {
1532 iSuArg = cSuArgs - 1;
1533 papszArgs[cSuArgs - 1] = szExecTool;
1534 }
1535 /*
1536 * The ultimate fallback is running 'su -' within an xterm. We use the
1537 * title of the xterm to tell what is going on.
1538 */
1539 else if ( fHaveDisplayVar
1540 && FindExecTool(szExecTool, sizeof(szExecTool), "su")
1541 && FindExecTool(szXterm, sizeof(szXterm), "xterm"))
1542 {
1543 iSuArg = cSuArgs - 9;
1544 papszArgs[cSuArgs - 9] = szXterm;
1545 papszArgs[cSuArgs - 8] = "-T";
1546 papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL
1547 ? "VirtualBox extension pack installer - su"
1548 : iCmd == CMD_UNINSTALL
1549 ? "VirtualBox extension pack uninstaller - su"
1550 : "VirtualBox extension pack maintainer - su";
1551 papszArgs[cSuArgs - 6] = "-e";
1552 papszArgs[cSuArgs - 5] = szExecTool;
1553 papszArgs[cSuArgs - 4] = "-";
1554 papszArgs[cSuArgs - 3] = "root";
1555 papszArgs[cSuArgs - 2] = "-c";
1556 papszArgs[cSuArgs - 1] = pszCmdLine;
1557 papszArgs[cSuArgs] = NULL;
1558 }
1559 else if (fHaveDisplayVar)
1560 RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root");
1561 else
1562 RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root");
1563 if (iSuArg != cSuArgs)
1564 {
1565 AssertRelease(iSuArg >= 0);
1566
1567 /*
1568 * Argument list constructed, execute it and wait for the exec
1569 * program to complete.
1570 */
1571 RTPROCESS hProcess;
1572 rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdIn*/,
1573 NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, NULL /* pvExtraData*/,
1574 &hProcess);
1575 if (RT_SUCCESS(rc))
1576 {
1577 RTPROCSTATUS Status;
1578 rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status);
1579 if (RT_SUCCESS(rc))
1580 {
1581 if (Status.enmReason == RTPROCEXITREASON_NORMAL)
1582 rcExit = (RTEXITCODE)Status.iStatus;
1583 else
1584 rcExit = RTEXITCODE_FAILURE;
1585 }
1586 else
1587 RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc);
1588 }
1589 else
1590 RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc);
1591 }
1592 RTStrFree(pszCmdLine);
1593
1594#endif
1595 return rcExit;
1596}
1597
1598
1599/**
1600 * Relaunches ourselves as a elevated process using platform specific facilities.
1601 *
1602 * @returns Program exit code.
1603 * @param argc The number of arguments.
1604 * @param argv The arguments.
1605 * @param iCmd The command that is being executed.
1606 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1607 */
1608static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack)
1609{
1610 /*
1611 * We need the executable name later, so get it now when it's easy to quit.
1612 */
1613 char szExecPath[RTPATH_MAX];
1614 if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
1615 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1616
1617 /*
1618 * Create a couple of temporary files for stderr and stdout.
1619 */
1620 char szTempDir[RTPATH_MAX - sizeof("/stderr")];
1621 int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
1622 if (RT_FAILURE(rc))
1623 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
1624 rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
1625 if (RT_FAILURE(rc))
1626 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
1627 rc = RTDirCreateTemp(szTempDir, 0700);
1628 if (RT_FAILURE(rc))
1629 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
1630
1631 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1632 char szStdOut[RTPATH_MAX];
1633 char szStdErr[RTPATH_MAX];
1634 rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
1635 if (RT_SUCCESS(rc))
1636 rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
1637 if (RT_SUCCESS(rc))
1638 {
1639 RTFILE hStdOut;
1640 rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1641 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1642 if (RT_SUCCESS(rc))
1643 {
1644 RTFILE hStdErr;
1645 rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1646 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1647 if (RT_SUCCESS(rc))
1648 {
1649 /*
1650 * Insert the --elevated and stdout/err names into the argument
1651 * list. Note that darwin skips the --stdout bit, so don't
1652 * change the order here.
1653 */
1654 int const cSuArgs = 12;
1655 int cArgs = argc + 5 + 1;
1656 char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *));
1657 if (papszArgs)
1658 {
1659 int iDst = cSuArgs;
1660 papszArgs[iDst++] = argv[0];
1661 papszArgs[iDst++] = "--stdout";
1662 papszArgs[iDst++] = szStdOut;
1663 papszArgs[iDst++] = "--stderr";
1664 papszArgs[iDst++] = szStdErr;
1665 papszArgs[iDst++] = "--elevated";
1666 for (int iSrc = 1; iSrc <= argc; iSrc++)
1667 papszArgs[iDst++] = argv[iSrc];
1668
1669 /*
1670 * Do the platform specific process execution (waiting included).
1671 */
1672 rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack);
1673
1674 /*
1675 * Copy the standard files to our standard handles.
1676 */
1677 CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
1678 CopyFileToStdXxx(hStdOut, g_pStdOut, false);
1679
1680 RTMemTmpFree(papszArgs);
1681 }
1682
1683 RTFileClose(hStdErr);
1684 RTFileDelete(szStdErr);
1685 }
1686 RTFileClose(hStdOut);
1687 RTFileDelete(szStdOut);
1688 }
1689 }
1690 RTDirRemove(szTempDir);
1691
1692 return rcExit;
1693}
1694
1695
1696/**
1697 * Checks if the process is elevated or not.
1698 *
1699 * @returns RTEXITCODE_SUCCESS if preconditions are fine,
1700 * otherwise error message + RTEXITCODE_FAILURE.
1701 * @param pfElevated Where to store the elevation indicator.
1702 */
1703static RTEXITCODE ElevationCheck(bool *pfElevated)
1704{
1705 *pfElevated = false;
1706
1707# if defined(RT_OS_WINDOWS)
1708 /** @todo This should probably check if UAC is diabled and if we are
1709 * Administrator first. Also needs to check for Vista+ first, probably.
1710 */
1711 DWORD cb;
1712 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1713 HANDLE hToken;
1714 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
1715 return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
1716
1717 /*
1718 * Check if we're member of the Administrators group. If we aren't, there
1719 * is no way to elevate ourselves to system admin.
1720 * N.B. CheckTokenMembership does not do the job here (due to attributes?).
1721 */
1722 BOOL fIsAdmin = FALSE;
1723 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
1724 PSID pAdminGrpSid;
1725 if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
1726 {
1727# ifdef DEBUG
1728 char *pszAdminGrpSid = NULL;
1729 ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
1730# endif
1731
1732 if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
1733 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1734 {
1735 PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
1736 if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
1737 {
1738 for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
1739 {
1740# ifdef DEBUG
1741 char *pszGrpSid = NULL;
1742 ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
1743# endif
1744 if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
1745 {
1746 /* That it's listed is enough I think, ignore attributes. */
1747 fIsAdmin = TRUE;
1748 break;
1749 }
1750 }
1751 }
1752 else
1753 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
1754 RTMemFree(pTokenGroups);
1755 }
1756 else
1757 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
1758
1759 FreeSid(pAdminGrpSid);
1760 }
1761 else
1762 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
1763 if (fIsAdmin)
1764 {
1765 /*
1766 * Check the integrity level (Vista / UAC).
1767 */
1768# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
1769# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
1770 if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
1771 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1772 {
1773 PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
1774 if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
1775 {
1776 DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
1777
1778 if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
1779 *pfElevated = true;
1780 }
1781 else
1782 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1783 RTMemFree(pSidAndAttr);
1784 }
1785 else if ( GetLastError() == ERROR_INVALID_PARAMETER
1786 || GetLastError() == ERROR_NOT_SUPPORTED)
1787 *pfElevated = true; /* Older Windows version. */
1788 else
1789 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1790 }
1791 else
1792 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
1793
1794 CloseHandle(hToken);
1795 return rcExit;
1796
1797# else
1798 /*
1799 * On Unixy systems, we check if the executable and the current user is
1800 * the same. This heuristic works fine for both hardened and development
1801 * builds.
1802 */
1803 char szExecPath[RTPATH_MAX];
1804 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
1805 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1806
1807 RTFSOBJINFO ObjInfo;
1808 int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
1809 if (RT_FAILURE(rc))
1810 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
1811
1812 *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
1813 || ObjInfo.Attr.u.Unix.uid == getuid();
1814 return RTEXITCODE_SUCCESS;
1815# endif
1816}
1817
1818#endif /* WITH_ELEVATION */
1819
1820int main(int argc, char **argv)
1821{
1822 /*
1823 * Initialize IPRT and check that we're correctly installed.
1824 */
1825#ifdef RT_OS_WINDOWS
1826 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_UTF8_ARGV); /* WinMain gives us UTF-8, see below. */
1827#else
1828 int rc = RTR3InitExe(argc, &argv, 0);
1829#endif
1830 if (RT_FAILURE(rc))
1831 return RTMsgInitFailure(rc);
1832
1833 SUPR3HardenedVerifyInit();
1834 RTERRINFOSTATIC ErrInfo;
1835 RTErrInfoInitStatic(&ErrInfo);
1836 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core);
1837 if (RT_FAILURE(rc))
1838 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg);
1839
1840 /*
1841 * Elevation check.
1842 */
1843 const char *pszDisplayInfoHack = NULL;
1844 RTEXITCODE rcExit;
1845#ifdef WITH_ELEVATION
1846 bool fElevated;
1847 rcExit = ElevationCheck(&fElevated);
1848 if (rcExit != RTEXITCODE_SUCCESS)
1849 return rcExit;
1850#endif
1851
1852 /*
1853 * Parse the top level arguments until we find a command.
1854 */
1855 static const RTGETOPTDEF s_aOptions[] =
1856 {
1857 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1858 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1859 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1860#ifdef WITH_ELEVATION
1861 { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
1862 { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
1863 { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
1864#endif
1865 { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING },
1866 };
1867 RTGETOPTSTATE GetState;
1868 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1869 if (RT_FAILURE(rc))
1870 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1871 for (;;)
1872 {
1873 RTGETOPTUNION ValueUnion;
1874 int ch = RTGetOpt(&GetState, &ValueUnion);
1875 switch (ch)
1876 {
1877 case 0:
1878 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1879
1880 case CMD_INSTALL:
1881 case CMD_UNINSTALL:
1882 case CMD_CLEANUP:
1883 {
1884#ifdef WITH_ELEVATION
1885 if (!fElevated)
1886 return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack);
1887#endif
1888 int cCmdargs = argc - GetState.iNext;
1889 char **papszCmdArgs = argv + GetState.iNext;
1890 switch (ch)
1891 {
1892 case CMD_INSTALL:
1893 rcExit = DoInstall( cCmdargs, papszCmdArgs);
1894 break;
1895 case CMD_UNINSTALL:
1896 rcExit = DoUninstall(cCmdargs, papszCmdArgs);
1897 break;
1898 case CMD_CLEANUP:
1899 rcExit = DoCleanup( cCmdargs, papszCmdArgs);
1900 break;
1901 default:
1902 AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
1903 }
1904
1905 /*
1906 * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
1907 * success since the exit code may otherwise get lost in the
1908 * process elevation fun.
1909 */
1910 RTStrmFlush(g_pStdOut);
1911 RTStrmFlush(g_pStdErr);
1912 switch (rcExit)
1913 {
1914 case RTEXITCODE_SUCCESS:
1915 RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
1916 break;
1917 default:
1918 RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
1919 break;
1920 }
1921 RTStrmFlush(g_pStdErr);
1922 RTStrmFlush(g_pStdOut);
1923 return rcExit;
1924 }
1925
1926#ifdef WITH_ELEVATION
1927 case OPT_ELEVATED:
1928 fElevated = true;
1929 break;
1930
1931 case OPT_STDERR:
1932 case OPT_STDOUT:
1933 {
1934# ifdef RT_OS_WINDOWS
1935 PRTUTF16 pwszName = NULL;
1936 rc = RTStrToUtf16(ValueUnion.psz, &pwszName);
1937 if (RT_FAILURE(rc))
1938 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error converting '%s' to UTF-16: %Rrc\n", ValueUnion.psz, rc);
1939 FILE *pFile = _wfreopen(pwszName, L"r+", ch == OPT_STDOUT ? stdout : stderr);
1940 RTUtf16Free(pwszName);
1941# else
1942 FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
1943# endif
1944 if (!pFile)
1945 {
1946 rc = RTErrConvertFromErrno(errno);
1947 return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
1948 }
1949 break;
1950 }
1951#endif
1952
1953 case OPT_DISP_INFO_HACK:
1954 if (pszDisplayInfoHack)
1955 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once");
1956 pszDisplayInfoHack = ValueUnion.psz;
1957 break;
1958
1959 case 'h':
1960 case 'V':
1961 return DoStandardOption(ch);
1962
1963 default:
1964 return RTGetOptPrintError(ch, &ValueUnion);
1965 }
1966 /* not currently reached */
1967 }
1968 /* not reached */
1969}
1970
1971
1972#ifdef RT_OS_WINDOWS
1973extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1974{
1975 g_hInstance = hInstance;
1976 NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine);
1977
1978 int rc = RTR3InitExeNoArguments(0);
1979 if (RT_FAILURE(rc))
1980 return RTMsgInitFailure(rc);
1981
1982 LPWSTR pwszCmdLine = GetCommandLineW();
1983 if (!pwszCmdLine)
1984 return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed");
1985
1986 char *pszCmdLine;
1987 rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */
1988 if (RT_FAILURE(rc))
1989 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc);
1990
1991 int cArgs;
1992 char **papszArgs;
1993 rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL);
1994 if (RT_SUCCESS(rc))
1995 {
1996
1997 rc = main(cArgs, papszArgs);
1998
1999 RTGetOptArgvFree(papszArgs);
2000 }
2001 else
2002 rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc);
2003 RTStrFree(pszCmdLine);
2004
2005 return rc;
2006}
2007#endif
2008
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use