VirtualBox

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

Last change on this file since 62875 was 62875, checked in by vboxsync, 8 years ago

Main: gcc warnings

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