VirtualBox

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

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

duh

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette