VirtualBox

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

Last change on this file since 47469 was 46326, checked in by vboxsync, 11 years ago

RT_STR_TUPLE

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

© 2023 Oracle
ContactPrivacy policyTerms of Use