VirtualBox

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

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

ExtPack: Don't suppress gksu and kdesudo output, just ignore extra the output on success.

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