VirtualBox

source: vbox/trunk/src/VBox/Main/VBoxExtPackHelperApp.cpp@ 35273

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

IExtPackFile,IExtPackManager: Added progress and display info to the Install and Uninstall methods. No progress object will be returned in 4.0.0 as that is too risky.

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