VirtualBox

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

Last change on this file since 73768 was 69753, checked in by vboxsync, 7 years ago

iprt/dir: Morphing PRTDIR into a handle named RTDIR. (Been wanting to correct this for years. Don't know why I makde it a pointer rather than an abstrct handle like everything else.)

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

© 2023 Oracle
ContactPrivacy policyTerms of Use