/* $Id: VBoxExtPackHelperApp.cpp 40104 2012-02-13 18:16:00Z vboxsync $ */ /** @file * VirtualBox Main - Extension Pack Helper Application, usually set-uid-to-root. */ /* * Copyright (C) 2010-2012 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. */ /******************************************************************************* * Header Files * *******************************************************************************/ #include "../include/ExtPackUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef RT_OS_WINDOWS # define _WIN32_WINNT 0x0501 # include /* CoInitializeEx */ # include /* ShellExecuteEx, ++ */ # ifdef DEBUG # include # endif #endif #ifdef RT_OS_DARWIN # include # include # include #endif #if !defined(RT_OS_OS2) # include # include # if !defined(RT_OS_WINDOWS) # include # include /* geteuid */ # endif #endif /******************************************************************************* * Defined Constants And Macros * *******************************************************************************/ /** Enable elevation on Windows and Darwin. */ #if !defined(RT_OS_OS2) || defined(DOXYGEN_RUNNING) # define WITH_ELEVATION #endif /** @name Command and option names * @{ */ #define CMD_INSTALL 1000 #define CMD_UNINSTALL 1001 #define CMD_CLEANUP 1002 #ifdef WITH_ELEVATION # define OPT_ELEVATED 1090 # define OPT_STDOUT 1091 # define OPT_STDERR 1092 #endif #define OPT_DISP_INFO_HACK 1093 /** @} */ /******************************************************************************* * Global Variables * *******************************************************************************/ #ifdef RT_OS_WINDOWS static HINSTANCE g_hInstance; #endif #ifdef IN_RT_R3 /* Override RTAssertShouldPanic to prevent gdb process creation. */ RTDECL(bool) RTAssertShouldPanic(void) { return true; } #endif /** * Handle the special standard options when these are specified after the * command. * * @param ch The option character. */ static RTEXITCODE DoStandardOption(int ch) { switch (ch) { case 'h': { RTMsgInfo(VBOX_PRODUCT " Extension Pack Helper App\n" "(C) " VBOX_C_YEAR " " VBOX_VENDOR "\n" "All rights reserved.\n" "\n" "This NOT intended for general use, please use VBoxManage instead\n" "or call the IExtPackManager API directly.\n" "\n" "Usage: %s [options]\n" "Commands:\n" " install --base-dir --cert-dir --name \\\n" " --tarball --tarball-fd \n" " uninstall --base-dir --name \n" " cleanup --base-dir \n" , RTProcShortName()); return RTEXITCODE_SUCCESS; } case 'V': RTPrintf("%sr%d\n", VBOX_VERSION_STRING, RTBldCfgRevision()); return RTEXITCODE_SUCCESS; default: AssertFailedReturn(RTEXITCODE_FAILURE); } } /** * Checks if the cerficiate directory is valid. * * @returns true if it is valid, false if it isn't. * @param pszCertDir The certificate directory to validate. */ static bool IsValidCertificateDir(const char *pszCertDir) { /* * Just be darn strict for now. */ char szCorrect[RTPATH_MAX]; int rc = RTPathAppPrivateNoArch(szCorrect, sizeof(szCorrect)); if (RT_FAILURE(rc)) return false; rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_CERT_DIR); if (RT_FAILURE(rc)) return false; return RTPathCompare(szCorrect, pszCertDir) == 0; } /** * Checks if the base directory is valid. * * @returns true if it is valid, false if it isn't. * @param pszBaesDir The base directory to validate. */ static bool IsValidBaseDir(const char *pszBaseDir) { /* * Just be darn strict for now. */ char szCorrect[RTPATH_MAX]; int rc = RTPathAppPrivateArchTop(szCorrect, sizeof(szCorrect)); if (RT_FAILURE(rc)) return false; rc = RTPathAppend(szCorrect, sizeof(szCorrect), VBOX_EXTPACK_INSTALL_DIR); if (RT_FAILURE(rc)) return false; return RTPathCompare(szCorrect, pszBaseDir) == 0; } /** * Cleans up a temporary extension pack directory. * * This is used by 'uninstall', 'cleanup' and in the failure path of 'install'. * * @returns The program exit code. * @param pszDir The directory to clean up. The caller is * responsible for making sure this is valid. * @param fTemporary Whether this is a temporary install directory or * not. */ static RTEXITCODE RemoveExtPackDir(const char *pszDir, bool fTemporary) { /** @todo May have to undo 555 modes here later. */ int rc = RTDirRemoveRecursive(pszDir, RTDIRRMREC_F_CONTENT_AND_DIR); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to delete the %sextension pack directory: %Rrc ('%s')", fTemporary ? "temporary " : "", rc, pszDir); return RTEXITCODE_SUCCESS; } /** * Common uninstall worker used by both uninstall and install --replace. * * @returns success or failure, message displayed on failure. * @param pszExtPackDir The extension pack directory name. */ static RTEXITCODE CommonUninstallWorker(const char *pszExtPackDir) { /* Rename the extension pack directory before deleting it to prevent new VM processes from picking it up. */ char szExtPackUnInstDir[RTPATH_MAX]; int rc = RTStrCopy(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), pszExtPackDir); if (RT_SUCCESS(rc)) rc = RTStrCat(szExtPackUnInstDir, sizeof(szExtPackUnInstDir), "-_-uninst"); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct temporary extension pack path: %Rrc", rc); rc = RTDirRename(pszExtPackDir, szExtPackUnInstDir, RTPATHRENAME_FLAGS_NO_REPLACE); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the extension pack directory: %Rrc", rc); /* Recursively delete the directory content. */ return RemoveExtPackDir(szExtPackUnInstDir, false /*fTemporary*/); } /** * Wrapper around VBoxExtPackOpenTarFss. * * @returns success or failure, message displayed on failure. * @param hTarballFile The handle to the tarball file. * @param phTarFss Where to return the filesystem stream handle. */ static RTEXITCODE OpenTarFss(RTFILE hTarballFile, PRTVFSFSSTREAM phTarFss) { char szError[8192]; int rc = VBoxExtPackOpenTarFss(hTarballFile, szError, sizeof(szError), phTarFss, NULL); if (RT_FAILURE(rc)) { Assert(szError[0]); return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); } Assert(!szError[0]); return RTEXITCODE_SUCCESS; } /** * Sets the permissions of the temporary extension pack directory just before * renaming it. * * By default the temporary directory is only accessible by root, this function * will make it world readable and browseable. * * @returns The program exit code. * @param pszDir The temporary extension pack directory. */ static RTEXITCODE SetExtPackPermissions(const char *pszDir) { RTMsgInfo("Setting permissions..."); #if !defined(RT_OS_WINDOWS) int rc = RTPathSetMode(pszDir, 0755); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions: %Rrc ('%s')", rc, pszDir); #else /** @todo */ #endif return RTEXITCODE_SUCCESS; } /** * Wrapper around VBoxExtPackValidateMember. * * @returns Program exit code, failure with message. * @param pszName The name of the directory. * @param enmType The object type. * @param hVfsObj The VFS object. */ static RTEXITCODE ValidateMemberOfExtPack(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj) { char szError[8192]; int rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, szError, sizeof(szError)); if (RT_FAILURE(rc)) { Assert(szError[0]); return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); } Assert(!szError[0]); return RTEXITCODE_SUCCESS; } /** * Validates the extension pack tarball prior to unpacking. * * Operations performed: * - Hardening checks. * * @returns The program exit code. * @param pszDir The directory where the extension pack has been * unpacked. * @param pszExtPackName The expected extension pack name. * @param pszTarball The name of the tarball in case we have to * complain about something. */ static RTEXITCODE ValidateUnpackedExtPack(const char *pszDir, const char *pszTarball, const char *pszExtPackName) { RTMsgInfo("Validating unpacked extension pack..."); RTERRINFOSTATIC ErrInfo; RTErrInfoInitStatic(&ErrInfo); int rc = SUPR3HardenedVerifyDir(pszDir, true /*fRecursive*/, true /*fCheckFiles*/, &ErrInfo.Core); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Hardening check failed with %Rrc: %s", rc, ErrInfo.Core.pszMsg); return RTEXITCODE_SUCCESS; } /** * Unpacks a directory from an extension pack tarball. * * @returns Program exit code, failure with message. * @param pszDstDirName The name of the unpacked directory. * @param hVfsObj The source object for the directory. */ static RTEXITCODE UnpackExtPackDir(const char *pszDstDirName, RTVFSOBJ hVfsObj) { /* * Get the mode mask before creating the directory. */ RTFSOBJINFO ObjInfo; int rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszDstDirName, rc); ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); rc = RTDirCreate(pszDstDirName, ObjInfo.Attr.fMode, 0); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create directory '%s': %Rrc", pszDstDirName, rc); #ifndef RT_OS_WINDOWS /* * Because of umask, we have to apply the mode again. */ rc = RTPathSetMode(pszDstDirName, ObjInfo.Attr.fMode); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszDstDirName, rc); #else /** @todo Ownership tricks on windows? */ #endif return RTEXITCODE_SUCCESS; } /** * Unpacks a file from an extension pack tarball. * * @returns Program exit code, failure with message. * @param pszName The name in the tarball. * @param pszDstFilename The name of the unpacked file. * @param hVfsIosSrc The source stream for the file. * @param hUnpackManifest The manifest to add the file digest to. */ static RTEXITCODE UnpackExtPackFile(const char *pszName, const char *pszDstFilename, RTVFSIOSTREAM hVfsIosSrc, RTMANIFEST hUnpackManifest) { /* * Query the object info, we'll need it for buffer sizing as well as * setting the file mode. */ RTFSOBJINFO ObjInfo; int rc = RTVfsIoStrmQueryInfo(hVfsIosSrc, &ObjInfo, RTFSOBJATTRADD_NOTHING); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsIoStrmQueryInfo failed with %Rrc on '%s'", rc, pszDstFilename); /* * Create the file. */ uint32_t fFlags = RTFILE_O_WRITE | RTFILE_O_DENY_ALL | RTFILE_O_CREATE | (0600 << RTFILE_O_CREATE_MODE_SHIFT); RTFILE hFile; rc = RTFileOpen(&hFile, pszDstFilename, fFlags); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create '%s': %Rrc", pszDstFilename, rc); /* * Create a I/O stream for the destination file, stack a manifest entry * creator on top of it. */ RTVFSIOSTREAM hVfsIosDst2; rc = RTVfsIoStrmFromRTFile(hFile, fFlags, true /*fLeaveOpen*/, &hVfsIosDst2); if (RT_SUCCESS(rc)) { RTVFSIOSTREAM hVfsIosDst; rc = RTManifestEntryAddPassthruIoStream(hUnpackManifest, hVfsIosDst2, pszName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256, false /*fReadOrWrite*/, &hVfsIosDst); RTVfsIoStrmRelease(hVfsIosDst2); if (RT_SUCCESS(rc)) { /* * Pump the data thru. */ rc = RTVfsUtilPumpIoStreams(hVfsIosSrc, hVfsIosDst, (uint32_t)RT_MIN(ObjInfo.cbObject, _1G)); if (RT_SUCCESS(rc)) { rc = RTManifestPtIosAddEntryNow(hVfsIosDst); if (RT_SUCCESS(rc)) { RTVfsIoStrmRelease(hVfsIosDst); hVfsIosDst = NIL_RTVFSIOSTREAM; /* * Set the mode mask. */ ObjInfo.Attr.fMode &= ~(RTFS_UNIX_IWOTH | RTFS_UNIX_IWGRP); rc = RTFileSetMode(hFile, ObjInfo.Attr.fMode); /** @todo Windows needs to do more here, I think. */ if (RT_SUCCESS(rc)) { RTFileClose(hFile); return RTEXITCODE_SUCCESS; } RTMsgError("Failed to set the mode of '%s' to %RTfmode: %Rrc", pszDstFilename, ObjInfo.Attr.fMode, rc); } else RTMsgError("RTManifestPtIosAddEntryNow failed for '%s': %Rrc", pszDstFilename, rc); } else RTMsgError("RTVfsUtilPumpIoStreams failed for '%s': %Rrc", pszDstFilename, rc); RTVfsIoStrmRelease(hVfsIosDst); } else RTMsgError("RTManifestEntryAddPassthruIoStream failed: %Rrc", rc); } else RTMsgError("RTVfsIoStrmFromRTFile failed: %Rrc", rc); RTFileClose(hFile); return RTEXITCODE_FAILURE; } /** * Unpacks the extension pack into the specified directory. * * This will apply ownership and permission changes to all the content, the * exception is @a pszDirDst which will be handled by SetExtPackPermissions. * * @returns The program exit code. * @param hTarballFile The tarball to unpack. * @param pszDirDst Where to unpack it. * @param hValidManifest The manifest we've validated. * @param pszTarball The name of the tarball in case we have to * complain about something. */ static RTEXITCODE UnpackExtPack(RTFILE hTarballFile, const char *pszDirDst, RTMANIFEST hValidManifest, const char *pszTarball) { RTMsgInfo("Unpacking extension pack into '%s'...", pszDirDst); /* * Set up the destination path. */ char szDstPath[RTPATH_MAX]; int rc = RTPathAbs(pszDirDst, szDstPath, sizeof(szDstPath) - VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH - 2); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAbs('%s',,) failed: %Rrc", pszDirDst, rc); size_t offDstPath = RTPathStripTrailingSlash(szDstPath); szDstPath[offDstPath++] = '/'; szDstPath[offDstPath] = '\0'; /* * Open the tar.gz filesystem stream and set up an manifest in-memory file. */ RTVFSFSSTREAM hTarFss; RTEXITCODE rcExit = OpenTarFss(hTarballFile, &hTarFss); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; RTMANIFEST hUnpackManifest; rc = RTManifestCreate(0 /*fFlags*/, &hUnpackManifest); if (RT_SUCCESS(rc)) { /* * Process the tarball (would be nice to move this to a function). */ for (;;) { /* * Get the next stream object. */ char *pszName; RTVFSOBJ hVfsObj; RTVFSOBJTYPE enmType; rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj); if (RT_FAILURE(rc)) { if (rc != VERR_EOF) rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTVfsFsStrmNext failed: %Rrc", rc); break; } const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName; /* * Check the type & name validity then unpack it. */ rcExit = ValidateMemberOfExtPack(pszName, enmType, hVfsObj); if (rcExit == RTEXITCODE_SUCCESS) { szDstPath[offDstPath] = '\0'; rc = RTStrCopy(&szDstPath[offDstPath], sizeof(szDstPath) - offDstPath, pszAdjName); if (RT_SUCCESS(rc)) { if ( enmType == RTVFSOBJTYPE_FILE || enmType == RTVFSOBJTYPE_IO_STREAM) { RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj); rcExit = UnpackExtPackFile(pszAdjName, szDstPath, hVfsIos, hUnpackManifest); RTVfsIoStrmRelease(hVfsIos); } else if (*pszAdjName && strcmp(pszAdjName, ".")) rcExit = UnpackExtPackDir(szDstPath, hVfsObj); } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Name is too long: '%s' (%Rrc)", pszAdjName, rc); } /* * Clean up and break out on failure. */ RTVfsObjRelease(hVfsObj); RTStrFree(pszName); if (rcExit != RTEXITCODE_SUCCESS) break; } /* * Check that what we just extracted matches the already verified * manifest. */ if (rcExit == RTEXITCODE_SUCCESS) { char szError[RTPATH_MAX]; rc = RTManifestEqualsEx(hUnpackManifest, hValidManifest, NULL /*papszIgnoreEntries*/, NULL /*papszIgnoreAttr*/, 0 /*fFlags*/, szError, sizeof(szError)); if (RT_SUCCESS(rc)) rcExit = RTEXITCODE_SUCCESS; else if (rc == VERR_NOT_EQUAL && szError[0]) rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Manifest mismatch: %s", szError); else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTManifestEqualsEx failed: %Rrc", rc); } #if 0 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM; RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut); RTVfsIoStrmWrite(hVfsIosStdOut, "Unpack:\n", sizeof("Unpack:\n") - 1, true, NULL); RTManifestWriteStandard(hUnpackManifest, hVfsIosStdOut); RTVfsIoStrmWrite(hVfsIosStdOut, "Valid:\n", sizeof("Valid:\n") - 1, true, NULL); RTManifestWriteStandard(hValidManifest, hVfsIosStdOut); #endif RTManifestRelease(hUnpackManifest); } RTVfsFsStrmRelease(hTarFss); return rcExit; } /** * Wrapper around VBoxExtPackValidateTarball. * * @returns The program exit code. * @param hTarballFile The handle to open the @a pszTarball file. * @param pszExtPackName The name of the extension pack name. * @param pszTarball The name of the tarball in case we have to * complain about something. * @param pszTarballDigest The SHA-256 digest of the tarball. * @param phValidManifest Where to return the handle to fully validated * the manifest for the extension pack. This * includes all files. */ static RTEXITCODE ValidateExtPackTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball, const char *pszTarballDigest, PRTMANIFEST phValidManifest) { *phValidManifest = NIL_RTMANIFEST; RTMsgInfo("Validating extension pack '%s' ('%s')...", pszTarball, pszExtPackName); Assert(pszTarballDigest && *pszTarballDigest); char szError[8192]; int rc = VBoxExtPackValidateTarball(hTarballFile, pszExtPackName, pszTarball, pszTarballDigest, szError, sizeof(szError), phValidManifest, NULL /*phXmlFile*/, NULL /*pStrDigest*/); if (RT_FAILURE(rc)) { Assert(szError[0]); return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", szError); } Assert(!szError[0]); return RTEXITCODE_SUCCESS; } /** * The 2nd part of the installation process. * * @returns The program exit code. * @param pszBaseDir The base directory. * @param pszCertDir The certificat directory. * @param pszTarball The tarball name. * @param pszTarballDigest The SHA-256 digest of the tarball. Empty string * if no digest available. * @param hTarballFile The handle to open the @a pszTarball file. * @param hTarballFileOpt The tarball file handle (optional). * @param pszName The extension pack name. * @param pszMangledName The mangled extension pack name. * @param fReplace Whether to replace any existing ext pack. */ static RTEXITCODE DoInstall2(const char *pszBaseDir, const char *pszCertDir, const char *pszTarball, const char *pszTarballDigest, RTFILE hTarballFile, RTFILE hTarballFileOpt, const char *pszName, const char *pszMangledName, bool fReplace) { /* * Do some basic validation of the tarball file. */ RTFSOBJINFO ObjInfo; int rc = RTFileQueryInfo(hTarballFile, &ObjInfo, RTFSOBJATTRADD_UNIX); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on '%s'", rc, pszTarball); if (!RTFS_IS_FILE(ObjInfo.Attr.fMode)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Not a regular file: %s", pszTarball); if (hTarballFileOpt != NIL_RTFILE) { RTFSOBJINFO ObjInfo2; rc = RTFileQueryInfo(hTarballFileOpt, &ObjInfo2, RTFSOBJATTRADD_UNIX); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTFileQueryInfo failed with %Rrc on --tarball-fd", rc); if ( ObjInfo.Attr.u.Unix.INodeIdDevice != ObjInfo2.Attr.u.Unix.INodeIdDevice || ObjInfo.Attr.u.Unix.INodeId != ObjInfo2.Attr.u.Unix.INodeId) return RTMsgErrorExit(RTEXITCODE_FAILURE, "--tarball and --tarball-fd does not match"); } /* * Construct the paths to the two directories we'll be using. */ char szFinalPath[RTPATH_MAX]; rc = RTPathJoin(szFinalPath, sizeof(szFinalPath), pszBaseDir, pszMangledName); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct the path to the final extension pack directory: %Rrc", rc); char szTmpPath[RTPATH_MAX]; rc = RTPathJoin(szTmpPath, sizeof(szTmpPath) - 64, pszBaseDir, pszMangledName); if (RT_SUCCESS(rc)) { size_t cchTmpPath = strlen(szTmpPath); RTStrPrintf(&szTmpPath[cchTmpPath], sizeof(szTmpPath) - cchTmpPath, "-_-inst-%u", (uint32_t)RTProcSelf()); } if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct the path to the temporary extension pack directory: %Rrc", rc); /* * Check that they don't exist at this point in time, unless fReplace=true. */ rc = RTPathQueryInfoEx(szFinalPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); if (RT_SUCCESS(rc) && RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) { if (!fReplace) return RTMsgErrorExit(RTEXITCODE_FAILURE, "The extension pack is already installed. You must uninstall the old one first."); } else if (RT_SUCCESS(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Found non-directory file system object where the extension pack would be installed ('%s')", szFinalPath); else if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath); rc = RTPathQueryInfoEx(szTmpPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); if (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Unexpected RTPathQueryInfoEx status code %Rrc for '%s'", rc, szFinalPath); /* * Create the temporary directory and prepare the extension pack within it. * If all checks out correctly, rename it to the final directory. */ RTDirCreate(pszBaseDir, 0755, 0); #ifndef RT_OS_WINDOWS /* * Because of umask, we have to apply the mode again. */ rc = RTPathSetMode(pszBaseDir, 0755); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to set directory permissions on '%s': %Rrc", pszBaseDir, rc); #else /** @todo Ownership tricks on windows? */ #endif rc = RTDirCreate(szTmpPath, 0700, 0); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to create temporary directory: %Rrc ('%s')", rc, szTmpPath); RTMANIFEST hValidManifest = NIL_RTMANIFEST; RTEXITCODE rcExit = ValidateExtPackTarball(hTarballFile, pszName, pszTarball, pszTarballDigest, &hValidManifest); if (rcExit == RTEXITCODE_SUCCESS) rcExit = UnpackExtPack(hTarballFile, szTmpPath, hValidManifest, pszTarball); if (rcExit == RTEXITCODE_SUCCESS) rcExit = ValidateUnpackedExtPack(szTmpPath, pszTarball, pszName); if (rcExit == RTEXITCODE_SUCCESS) rcExit = SetExtPackPermissions(szTmpPath); RTManifestRelease(hValidManifest); if (rcExit == RTEXITCODE_SUCCESS) { rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); if ( RT_FAILURE(rc) && fReplace && RTDirExists(szFinalPath)) { /* Automatic uninstall if --replace was given. */ rcExit = CommonUninstallWorker(szFinalPath); if (rcExit == RTEXITCODE_SUCCESS) rc = RTDirRename(szTmpPath, szFinalPath, RTPATHRENAME_FLAGS_NO_REPLACE); } if (RT_SUCCESS(rc)) RTMsgInfo("Successfully installed '%s' (%s)", pszName, pszTarball); else if (rcExit == RTEXITCODE_SUCCESS) rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to rename the temporary directory to the final one: %Rrc ('%s' -> '%s')", rc, szTmpPath, szFinalPath); } /* * Clean up the temporary directory on failure. */ if (rcExit != RTEXITCODE_SUCCESS) RemoveExtPackDir(szTmpPath, true /*fTemporary*/); return rcExit; } /** * Implements the 'install' command. * * @returns The program exit code. * @param argc The number of program arguments. * @param argv The program arguments. */ static RTEXITCODE DoInstall(int argc, char **argv) { /* * Parse the parameters. * * Note! The --base-dir and --cert-dir are only for checking that the * caller and this help applications have the same idea of where * things are. Likewise, the --name is for verifying assumptions * the caller made about the name. The optional --tarball-fd option * is just for easing the paranoia on the user side. */ static const RTGETOPTDEF s_aOptions[] = { { "--base-dir", 'b', RTGETOPT_REQ_STRING }, { "--cert-dir", 'c', RTGETOPT_REQ_STRING }, { "--name", 'n', RTGETOPT_REQ_STRING }, { "--tarball", 't', RTGETOPT_REQ_STRING }, { "--tarball-fd", 'd', RTGETOPT_REQ_UINT64 }, { "--replace", 'r', RTGETOPT_REQ_NOTHING }, { "--sha-256", 's', RTGETOPT_REQ_STRING } }; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); const char *pszBaseDir = NULL; const char *pszCertDir = NULL; const char *pszName = NULL; const char *pszTarball = NULL; const char *pszTarballDigest = NULL; RTFILE hTarballFileOpt = NIL_RTFILE; bool fReplace = false; RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'b': if (pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); pszBaseDir = ValueUnion.psz; if (!IsValidBaseDir(pszBaseDir)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); break; case 'c': if (pszCertDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --cert-dir options"); pszCertDir = ValueUnion.psz; if (!IsValidCertificateDir(pszCertDir)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid certificate directory: '%s'", pszCertDir); break; case 'n': if (pszName) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options"); pszName = ValueUnion.psz; if (!VBoxExtPackIsValidName(pszName)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName); break; case 't': if (pszTarball) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball options"); pszTarball = ValueUnion.psz; break; case 'd': { if (hTarballFileOpt != NIL_RTFILE) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --tarball-fd options"); RTHCUINTPTR hNative = (RTHCUINTPTR)ValueUnion.u64; if (hNative != ValueUnion.u64) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "The --tarball-fd value is out of range: %#RX64", ValueUnion.u64); rc = RTFileFromNative(&hTarballFileOpt, hNative); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "RTFileFromNative failed on --target-fd value: %Rrc", rc); break; } case 'r': fReplace = true; break; case 's': { if (pszTarballDigest) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --sha-256 options"); pszTarballDigest = ValueUnion.psz; uint8_t abDigest[RTSHA256_HASH_SIZE]; rc = RTSha256FromString(pszTarballDigest, abDigest); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Bad SHA-256 string: %Rrc", rc); break; } case 'h': case 'V': return DoStandardOption(ch); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszName) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option"); if (!pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); if (!pszCertDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --cert-dir option"); if (!pszTarball) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --tarball option"); if (!pszTarballDigest) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --sha-256 option"); /* * Ok, down to business. */ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName); if (!pstrMangledName) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName); RTEXITCODE rcExit; RTFILE hTarballFile; rc = RTFileOpen(&hTarballFile, pszTarball, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_SUCCESS(rc)) { rcExit = DoInstall2(pszBaseDir, pszCertDir, pszTarball, pszTarballDigest, hTarballFile, hTarballFileOpt, pszName, pstrMangledName->c_str(), fReplace); RTFileClose(hTarballFile); } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to open the extension pack tarball: %Rrc ('%s')", rc, pszTarball); delete pstrMangledName; return rcExit; } /** * Implements the 'uninstall' command. * * @returns The program exit code. * @param argc The number of program arguments. * @param argv The program arguments. */ static RTEXITCODE DoUninstall(int argc, char **argv) { /* * Parse the parameters. * * Note! The --base-dir is only for checking that the caller and this help * applications have the same idea of where things are. */ static const RTGETOPTDEF s_aOptions[] = { { "--base-dir", 'b', RTGETOPT_REQ_STRING }, { "--name", 'n', RTGETOPT_REQ_STRING } }; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); const char *pszBaseDir = NULL; const char *pszName = NULL; RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'b': if (pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); pszBaseDir = ValueUnion.psz; if (!IsValidBaseDir(pszBaseDir)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); break; case 'n': if (pszName) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --name options"); pszName = ValueUnion.psz; if (!VBoxExtPackIsValidName(pszName)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid extension pack name: '%s'", pszName); break; case 'h': case 'V': return DoStandardOption(ch); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszName) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --name option"); if (!pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); /* * Mangle the name so we can construct the directory names. */ RTCString *pstrMangledName = VBoxExtPackMangleName(pszName); if (!pstrMangledName) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to mangle name ('%s)", pszName); RTCString strMangledName(*pstrMangledName); delete pstrMangledName; /* * Ok, down to business. */ /* Check that it exists. */ char szExtPackDir[RTPATH_MAX]; rc = RTPathJoin(szExtPackDir, sizeof(szExtPackDir), pszBaseDir, strMangledName.c_str()); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct extension pack path: %Rrc", rc); if (!RTDirExists(szExtPackDir)) { RTMsgInfo("Extension pack not installed. Nothing to do."); return RTEXITCODE_SUCCESS; } RTEXITCODE rcExit = CommonUninstallWorker(szExtPackDir); if (rcExit == RTEXITCODE_SUCCESS) RTMsgInfo("Successfully removed extension pack '%s'\n", pszName); return rcExit; } /** * Implements the 'cleanup' command. * * @returns The program exit code. * @param argc The number of program arguments. * @param argv The program arguments. */ static RTEXITCODE DoCleanup(int argc, char **argv) { /* * Parse the parameters. * * Note! The --base-dir is only for checking that the caller and this help * applications have the same idea of where things are. */ static const RTGETOPTDEF s_aOptions[] = { { "--base-dir", 'b', RTGETOPT_REQ_STRING }, }; RTGETOPTSTATE GetState; int rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /*fFlags*/); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); const char *pszBaseDir = NULL; RTGETOPTUNION ValueUnion; int ch; while ((ch = RTGetOpt(&GetState, &ValueUnion))) { switch (ch) { case 'b': if (pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Too many --base-dir options"); pszBaseDir = ValueUnion.psz; if (!IsValidBaseDir(pszBaseDir)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Invalid base directory: '%s'", pszBaseDir); break; case 'h': case 'V': return DoStandardOption(ch); default: return RTGetOptPrintError(ch, &ValueUnion); } } if (!pszBaseDir) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Missing --base-dir option"); /* * Ok, down to business. */ PRTDIR pDir; rc = RTDirOpen(&pDir, pszBaseDir); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed open the base directory: %Rrc ('%s')", rc, pszBaseDir); uint32_t cCleaned = 0; RTEXITCODE rcExit = RTEXITCODE_SUCCESS; for (;;) { RTDIRENTRYEX Entry; rc = RTDirReadEx(pDir, &Entry, NULL /*pcbDirEntry*/, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); if (RT_FAILURE(rc)) { if (rc != VERR_NO_MORE_FILES) rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirReadEx returns %Rrc", rc); break; } /* * Only directories which conform with our temporary install/uninstall * naming scheme are candidates for cleaning. */ if ( RTFS_IS_DIRECTORY(Entry.Info.Attr.fMode) && strcmp(Entry.szName, ".") != 0 && strcmp(Entry.szName, "..") != 0) { bool fCandidate = false; char *pszMarker = strstr(Entry.szName, "-_-"); if ( pszMarker && ( !strcmp(pszMarker, "-_-uninst") || !strncmp(pszMarker, "-_-inst", sizeof("-_-inst") - 1))) fCandidate = VBoxExtPackIsValidMangledName(Entry.szName, pszMarker - &Entry.szName[0]); if (fCandidate) { /* * Recursive delete, safe. */ char szPath[RTPATH_MAX]; rc = RTPathJoin(szPath, sizeof(szPath), pszBaseDir, Entry.szName); if (RT_SUCCESS(rc)) { RTEXITCODE rcExit2 = RemoveExtPackDir(szPath, true /*fTemporary*/); if (rcExit2 == RTEXITCODE_SUCCESS) RTMsgInfo("Successfully removed '%s'.", Entry.szName); else if (rcExit == RTEXITCODE_SUCCESS) rcExit = rcExit2; } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathJoin failed with %Rrc for '%s'", rc, Entry.szName); cCleaned++; } } } RTDirClose(pDir); if (!cCleaned) RTMsgInfo("Nothing to clean."); return rcExit; } #ifdef WITH_ELEVATION #if !defined(RT_OS_WINDOWS) && !defined(RT_OS_DARWIN) /** * Looks in standard locations for a suitable exec tool. * * @returns true if found, false if not. * @param pszPath Where to store the path to the tool on * successs. * @param cbPath The size of the buffer @a pszPath points to. * @param pszName The name of the tool we're looking for. */ static bool FindExecTool(char *pszPath, size_t cbPath, const char *pszName) { static const char * const s_apszPaths[] = { "/bin", "/usr/bin", "/usr/local/bin", "/sbin", "/usr/sbin", "/usr/local/sbin", #ifdef RT_OS_SOLARIS "/usr/sfw/bin", "/usr/gnu/bin", "/usr/xpg4/bin", "/usr/xpg6/bin", "/usr/openwin/bin", "/usr/ucb" #endif }; for (unsigned i = 0; i < RT_ELEMENTS(s_apszPaths); i++) { int rc = RTPathJoin(pszPath, cbPath, s_apszPaths[i], pszName); if (RT_SUCCESS(rc)) { RTFSOBJINFO ObjInfo; rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_FOLLOW_LINK); if (RT_SUCCESS(rc)) { if (!(ObjInfo.Attr.fMode & RTFS_UNIX_IWOTH)) return true; } } } return false; } #endif /** * Copies the content of a file to a stream. * * @param hSrc The source file. * @param pDst The destination stream. * @param fComplain Whether to complain about errors (i.e. is this * stderr, if not keep the trap shut because it * may be missing when running under VBoxSVC.) */ static void CopyFileToStdXxx(RTFILE hSrc, PRTSTREAM pDst, bool fComplain) { int rc; for (;;) { char abBuf[0x1000]; size_t cbRead; rc = RTFileRead(hSrc, abBuf, sizeof(abBuf), &cbRead); if (RT_FAILURE(rc)) { RTMsgError("RTFileRead failed: %Rrc", rc); break; } if (!cbRead) break; rc = RTStrmWrite(pDst, abBuf, cbRead); if (RT_FAILURE(rc)) { if (fComplain) RTMsgError("RTStrmWrite failed: %Rrc", rc); break; } } rc = RTStrmFlush(pDst); if (RT_FAILURE(rc) && fComplain) RTMsgError("RTStrmFlush failed: %Rrc", rc); } /** * Relaunches ourselves as a elevated process using platform specific facilities. * * @returns Program exit code. * @param pszExecPath The executable path. * @param papszArgs The arguments. * @param cSuArgs The number of argument entries reserved for the * 'su' like programs at the start of papszArgs. * @param cMyArgs The number of arguments following @a cSuArgs. * @param iCmd The command that is being executed. (For * selecting messages.) * @param pszDisplayInfoHack Display information hack. Platform specific++. */ static RTEXITCODE RelaunchElevatedNative(const char *pszExecPath, const char **papszArgs, int cSuArgs, int cMyArgs, int iCmd, const char *pszDisplayInfoHack) { RTEXITCODE rcExit = RTEXITCODE_FAILURE; #ifdef RT_OS_WINDOWS NOREF(iCmd); MSG Msg; PeekMessage(&Msg, NULL, 0, 0, PM_NOREMOVE); CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); SHELLEXECUTEINFOW Info; Info.cbSize = sizeof(Info); Info.fMask = SEE_MASK_NOCLOSEPROCESS; Info.hwnd = NULL; Info.lpVerb = L"runas"; int rc = RTStrToUtf16(pszExecPath, (PRTUTF16 *)&Info.lpFile); if (RT_SUCCESS(rc)) { char *pszCmdLine; rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs + 1], RTGETOPTARGV_CNV_QUOTE_MS_CRT); if (RT_SUCCESS(rc)) { rc = RTStrToUtf16(pszCmdLine, (PRTUTF16 *)&Info.lpParameters); if (RT_SUCCESS(rc)) { Info.lpDirectory = NULL; Info.nShow = SW_SHOWMAXIMIZED; Info.hInstApp = NULL; Info.lpIDList = NULL; Info.lpClass = NULL; Info.hkeyClass = NULL; Info.dwHotKey = 0; Info.hMonitor = NULL; Info.hProcess = INVALID_HANDLE_VALUE; /* Apply display hacks. */ if (pszDisplayInfoHack) { const char *pszArg = strstr(pszDisplayInfoHack, "hwnd="); if (pszArg) { uint64_t u64Hwnd; rc = RTStrToUInt64Ex(pszArg + sizeof("hwnd=") - 1, NULL, 0, &u64Hwnd); if (RT_SUCCESS(rc)) { HWND hwnd = (HWND)(uintptr_t)u64Hwnd; Info.hwnd = hwnd; Info.hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); } } } if (Info.hMonitor == NULL) { POINT Pt = {0,0}; Info.hMonitor = MonitorFromPoint(Pt, MONITOR_DEFAULTTOPRIMARY); } if (Info.hMonitor != NULL) Info.fMask |= SEE_MASK_HMONITOR; if (ShellExecuteExW(&Info)) { if (Info.hProcess != INVALID_HANDLE_VALUE) { /* * Wait for the process, make sure the deal with messages. */ for (;;) { DWORD dwRc = MsgWaitForMultipleObjects(1, &Info.hProcess, FALSE, 5000/*ms*/, QS_ALLEVENTS); if (dwRc == WAIT_OBJECT_0) break; if ( dwRc != WAIT_TIMEOUT && dwRc != WAIT_OBJECT_0 + 1) { RTMsgError("MsgWaitForMultipleObjects returned: %#x (%d), err=%u", dwRc, dwRc, GetLastError()); break; } MSG Msg; while (PeekMessageW(&Msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&Msg); DispatchMessageW(&Msg); } } DWORD dwExitCode; if (GetExitCodeProcess(Info.hProcess, &dwExitCode)) { if (dwExitCode >= 0 && dwExitCode < 128) rcExit = (RTEXITCODE)dwExitCode; else rcExit = RTEXITCODE_FAILURE; } CloseHandle(Info.hProcess); } else RTMsgError("ShellExecuteExW return INVALID_HANDLE_VALUE as Info.hProcess"); } else RTMsgError("ShellExecuteExW failed: %u (%#x)", GetLastError(), GetLastError()); RTUtf16Free((PRTUTF16)Info.lpParameters); } RTStrFree(pszCmdLine); } RTUtf16Free((PRTUTF16)Info.lpFile); } else RTMsgError("RTStrToUtf16 failed: %Rc", rc); #elif defined(RT_OS_DARWIN) char szIconName[RTPATH_MAX]; int rc = RTPathAppPrivateArch(szIconName, sizeof(szIconName)); if (RT_SUCCESS(rc)) rc = RTPathAppend(szIconName, sizeof(szIconName), "../Resources/virtualbox.png"); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to construct icon path: %Rrc", rc); AuthorizationRef AuthRef; OSStatus orc = AuthorizationCreate(NULL, 0, kAuthorizationFlagDefaults, &AuthRef); if (orc == errAuthorizationSuccess) { /* * Preautorize the privileged execution of ourselves. */ AuthorizationItem AuthItem = { kAuthorizationRightExecute, 0, NULL, 0 }; AuthorizationRights AuthRights = { 1, &AuthItem }; NOREF(iCmd); static char s_szPrompt[] = "VirtualBox needs further rights to make changes to your installation.\n\n"; AuthorizationItem aAuthEnvItems[] = { { kAuthorizationEnvironmentPrompt, strlen(s_szPrompt), s_szPrompt, 0 }, { kAuthorizationEnvironmentIcon, strlen(szIconName), szIconName, 0 } }; AuthorizationEnvironment AuthEnv = { RT_ELEMENTS(aAuthEnvItems), aAuthEnvItems }; orc = AuthorizationCopyRights(AuthRef, &AuthRights, &AuthEnv, kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, NULL); if (orc == errAuthorizationSuccess) { /* * Execute with extra permissions */ FILE *pSocketStrm; orc = AuthorizationExecuteWithPrivileges(AuthRef, pszExecPath, kAuthorizationFlagDefaults, (char * const *)&papszArgs[cSuArgs + 3], &pSocketStrm); if (orc == errAuthorizationSuccess) { /* * Read the output of the tool, the read will fail when it quits. */ for (;;) { char achBuf[1024]; size_t cbRead = fread(achBuf, 1, sizeof(achBuf), pSocketStrm); if (!cbRead) break; fwrite(achBuf, 1, cbRead, stdout); } rcExit = RTEXITCODE_SUCCESS; fclose(pSocketStrm); } else RTMsgError("AuthorizationExecuteWithPrivileges failed: %d", orc); } else if (orc == errAuthorizationCanceled) RTMsgError("Authorization canceled by the user"); else RTMsgError("AuthorizationCopyRights failed: %d", orc); AuthorizationFree(AuthRef, kAuthorizationFlagDefaults); } else RTMsgError("AuthorizationCreate failed: %d", orc); #else /* * Several of the alternatives below will require a command line. */ char *pszCmdLine; int rc = RTGetOptArgvToString(&pszCmdLine, &papszArgs[cSuArgs], RTGETOPTARGV_CNV_QUOTE_BOURNE_SH); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvToString failed: %Rrc"); /* * Look for various standard stuff for executing a program as root. * * N.B. When adding new arguments, please make 100% sure RelaunchElevated * allocates enough array entries. * * TODO: Feel free to contribute code for using PolicyKit directly. */ bool fHaveDisplayVar = RTEnvExist("DISPLAY"); int iSuArg = cSuArgs; char szExecTool[260]; char szXterm[260]; /* * kdesudo is available on KDE3/KDE4 */ if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "kdesudo")) { iSuArg = cSuArgs - 4; papszArgs[cSuArgs - 4] = szExecTool; papszArgs[cSuArgs - 3] = "--comment"; papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL ? "VirtualBox extension pack installer" : iCmd == CMD_UNINSTALL ? "VirtualBox extension pack uninstaller" : "VirtualBox extension pack maintainer"; papszArgs[cSuArgs - 1] = "--"; } /* * gksu is our favorite as it is very well integrated. */ else if (fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "gksu")) { #if 0 /* older gksu does not grok --description nor '--' and multiple args. */ iSuArg = cSuArgs - 4; papszArgs[cSuArgs - 4] = szExecTool; papszArgs[cSuArgs - 3] = "--description"; papszArgs[cSuArgs - 2] = iCmd == CMD_INSTALL ? "VirtualBox extension pack installer" : iCmd == CMD_UNINSTALL ? "VirtualBox extension pack uninstaller" : "VirtualBox extension pack maintainer"; papszArgs[cSuArgs - 1] = "--"; #elif defined(RT_OS_SOLARIS) /* Force it not to use pfexec as it won't wait then. */ iSuArg = cSuArgs - 4; papszArgs[cSuArgs - 4] = szExecTool; papszArgs[cSuArgs - 3] = "-au"; papszArgs[cSuArgs - 2] = "root"; papszArgs[cSuArgs - 1] = pszCmdLine; papszArgs[cSuArgs] = NULL; #else iSuArg = cSuArgs - 2; papszArgs[cSuArgs - 2] = szExecTool; papszArgs[cSuArgs - 1] = pszCmdLine; papszArgs[cSuArgs] = NULL; #endif } /* * pkexec may work for ssh console sessions as well if the right agents * are installed. However it is very generic and does not allow for any * custom messages. Thus it comes after gksu. */ else if (FindExecTool(szExecTool, sizeof(szExecTool), "pkexec")) { iSuArg = cSuArgs - 1; papszArgs[cSuArgs - 1] = szExecTool; } /* * The ultimate fallback is running 'su -' within an xterm. We use the * title of the xterm to tell what is going on. */ else if ( fHaveDisplayVar && FindExecTool(szExecTool, sizeof(szExecTool), "su") && FindExecTool(szXterm, sizeof(szXterm), "xterm")) { iSuArg = cSuArgs - 9; papszArgs[cSuArgs - 9] = szXterm; papszArgs[cSuArgs - 8] = "-T"; papszArgs[cSuArgs - 7] = iCmd == CMD_INSTALL ? "VirtualBox extension pack installer - su" : iCmd == CMD_UNINSTALL ? "VirtualBox extension pack uninstaller - su" : "VirtualBox extension pack maintainer - su"; papszArgs[cSuArgs - 6] = "-e"; papszArgs[cSuArgs - 5] = szExecTool; papszArgs[cSuArgs - 4] = "-"; papszArgs[cSuArgs - 3] = "root"; papszArgs[cSuArgs - 2] = "-c"; papszArgs[cSuArgs - 1] = pszCmdLine; papszArgs[cSuArgs] = NULL; } else if (fHaveDisplayVar) RTMsgError("Unable to locate 'pkexec', 'gksu' or 'su+xterm'. Try perform the operation using VBoxManage running as root"); else RTMsgError("Unable to locate 'pkexec'. Try perform the operation using VBoxManage running as root"); if (iSuArg != cSuArgs) { AssertRelease(iSuArg >= 0); /* * Argument list constructed, execute it and wait for the exec * program to complete. */ RTPROCESS hProcess; rc = RTProcCreateEx(papszArgs[iSuArg], &papszArgs[iSuArg], RTENV_DEFAULT, 0 /*fFlags*/, NULL /*phStdIn*/, NULL /*phStdOut*/, NULL /*phStdErr*/, NULL /*pszAsUser*/, NULL /*pszPassword*/, &hProcess); if (RT_SUCCESS(rc)) { RTPROCSTATUS Status; rc = RTProcWait(hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); if (RT_SUCCESS(rc)) { if (Status.enmReason == RTPROCEXITREASON_NORMAL) rcExit = (RTEXITCODE)Status.iStatus; else rcExit = RTEXITCODE_FAILURE; } else RTMsgError("Error while waiting for '%s': %Rrc", papszArgs[iSuArg], rc); } else RTMsgError("Failed to execute '%s': %Rrc", papszArgs[iSuArg], rc); } RTStrFree(pszCmdLine); #endif return rcExit; } /** * Relaunches ourselves as a elevated process using platform specific facilities. * * @returns Program exit code. * @param argc The number of arguments. * @param argv The arguments. * @param iCmd The command that is being executed. * @param pszDisplayInfoHack Display information hack. Platform specific++. */ static RTEXITCODE RelaunchElevated(int argc, char **argv, int iCmd, const char *pszDisplayInfoHack) { /* * We need the executable name later, so get it now when it's easy to quit. */ char szExecPath[RTPATH_MAX]; if (!RTProcGetExecutablePath(szExecPath,sizeof(szExecPath))) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed"); /* * Create a couple of temporary files for stderr and stdout. */ char szTempDir[RTPATH_MAX - sizeof("/stderr")]; int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathTemp failed: %Rrc", rc); rc = RTPathAppend(szTempDir, sizeof(szTempDir), "VBoxExtPackHelper-XXXXXX"); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathAppend failed: %Rrc", rc); rc = RTDirCreateTemp(szTempDir); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTDirCreateTemp failed: %Rrc", rc); RTEXITCODE rcExit = RTEXITCODE_FAILURE; char szStdOut[RTPATH_MAX]; char szStdErr[RTPATH_MAX]; rc = RTPathJoin(szStdOut, sizeof(szStdOut), szTempDir, "stdout"); if (RT_SUCCESS(rc)) rc = RTPathJoin(szStdErr, sizeof(szStdErr), szTempDir, "stderr"); if (RT_SUCCESS(rc)) { RTFILE hStdOut; rc = RTFileOpen(&hStdOut, szStdOut, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); if (RT_SUCCESS(rc)) { RTFILE hStdErr; rc = RTFileOpen(&hStdErr, szStdErr, RTFILE_O_READWRITE | RTFILE_O_CREATE | RTFILE_O_DENY_NONE | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); if (RT_SUCCESS(rc)) { /* * Insert the --elevated and stdout/err names into the argument * list. Note that darwin skips the --stdout bit, so don't * change the order here. */ int const cSuArgs = 12; int cArgs = argc + 5 + 1; char const **papszArgs = (char const **)RTMemTmpAllocZ((cSuArgs + cArgs + 1) * sizeof(const char *)); if (papszArgs) { int iDst = cSuArgs; papszArgs[iDst++] = argv[0]; papszArgs[iDst++] = "--stdout"; papszArgs[iDst++] = szStdOut; papszArgs[iDst++] = "--stderr"; papszArgs[iDst++] = szStdErr; papszArgs[iDst++] = "--elevated"; for (int iSrc = 1; iSrc <= argc; iSrc++) papszArgs[iDst++] = argv[iSrc]; /* * Do the platform specific process execution (waiting included). */ rcExit = RelaunchElevatedNative(szExecPath, papszArgs, cSuArgs, cArgs, iCmd, pszDisplayInfoHack); /* * Copy the standard files to our standard handles. */ CopyFileToStdXxx(hStdErr, g_pStdErr, true /*fComplain*/); CopyFileToStdXxx(hStdOut, g_pStdOut, false); RTMemTmpFree(papszArgs); } RTFileClose(hStdErr); RTFileDelete(szStdErr); } RTFileClose(hStdOut); RTFileDelete(szStdOut); } } RTDirRemove(szTempDir); return rcExit; } /** * Checks if the process is elevated or not. * * @returns RTEXITCODE_SUCCESS if preconditions are fine, * otherwise error message + RTEXITCODE_FAILURE. * @param pfElevated Where to store the elevation indicator. */ static RTEXITCODE ElevationCheck(bool *pfElevated) { *pfElevated = false; # if defined(RT_OS_WINDOWS) /** @todo This should probably check if UAC is diabled and if we are * Administrator first. Also needs to check for Vista+ first, probably. */ DWORD cb; RTEXITCODE rcExit = RTEXITCODE_SUCCESS; HANDLE hToken; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "OpenProcessToken failed: %u (%#x)", GetLastError(), GetLastError()); /* * Check if we're member of the Administrators group. If we aren't, there * is no way to elevate ourselves to system admin. * N.B. CheckTokenMembership does not do the job here (due to attributes?). */ BOOL fIsAdmin = FALSE; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID pAdminGrpSid; if (AllocateAndInitializeSid(&NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminGrpSid)) { # ifdef DEBUG char *pszAdminGrpSid = NULL; ConvertSidToStringSid(pAdminGrpSid, &pszAdminGrpSid); # endif if ( !GetTokenInformation(hToken, TokenGroups, NULL, 0, &cb) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemAllocZ(cb); if (GetTokenInformation(hToken, TokenGroups, pTokenGroups, cb, &cb)) { for (DWORD iGrp = 0; iGrp < pTokenGroups->GroupCount; iGrp++) { # ifdef DEBUG char *pszGrpSid = NULL; ConvertSidToStringSid(pTokenGroups->Groups[iGrp].Sid, &pszGrpSid); # endif if (EqualSid(pAdminGrpSid, pTokenGroups->Groups[iGrp].Sid)) { /* That it's listed is enough I think, ignore attributes. */ fIsAdmin = TRUE; break; } } } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,cb) failed: %u (%#x)", GetLastError(), GetLastError()); RTMemFree(pTokenGroups); } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation(TokenGroups,0) failed: %u (%#x)", GetLastError(), GetLastError()); FreeSid(pAdminGrpSid); } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "AllocateAndInitializeSid failed: %u (%#x)", GetLastError(), GetLastError()); if (fIsAdmin) { /* * Check the integrity level (Vista / UAC). */ # define MY_SECURITY_MANDATORY_HIGH_RID 0x00003000L # define MY_TokenIntegrityLevel ((TOKEN_INFORMATION_CLASS)25) if ( !GetTokenInformation(hToken, MY_TokenIntegrityLevel, NULL, 0, &cb) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { PSID_AND_ATTRIBUTES pSidAndAttr = (PSID_AND_ATTRIBUTES)RTMemAlloc(cb); if (GetTokenInformation(hToken, MY_TokenIntegrityLevel, pSidAndAttr, cb, &cb)) { DWORD dwIntegrityLevel = *GetSidSubAuthority(pSidAndAttr->Sid, *GetSidSubAuthorityCount(pSidAndAttr->Sid) - 1U); if (dwIntegrityLevel >= MY_SECURITY_MANDATORY_HIGH_RID) *pfElevated = true; } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError()); RTMemFree(pSidAndAttr); } else if ( GetLastError() == ERROR_INVALID_PARAMETER || GetLastError() == ERROR_NOT_SUPPORTED) *pfElevated = true; /* Older Windows version. */ else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "GetTokenInformation failed: %u (%#x)", GetLastError(), GetLastError()); } else rcExit = RTMsgErrorExit(RTEXITCODE_FAILURE, "Membership in the Administrators group is required to perform this action"); CloseHandle(hToken); return rcExit; # else /* * On Unixy systems, we check if the executable and the current user is * the same. This heuristic works fine for both hardened and development * builds. */ char szExecPath[RTPATH_MAX]; if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath)) == NULL) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcGetExecutablePath failed"); RTFSOBJINFO ObjInfo; int rc = RTPathQueryInfoEx(szExecPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTPathQueryInfoEx failed"); *pfElevated = ObjInfo.Attr.u.Unix.uid == geteuid() || ObjInfo.Attr.u.Unix.uid == getuid(); return RTEXITCODE_SUCCESS; # endif } #endif /* WITH_ELEVATION */ int main(int argc, char **argv) { /* * Initialize IPRT and check that we're correctly installed. */ int rc = RTR3InitExe(argc, &argv, 0); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); RTERRINFOSTATIC ErrInfo; RTErrInfoInitStatic(&ErrInfo); rc = SUPR3HardenedVerifySelf(argv[0], true /*fInternal*/, &ErrInfo.Core); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "%s", ErrInfo.Core.pszMsg); /* * Elevation check. */ const char *pszDisplayInfoHack = NULL; RTEXITCODE rcExit; #ifdef WITH_ELEVATION bool fElevated; rcExit = ElevationCheck(&fElevated); if (rcExit != RTEXITCODE_SUCCESS) return rcExit; #endif /* * Parse the top level arguments until we find a command. */ static const RTGETOPTDEF s_aOptions[] = { { "install", CMD_INSTALL, RTGETOPT_REQ_NOTHING }, { "uninstall", CMD_UNINSTALL, RTGETOPT_REQ_NOTHING }, { "cleanup", CMD_CLEANUP, RTGETOPT_REQ_NOTHING }, #ifdef WITH_ELEVATION { "--elevated", OPT_ELEVATED, RTGETOPT_REQ_NOTHING }, { "--stdout", OPT_STDOUT, RTGETOPT_REQ_STRING }, { "--stderr", OPT_STDERR, RTGETOPT_REQ_STRING }, #endif { "--display-info-hack", OPT_DISP_INFO_HACK, RTGETOPT_REQ_STRING }, }; RTGETOPTSTATE GetState; rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 1, 0 /*fFlags*/); if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptInit failed: %Rrc\n", rc); for (;;) { RTGETOPTUNION ValueUnion; int ch = RTGetOpt(&GetState, &ValueUnion); switch (ch) { case 0: return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No command specified"); case CMD_INSTALL: case CMD_UNINSTALL: case CMD_CLEANUP: { #ifdef WITH_ELEVATION if (!fElevated) return RelaunchElevated(argc, argv, ch, pszDisplayInfoHack); #endif int cCmdargs = argc - GetState.iNext; char **papszCmdArgs = argv + GetState.iNext; switch (ch) { case CMD_INSTALL: rcExit = DoInstall( cCmdargs, papszCmdArgs); break; case CMD_UNINSTALL: rcExit = DoUninstall(cCmdargs, papszCmdArgs); break; case CMD_CLEANUP: rcExit = DoCleanup( cCmdargs, papszCmdArgs); break; default: AssertReleaseFailedReturn(RTEXITCODE_FAILURE); } /* * Standard error should end with rcExit=RTEXITCODE_SUCCESS on * success since the exit code may otherwise get lost in the * process elevation fun. */ RTStrmFlush(g_pStdOut); RTStrmFlush(g_pStdErr); switch (rcExit) { case RTEXITCODE_SUCCESS: RTStrmPrintf(g_pStdErr, "rcExit=RTEXITCODE_SUCCESS\n"); break; default: RTStrmPrintf(g_pStdErr, "rcExit=%d\n", rcExit); break; } RTStrmFlush(g_pStdErr); RTStrmFlush(g_pStdOut); return rcExit; } #ifdef WITH_ELEVATION case OPT_ELEVATED: fElevated = true; break; case OPT_STDERR: case OPT_STDOUT: { FILE *pFile = freopen(ValueUnion.psz, "r+", ch == OPT_STDOUT ? stdout : stderr); if (!pFile) { rc = RTErrConvertFromErrno(errno); return RTMsgErrorExit(RTEXITCODE_FAILURE, "freopen on '%s': %Rrc", ValueUnion.psz, rc); } break; } #endif case OPT_DISP_INFO_HACK: if (pszDisplayInfoHack) return RTMsgErrorExit(RTEXITCODE_SYNTAX, "--display-info-hack shall only occur once"); pszDisplayInfoHack = ValueUnion.psz; break; case 'h': case 'V': return DoStandardOption(ch); default: return RTGetOptPrintError(ch, &ValueUnion); } /* not currently reached */ } /* not reached */ } #ifdef RT_OS_WINDOWS extern "C" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { g_hInstance = hInstance; NOREF(hPrevInstance); NOREF(nShowCmd); NOREF(lpCmdLine); int rc = RTR3InitExeNoArguments(0); if (RT_FAILURE(rc)) return RTMsgInitFailure(rc); LPWSTR pwszCmdLine = GetCommandLineW(); if (!pwszCmdLine) return RTMsgErrorExit(RTEXITCODE_FAILURE, "GetCommandLineW failed"); char *pszCmdLine; rc = RTUtf16ToUtf8(pwszCmdLine, &pszCmdLine); /* leaked */ if (RT_FAILURE(rc)) return RTMsgErrorExit(RTEXITCODE_FAILURE, "Failed to convert the command line: %Rrc", rc); int cArgs; char **papszArgs; rc = RTGetOptArgvFromString(&papszArgs, &cArgs, pszCmdLine, NULL); if (RT_SUCCESS(rc)) { rc = main(cArgs, papszArgs); RTGetOptArgvFree(papszArgs); } else rc = RTMsgErrorExit(RTEXITCODE_FAILURE, "RTGetOptArgvFromString failed: %Rrc", rc); RTStrFree(pszCmdLine); return rc; } #endif