VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 23 months ago

Copyright year updates by scm.

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