VirtualBox

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

Last change on this file since 76454 was 76409, checked in by vboxsync, 6 years ago

iprt/string.h: Dropped including utf16.h and let those who need it include it themselves. bugref:9344

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