VirtualBox

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

Last change on this file since 35699 was 35699, checked in by vboxsync, 14 years ago

VBoxExtPackHelperApp: override umask when creating directories

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