VirtualBox

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

Last change on this file since 39650 was 39612, checked in by vboxsync, 13 years ago

IPRT/*: add _NO_SYMLINKS flags to certain functions

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