VirtualBox

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

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

VBoxExtPackHelperApp.cpp: Adjusted r69630 to restore the actual directory permissions.

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