VirtualBox

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

Last change on this file since 55671 was 55671, checked in by vboxsync, 10 years ago

RTGetOptArgvFromString: Added fFlags parameter for selecting the quoting style.

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

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