VirtualBox

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

Last change on this file since 78395 was 77178, checked in by vboxsync, 6 years ago

VBoxExtPackHelperApp.cpp: Uninstall must handle --forced option.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 73.4 KB
Line 
1/* $Id: VBoxExtPackHelperApp.cpp 77178 2019-02-06 15:15:30Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root.
4 */
5
6/*
7 * Copyright (C) 2010-2019 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 { "--forced", 'f', RTGETOPT_REQ_NOTHING },
951 };
952 RTGETOPTSTATE GetState;
953 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
954 if (RT_FAILURE(rc))
955 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
956
957 const char *pszBaseDir = NULL;
958 const char *pszName = NULL;
959 RTGETOPTUNION ValueUnion;
960 int ch;
961 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
962 {
963 switch (ch)
964 {
965 case 'b':
966 if (pszBaseDir)
967 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
968 pszBaseDir = ValueUnion.psz;
969 if (!IsValidBaseDir(pszBaseDir))
970 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
971 break;
972
973 case 'n':
974 if (pszName)
975 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options");
976 pszName = ValueUnion.psz;
977 if (!VBoxExtPackIsValidName(pszName))
978 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName);
979 break;
980
981 case 'f':
982 /* ignored */
983 break;
984
985 case 'h':
986 case 'V':
987 return DoStandardOption(ch);
988
989 default:
990 return RTGetOptPrintError(ch, &ValueUnion);
991 }
992 }
993 if (!pszName)
994 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option");
995 if (!pszBaseDir)
996 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
997
998 /*
999 * Mangle the name so we can construct the directory names.
1000 */
1001 RTCString *pstrMangledName = VBoxExtPackMangleName(pszName);
1002 if (!pstrMangledName)
1003 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName);
1004 RTCString strMangledName(*pstrMangledName);
1005 delete pstrMangledName;
1006
1007 /*
1008 * Ok, down to business.
1009 */
1010 /* Check that it exists. */
1011 char szExtPackDir[RTPATH_MAX];
1012 rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str());
1013 if (RT_FAILURE(rc))
1014 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc);
1015
1016 if (!RTDirExists(szExtPackDir))
1017 {
1018 RTMsgInfo("Extension pack not installed. Nothing to do.");
1019 return RTEXITCODE_SUCCESS;
1020 }
1021
1022 RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir);
1023 if (rcExit == RTEXITCODE_SUCCESS)
1024 RTMsgInfo("Successfully removed extension pack '%s'\n", pszName);
1025
1026 return rcExit;
1027}
1028
1029/**
1030 * Implements the 'cleanup' command.
1031 *
1032 * @returns The program exit code.
1033 * @param argc The number of program arguments.
1034 * @param argv The program arguments.
1035 */
1036static RTEXITCODE DoCleanup(int argc, char **argv)
1037{
1038 /*
1039 * Parse the parameters.
1040 *
1041 * Note! The --base-dir is only for checking that the caller and this help
1042 * applications have the same idea of where things are.
1043 */
1044 static const RTGETOPTDEF s_aOptions[] =
1045 {
1046 { "--base-dir", 'b', RTGETOPT_REQ_STRING },
1047 };
1048 RTGETOPTSTATE GetState;
1049 int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/);
1050 if (RT_FAILURE(rc))
1051 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1052
1053 const char *pszBaseDir = NULL;
1054 RTGETOPTUNION ValueUnion;
1055 int ch;
1056 while ((ch = RTGetOpt(&GetState, &ValueUnion)))
1057 {
1058 switch (ch)
1059 {
1060 case 'b':
1061 if (pszBaseDir)
1062 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options");
1063 pszBaseDir = ValueUnion.psz;
1064 if (!IsValidBaseDir(pszBaseDir))
1065 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir);
1066 break;
1067
1068 case 'h':
1069 case 'V':
1070 return DoStandardOption(ch);
1071
1072 default:
1073 return RTGetOptPrintError(ch, &ValueUnion);
1074 }
1075 }
1076 if (!pszBaseDir)
1077 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option");
1078
1079 /*
1080 * Ok, down to business.
1081 */
1082 RTDIR hDir;
1083 rc = RTDirOpen(&hDir, pszBaseDir);
1084 if (RT_FAILURE(rc))
1085 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir);
1086
1087 uint32_t cCleaned = 0;
1088 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1089 for (;;)
1090 {
1091 RTDIRENTRYEX Entry;
1092 rc = RTDirReadEx(hDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK);
1093 if (RT_FAILURE(rc))
1094 {
1095 if (rc != VERR_NO_MORE_FILES)
1096 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc);
1097 break;
1098 }
1099
1100 /*
1101 * Only directories which conform with our temporary install/uninstall
1102 * naming scheme are candidates for cleaning.
1103 */
1104 if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode)
1105 && strcmp(Entry.szName, ".") != 0
1106 && strcmp(Entry.szName, "..") != 0)
1107 {
1108 bool fCandidate = false;
1109 char *pszMarker = strstr(Entry.szName, "-_-");
1110 if ( pszMarker
1111 && ( !strcmp(pszMarker, "-_-uninst")
1112 || !strncmp(pszMarker, RT_STR_TUPLE("-_-inst"))))
1113 fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]);
1114 if (fCandidate)
1115 {
1116 /*
1117 * Recursive delete, safe.
1118 */
1119 char szPath[RTPATH_MAX];
1120 rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName);
1121 if (RT_SUCCESS(rc))
1122 {
1123 RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/);
1124 if (rcExit2 == RTEXITCODE_SUCCESS)
1125 RTMsgInfo("Successfully removed '%s'.", Entry.szName);
1126 else if (rcExit == RTEXITCODE_SUCCESS)
1127 rcExit = rcExit2;
1128 }
1129 else
1130 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName);
1131 cCleaned++;
1132 }
1133 }
1134 }
1135 RTDirClose(hDir);
1136 if (!cCleaned)
1137 RTMsgInfo("Nothing to clean.");
1138 return rcExit;
1139}
1140
1141#ifdef WITH_ELEVATION
1142
1143#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN)
1144/**
1145 * Looks in standard locations for a suitable exec tool.
1146 *
1147 * @returns true if found, false if not.
1148 * @param pszPath Where to store the path to the tool on
1149 * successs.
1150 * @param cbPath The size of the buffer @a pszPath points to.
1151 * @param pszName The name of the tool we're looking for.
1152 */
1153static bool FindExecTool(char *pszPath, size_t cbPath, const char *pszName)
1154{
1155 static const char * const s_apszPaths[] =
1156 {
1157 "/bin",
1158 "/usr/bin",
1159 "/usr/local/bin",
1160 "/sbin",
1161 "/usr/sbin",
1162 "/usr/local/sbin",
1163#ifdef RT_OS_SOLARIS
1164 "/usr/sfw/bin",
1165 "/usr/gnu/bin",
1166 "/usr/xpg4/bin",
1167 "/usr/xpg6/bin",
1168 "/usr/openwin/bin",
1169 "/usr/ucb"
1170#endif
1171 };
1172
1173 for (unsigned i = 0; i < RT_ELEMENTS(s_apszPaths); i++)
1174 {
1175 int rc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName);
1176 if (RT_SUCCESS(rc))
1177 {
1178 RTFSOBJINFO ObjInfo;
1179 rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK);
1180 if (RT_SUCCESS(rc))
1181 {
1182 if (!(ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH))
1183 return true;
1184 }
1185 }
1186 }
1187 return false;
1188}
1189#endif
1190
1191
1192/**
1193 * Copies the content of a file to a stream.
1194 *
1195 * @param hSrc The source file.
1196 * @param pDst The destination stream.
1197 * @param fComplain Whether to complain about errors (i.e. is this
1198 * stderr, if not keep the trap shut because it
1199 * may be missing when running under VBoxSVC.)
1200 */
1201static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain)
1202{
1203 int rc;
1204 for (;;)
1205 {
1206 char abBuf[0x1000];
1207 size_t cbRead;
1208 rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead);
1209 if (RT_FAILURE(rc))
1210 {
1211 RTMsgError("RTFileRead failed: %Rrc", rc);
1212 break;
1213 }
1214 if (!cbRead)
1215 break;
1216 rc = RTStrmWrite(pDst, abBuf, cbRead);
1217 if (RT_FAILURE(rc))
1218 {
1219 if (fComplain)
1220 RTMsgError("RTStrmWrite failed: %Rrc", rc);
1221 break;
1222 }
1223 }
1224 rc = RTStrmFlush(pDst);
1225 if (RT_FAILURE(rc) && fComplain)
1226 RTMsgError("RTStrmFlush failed: %Rrc", rc);
1227}
1228
1229
1230/**
1231 * Relaunches ourselves as a elevated process using platform specific facilities.
1232 *
1233 * @returns Program exit code.
1234 * @param pszExecPath The executable path.
1235 * @param papszArgs The arguments.
1236 * @param cSuArgs The number of argument entries reserved for the
1237 * 'su' like programs at the start of papszArgs.
1238 * @param cMyArgs The number of arguments following @a cSuArgs.
1239 * @param iCmd The command that is being executed. (For
1240 * selecting messages.)
1241 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1242 */
1243static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, const char **papszArgs, int cSuArgs, int cMyArgs,
1244 int iCmd, const char *pszDisplayInfoHack)
1245{
1246 RT_NOREF1(cMyArgs);
1247 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1248#ifdef RT_OS_WINDOWS
1249 NOREF(iCmd);
1250
1251 MSG Msg;
1252 PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE);
1253 CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
1254
1255 SHELLEXECUTEINFOW Info;
1256
1257 Info.cbSize = sizeof(Info);
1258 Info.fMask = SEE_MASK_NOCLOSEPROCESS;
1259 Info.hwnd = NULL;
1260 Info.lpVerb = L"runas";
1261 int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile);
1262 if (RT_SUCCESS(rc))
1263 {
1264 char *pszCmdLine;
1265 rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT);
1266 if (RT_SUCCESS(rc))
1267 {
1268 rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters);
1269 if (RT_SUCCESS(rc))
1270 {
1271 Info.lpDirectory = NULL;
1272 Info.nShow = SW_SHOWMAXIMIZED;
1273 Info.hInstApp = NULL;
1274 Info.lpIDList = NULL;
1275 Info.lpClass = NULL;
1276 Info.hkeyClass = NULL;
1277 Info.dwHotKey = 0;
1278 Info.hMonitor = NULL;
1279 Info.hProcess = INVALID_HANDLE_VALUE;
1280
1281 /* Apply display hacks. */
1282 if (pszDisplayInfoHack)
1283 {
1284 const char *pszArg = strstr(pszDisplayInfoHack, "hwnd=");
1285 if (pszArg)
1286 {
1287 uint64_t u64Hwnd;
1288 rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd);
1289 if (RT_SUCCESS(rc))
1290 {
1291 HWND hwnd = (HWND)(uintptr_t)u64Hwnd;
1292 Info.hwnd = hwnd;
1293 Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY);
1294 }
1295 }
1296 }
1297 if (Info.hMonitor == NULL)
1298 {
1299 POINT Pt = {0,0};
1300 Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY);
1301 }
1302 if (Info.hMonitor != NULL)
1303 Info.fMask |= SEE_MASK_HMONITOR;
1304
1305 if (ShellExecuteExW(&Info))
1306 {
1307 if (Info.hProcess != INVALID_HANDLE_VALUE)
1308 {
1309 /*
1310 * Wait for the process, make sure the deal with messages.
1311 */
1312 for (;;)
1313 {
1314 DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS);
1315 if (dwRc == WAIT_OBJECT_0)
1316 break;
1317 if ( dwRc != WAIT_TIMEOUT
1318 && dwRc != WAIT_OBJECT_0 + 1)
1319 {
1320 RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError());
1321 break;
1322 }
1323 MSG Msg;
1324 while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE))
1325 {
1326 TranslateMessage(&Msg);
1327 DispatchMessageW(&Msg);
1328 }
1329 }
1330
1331 DWORD dwExitCode;
1332 if (GetExitCodeProcess(Info.hProcess, &dwExitCode))
1333 {
1334 if (dwExitCode < 128)
1335 rcExit = (RTEXITCODE)dwExitCode;
1336 else
1337 rcExit = RTEXITCODE_FAILURE;
1338 }
1339 CloseHandle(Info.hProcess);
1340 }
1341 else
1342 RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess");
1343 }
1344 else
1345 RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError());
1346
1347
1348 RTUtf16Free((PRTUTF16)Info.lpParameters);
1349 }
1350 RTStrFree(pszCmdLine);
1351 }
1352
1353 RTUtf16Free((PRTUTF16)Info.lpFile);
1354 }
1355 else
1356 RTMsgError("RTStrToUtf16 failed: %Rc", rc);
1357
1358#elif defined(RT_OS_DARWIN)
1359 RT_NOREF(pszDisplayInfoHack);
1360 char szIconName[RTPATH_MAX];
1361 int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName));
1362 if (RT_SUCCESS(rc))
1363 rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png");
1364 if (RT_FAILURE(rc))
1365 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc);
1366
1367 AuthorizationRef AuthRef;
1368 OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef);
1369 if (orc == errAuthorizationSuccess)
1370 {
1371 /*
1372 * Preautorize the privileged execution of ourselves.
1373 */
1374 AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 };
1375 AuthorizationRights AuthRights = { 1, &AuthItem };
1376
1377 NOREF(iCmd);
1378 static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n";
1379 AuthorizationItem aAuthEnvItems[] =
1380 {
1381 { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 },
1382 { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 }
1383 };
1384 AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems };
1385
1386 orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv,
1387 kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed
1388 | kAuthorizationFlagExtendRights,
1389 NULL);
1390 if (orc == errAuthorizationSuccess)
1391 {
1392 /*
1393 * Execute with extra permissions
1394 */
1395 FILE *pSocketStrm;
1396#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
1397# pragma GCC diagnostic push
1398# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
1399#endif
1400 orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults,
1401 (char * const *)&papszArgs[cSuArgs + 3],
1402 &pSocketStrm);
1403#if defined(__clang__) || RT_GNUC_PREREQ(4, 4)
1404# pragma GCC diagnostic pop
1405#endif
1406 if (orc == errAuthorizationSuccess)
1407 {
1408 /*
1409 * Read the output of the tool, the read will fail when it quits.
1410 */
1411 for (;;)
1412 {
1413 char achBuf[1024];
1414 size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm);
1415 if (!cbRead)
1416 break;
1417 fwrite(achBuf, 1, cbRead, stdout);
1418 }
1419 rcExit = RTEXITCODE_SUCCESS;
1420 fclose(pSocketStrm);
1421 }
1422 else
1423 RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc);
1424 }
1425 else if (orc == errAuthorizationCanceled)
1426 RTMsgError("Authorization canceled by the user");
1427 else
1428 RTMsgError("AuthorizationCopyRights failed: %d", orc);
1429 AuthorizationFree(AuthRef, kAuthorizationFlagDefaults);
1430 }
1431 else
1432 RTMsgError("AuthorizationCreate failed: %d", orc);
1433
1434#else
1435
1436 RT_NOREF2(pszExecPath, pszDisplayInfoHack);
1437
1438 /*
1439 * Several of the alternatives below will require a command line.
1440 */
1441 char *pszCmdLine;
1442 int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
1443 if (RT_FAILURE(rc))
1444 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc", rc);
1445
1446 /*
1447 * Look for various standard stuff for executing a program as root.
1448 *
1449 * N.B. When adding new arguments, please make 100% sure RelaunchElevated
1450 * allocates enough array entries.
1451 *
1452 * TODO: Feel free to contribute code for using PolicyKit directly.
1453 */
1454 bool fHaveDisplayVar = RTEnvExist("DISPLAY");
1455 int iSuArg = cSuArgs;
1456 char szExecTool[260];
1457 char szXterm[260];
1458
1459 /*
1460 * kdesudo is available on KDE3/KDE4
1461 */
1462 if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo"))
1463 {
1464 iSuArg = cSuArgs - 4;
1465 papszArgs[cSuArgs - 4] = szExecTool;
1466 papszArgs[cSuArgs - 3] = "--comment";
1467 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1468 ? "VirtualBox extension pack installer"
1469 : iCmd == CMD_UNINSTALL
1470 ? "VirtualBox extension pack uninstaller"
1471 : "VirtualBox extension pack maintainer";
1472 papszArgs[cSuArgs - 1] = "--";
1473 }
1474 /*
1475 * gksu is our favorite as it is very well integrated.
1476 */
1477 else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu"))
1478 {
1479#if 0 /* older gksu does not grok --description nor '--' and multiple args. */
1480 iSuArg = cSuArgs - 4;
1481 papszArgs[cSuArgs - 4] = szExecTool;
1482 papszArgs[cSuArgs - 3] = "--description";
1483 papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL
1484 ? "VirtualBox extension pack installer"
1485 : iCmd == CMD_UNINSTALL
1486 ? "VirtualBox extension pack uninstaller"
1487 : "VirtualBox extension pack maintainer";
1488 papszArgs[cSuArgs - 1] = "--";
1489#elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */
1490 iSuArg = cSuArgs - 4;
1491 papszArgs[cSuArgs - 4] = szExecTool;
1492 papszArgs[cSuArgs - 3] = "-au";
1493 papszArgs[cSuArgs - 2] = "root";
1494 papszArgs[cSuArgs - 1] = pszCmdLine;
1495 papszArgs[cSuArgs] = NULL;
1496#else
1497 iSuArg = cSuArgs - 2;
1498 papszArgs[cSuArgs - 2] = szExecTool;
1499 papszArgs[cSuArgs - 1] = pszCmdLine;
1500 papszArgs[cSuArgs] = NULL;
1501#endif
1502 }
1503 /*
1504 * pkexec may work for ssh console sessions as well if the right agents
1505 * are installed. However it is very generic and does not allow for any
1506 * custom messages. Thus it comes after gksu.
1507 */
1508 else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec"))
1509 {
1510 iSuArg = cSuArgs - 1;
1511 papszArgs[cSuArgs - 1] = szExecTool;
1512 }
1513 /*
1514 * The ultimate fallback is running 'su -' within an xterm. We use the
1515 * title of the xterm to tell what is going on.
1516 */
1517 else if ( fHaveDisplayVar
1518 && FindExecTool(szExecTool, sizeof(szExecTool), "su")
1519 && FindExecTool(szXterm, sizeof(szXterm), "xterm"))
1520 {
1521 iSuArg = cSuArgs - 9;
1522 papszArgs[cSuArgs - 9] = szXterm;
1523 papszArgs[cSuArgs - 8] = "-T";
1524 papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL
1525 ? "VirtualBox extension pack installer - su"
1526 : iCmd == CMD_UNINSTALL
1527 ? "VirtualBox extension pack uninstaller - su"
1528 : "VirtualBox extension pack maintainer - su";
1529 papszArgs[cSuArgs - 6] = "-e";
1530 papszArgs[cSuArgs - 5] = szExecTool;
1531 papszArgs[cSuArgs - 4] = "-";
1532 papszArgs[cSuArgs - 3] = "root";
1533 papszArgs[cSuArgs - 2] = "-c";
1534 papszArgs[cSuArgs - 1] = pszCmdLine;
1535 papszArgs[cSuArgs] = NULL;
1536 }
1537 else if (fHaveDisplayVar)
1538 RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root");
1539 else
1540 RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root");
1541 if (iSuArg != cSuArgs)
1542 {
1543 AssertRelease(iSuArg >= 0);
1544
1545 /*
1546 * Argument list constructed, execute it and wait for the exec
1547 * program to complete.
1548 */
1549 RTPROCESS hProcess;
1550 rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/,
1551 NULL /*phStdIn*/, NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/,
1552 &hProcess);
1553 if (RT_SUCCESS(rc))
1554 {
1555 RTPROCSTATUS Status;
1556 rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status);
1557 if (RT_SUCCESS(rc))
1558 {
1559 if (Status.enmReason == RTPROCEXITREASON_NORMAL)
1560 rcExit = (RTEXITCODE)Status.iStatus;
1561 else
1562 rcExit = RTEXITCODE_FAILURE;
1563 }
1564 else
1565 RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc);
1566 }
1567 else
1568 RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc);
1569 }
1570 RTStrFree(pszCmdLine);
1571
1572#endif
1573 return rcExit;
1574}
1575
1576
1577/**
1578 * Relaunches ourselves as a elevated process using platform specific facilities.
1579 *
1580 * @returns Program exit code.
1581 * @param argc The number of arguments.
1582 * @param argv The arguments.
1583 * @param iCmd The command that is being executed.
1584 * @param pszDisplayInfoHack Display information hack. Platform specific++.
1585 */
1586static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack)
1587{
1588 /*
1589 * We need the executable name later, so get it now when it's easy to quit.
1590 */
1591 char szExecPath[RTPATH_MAX];
1592 if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath)))
1593 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1594
1595 /*
1596 * Create a couple of temporary files for stderr and stdout.
1597 */
1598 char szTempDir[RTPATH_MAX - sizeof("/stderr")];
1599 int rc = RTPathTemp(szTempDir, sizeof(szTempDir));
1600 if (RT_FAILURE(rc))
1601 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc);
1602 rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX");
1603 if (RT_FAILURE(rc))
1604 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc);
1605 rc = RTDirCreateTemp(szTempDir, 0700);
1606 if (RT_FAILURE(rc))
1607 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc);
1608
1609 RTEXITCODE rcExit = RTEXITCODE_FAILURE;
1610 char szStdOut[RTPATH_MAX];
1611 char szStdErr[RTPATH_MAX];
1612 rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout");
1613 if (RT_SUCCESS(rc))
1614 rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr");
1615 if (RT_SUCCESS(rc))
1616 {
1617 RTFILE hStdOut;
1618 rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1619 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1620 if (RT_SUCCESS(rc))
1621 {
1622 RTFILE hStdErr;
1623 rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE
1624 | (0600 << RTFILE_O_CREATE_MODE_SHIFT));
1625 if (RT_SUCCESS(rc))
1626 {
1627 /*
1628 * Insert the --elevated and stdout/err names into the argument
1629 * list. Note that darwin skips the --stdout bit, so don't
1630 * change the order here.
1631 */
1632 int const cSuArgs = 12;
1633 int cArgs = argc + 5 + 1;
1634 char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *));
1635 if (papszArgs)
1636 {
1637 int iDst = cSuArgs;
1638 papszArgs[iDst++] = argv[0];
1639 papszArgs[iDst++] = "--stdout";
1640 papszArgs[iDst++] = szStdOut;
1641 papszArgs[iDst++] = "--stderr";
1642 papszArgs[iDst++] = szStdErr;
1643 papszArgs[iDst++] = "--elevated";
1644 for (int iSrc = 1; iSrc <= argc; iSrc++)
1645 papszArgs[iDst++] = argv[iSrc];
1646
1647 /*
1648 * Do the platform specific process execution (waiting included).
1649 */
1650 rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack);
1651
1652 /*
1653 * Copy the standard files to our standard handles.
1654 */
1655 CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/);
1656 CopyFileToStdXxx(hStdOut, g_pStdOut, false);
1657
1658 RTMemTmpFree(papszArgs);
1659 }
1660
1661 RTFileClose(hStdErr);
1662 RTFileDelete(szStdErr);
1663 }
1664 RTFileClose(hStdOut);
1665 RTFileDelete(szStdOut);
1666 }
1667 }
1668 RTDirRemove(szTempDir);
1669
1670 return rcExit;
1671}
1672
1673
1674/**
1675 * Checks if the process is elevated or not.
1676 *
1677 * @returns RTEXITCODE_SUCCESS if preconditions are fine,
1678 * otherwise error message + RTEXITCODE_FAILURE.
1679 * @param pfElevated Where to store the elevation indicator.
1680 */
1681static RTEXITCODE ElevationCheck(bool *pfElevated)
1682{
1683 *pfElevated = false;
1684
1685# if defined(RT_OS_WINDOWS)
1686 /** @todo This should probably check if UAC is diabled and if we are
1687 * Administrator first. Also needs to check for Vista+ first, probably.
1688 */
1689 DWORD cb;
1690 RTEXITCODE rcExit = RTEXITCODE_SUCCESS;
1691 HANDLE hToken;
1692 if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
1693 return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError());
1694
1695 /*
1696 * Check if we're member of the Administrators group. If we aren't, there
1697 * is no way to elevate ourselves to system admin.
1698 * N.B. CheckTokenMembership does not do the job here (due to attributes?).
1699 */
1700 BOOL fIsAdmin = FALSE;
1701 SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
1702 PSID pAdminGrpSid;
1703 if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid))
1704 {
1705# ifdef DEBUG
1706 char *pszAdminGrpSid = NULL;
1707 ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid);
1708# endif
1709
1710 if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb)
1711 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1712 {
1713 PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb);
1714 if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb))
1715 {
1716 for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++)
1717 {
1718# ifdef DEBUG
1719 char *pszGrpSid = NULL;
1720 ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid);
1721# endif
1722 if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid))
1723 {
1724 /* That it's listed is enough I think, ignore attributes. */
1725 fIsAdmin = TRUE;
1726 break;
1727 }
1728 }
1729 }
1730 else
1731 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError());
1732 RTMemFree(pTokenGroups);
1733 }
1734 else
1735 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError());
1736
1737 FreeSid(pAdminGrpSid);
1738 }
1739 else
1740 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError());
1741 if (fIsAdmin)
1742 {
1743 /*
1744 * Check the integrity level (Vista / UAC).
1745 */
1746# define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L
1747# define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25)
1748 if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb)
1749 && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
1750 {
1751 PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb);
1752 if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb))
1753 {
1754 DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U);
1755
1756 if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID)
1757 *pfElevated = true;
1758 }
1759 else
1760 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1761 RTMemFree(pSidAndAttr);
1762 }
1763 else if ( GetLastError() == ERROR_INVALID_PARAMETER
1764 || GetLastError() == ERROR_NOT_SUPPORTED)
1765 *pfElevated = true; /* Older Windows version. */
1766 else
1767 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError());
1768 }
1769 else
1770 rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action");
1771
1772 CloseHandle(hToken);
1773 return rcExit;
1774
1775# else
1776 /*
1777 * On Unixy systems, we check if the executable and the current user is
1778 * the same. This heuristic works fine for both hardened and development
1779 * builds.
1780 */
1781 char szExecPath[RTPATH_MAX];
1782 if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL)
1783 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed");
1784
1785 RTFSOBJINFO ObjInfo;
1786 int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
1787 if (RT_FAILURE(rc))
1788 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed");
1789
1790 *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid()
1791 || ObjInfo.Attr.u.Unix.uid == getuid();
1792 return RTEXITCODE_SUCCESS;
1793# endif
1794}
1795
1796#endif /* WITH_ELEVATION */
1797
1798int main(int argc, char **argv)
1799{
1800 /*
1801 * Initialize IPRT and check that we're correctly installed.
1802 */
1803#ifdef RT_OS_WINDOWS
1804 int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_UTF8_ARGV); /* WinMain gives us UTF-8, see below. */
1805#else
1806 int rc = RTR3InitExe(argc, &argv, 0);
1807#endif
1808 if (RT_FAILURE(rc))
1809 return RTMsgInitFailure(rc);
1810
1811 SUPR3HardenedVerifyInit();
1812 RTERRINFOSTATIC ErrInfo;
1813 RTErrInfoInitStatic(&ErrInfo);
1814 rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core);
1815 if (RT_FAILURE(rc))
1816 return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg);
1817
1818 /*
1819 * Elevation check.
1820 */
1821 const char *pszDisplayInfoHack = NULL;
1822 RTEXITCODE rcExit;
1823#ifdef WITH_ELEVATION
1824 bool fElevated;
1825 rcExit = ElevationCheck(&fElevated);
1826 if (rcExit != RTEXITCODE_SUCCESS)
1827 return rcExit;
1828#endif
1829
1830 /*
1831 * Parse the top level arguments until we find a command.
1832 */
1833 static const RTGETOPTDEF s_aOptions[] =
1834 {
1835 { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING },
1836 { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING },
1837 { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING },
1838#ifdef WITH_ELEVATION
1839 { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING },
1840 { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING },
1841 { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING },
1842#endif
1843 { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING },
1844 };
1845 RTGETOPTSTATE GetState;
1846 rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/);
1847 if (RT_FAILURE(rc))
1848 return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc);
1849 for (;;)
1850 {
1851 RTGETOPTUNION ValueUnion;
1852 int ch = RTGetOpt(&GetState, &ValueUnion);
1853 switch (ch)
1854 {
1855 case 0:
1856 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified");
1857
1858 case CMD_INSTALL:
1859 case CMD_UNINSTALL:
1860 case CMD_CLEANUP:
1861 {
1862#ifdef WITH_ELEVATION
1863 if (!fElevated)
1864 return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack);
1865#endif
1866 int cCmdargs = argc - GetState.iNext;
1867 char **papszCmdArgs = argv + GetState.iNext;
1868 switch (ch)
1869 {
1870 case CMD_INSTALL:
1871 rcExit = DoInstall( cCmdargs, papszCmdArgs);
1872 break;
1873 case CMD_UNINSTALL:
1874 rcExit = DoUninstall(cCmdargs, papszCmdArgs);
1875 break;
1876 case CMD_CLEANUP:
1877 rcExit = DoCleanup( cCmdargs, papszCmdArgs);
1878 break;
1879 default:
1880 AssertReleaseFailedReturn(RTEXITCODE_FAILURE);
1881 }
1882
1883 /*
1884 * Standard error should end with rcExit=RTEXITCODE_SUCCESS on
1885 * success since the exit code may otherwise get lost in the
1886 * process elevation fun.
1887 */
1888 RTStrmFlush(g_pStdOut);
1889 RTStrmFlush(g_pStdErr);
1890 switch (rcExit)
1891 {
1892 case RTEXITCODE_SUCCESS:
1893 RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n");
1894 break;
1895 default:
1896 RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit);
1897 break;
1898 }
1899 RTStrmFlush(g_pStdErr);
1900 RTStrmFlush(g_pStdOut);
1901 return rcExit;
1902 }
1903
1904#ifdef WITH_ELEVATION
1905 case OPT_ELEVATED:
1906 fElevated = true;
1907 break;
1908
1909 case OPT_STDERR:
1910 case OPT_STDOUT:
1911 {
1912# ifdef RT_OS_WINDOWS
1913 PRTUTF16 pwszName = NULL;
1914 rc = RTStrToUtf16(ValueUnion.psz, &pwszName);
1915 if (RT_FAILURE(rc))
1916 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Error converting '%s' to UTF-16: %Rrc\n", ValueUnion.psz, rc);
1917 FILE *pFile = _wfreopen(pwszName, L"r+", ch == OPT_STDOUT ? stdout : stderr);
1918 RTUtf16Free(pwszName);
1919# else
1920 FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr);
1921# endif
1922 if (!pFile)
1923 {
1924 rc = RTErrConvertFromErrno(errno);
1925 return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc);
1926 }
1927 break;
1928 }
1929#endif
1930
1931 case OPT_DISP_INFO_HACK:
1932 if (pszDisplayInfoHack)
1933 return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once");
1934 pszDisplayInfoHack = ValueUnion.psz;
1935 break;
1936
1937 case 'h':
1938 case 'V':
1939 return DoStandardOption(ch);
1940
1941 default:
1942 return RTGetOptPrintError(ch, &ValueUnion);
1943 }
1944 /* not currently reached */
1945 }
1946 /* not reached */
1947}
1948
1949
1950#ifdef RT_OS_WINDOWS
1951extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
1952{
1953 g_hInstance = hInstance;
1954 NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine);
1955
1956 int rc = RTR3InitExeNoArguments(0);
1957 if (RT_FAILURE(rc))
1958 return RTMsgInitFailure(rc);
1959
1960 LPWSTR pwszCmdLine = GetCommandLineW();
1961 if (!pwszCmdLine)
1962 return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed");
1963
1964 char *pszCmdLine;
1965 rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */
1966 if (RT_FAILURE(rc))
1967 return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc);
1968
1969 int cArgs;
1970 char **papszArgs;
1971 rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, RTGETOPTARGV_CNV_QUOTE_MS_CRT, NULL);
1972 if (RT_SUCCESS(rc))
1973 {
1974
1975 rc = main(cArgs, papszArgs);
1976
1977 RTGetOptArgvFree(papszArgs);
1978 }
1979 else
1980 rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc);
1981 RTStrFree(pszCmdLine);
1982
1983 return rc;
1984}
1985#endif
1986
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