/* $Id$ */
/** @file
* VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
*/
/*
* 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;
* 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 <iprt/manifest.h>
/**
* Worker for VBoxExtPackLoadDesc that loads the plug-in descriptors.
*
* @returns Same as VBoxExtPackLoadDesc.
* @param pVBoxExtPackElm
* @param pcPlugIns Where to return the number of plug-ins in the
* array.
* @param paPlugIns Where to return the plug-in descriptor array.
* (RTMemFree it even on failure)
*/
static RTCString *
{
*pcPlugIns = 0;
/** @todo plug-ins */
return NULL;
}
/**
* Clears the extension pack descriptor.
*
* @param a_pExtPackDesc The descriptor to clear.
*/
{
a_pExtPackDesc->uRevision = 0;
a_pExtPackDesc->cPlugIns = 0;
a_pExtPackDesc->fShowLicense = false;
}
/**
* Initializes an extension pack descriptor so that it's safe to call free on
* it whatever happens later on.
*
* @param a_pExtPackDesc The descirptor to initialize.
*/
{
}
/**
* Load the extension pack descriptor from an XML document.
*
* @returns NULL on success, pointer to an error message on failure (caller
* deletes it).
* @param a_pDoc Pointer to the XML document.
* @param a_pExtPackDesc Where to store the extension pack descriptor.
*/
static RTCString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
{
/*
* Get the main element and check its version.
*/
if ( !pVBoxExtPackElm
return new RTCString("No VirtualBoxExtensionPack element");
return new RTCString("Missing format version");
/*
* Read and validate mandatory bits.
*/
if (!pNameElm)
return new RTCString("The 'Name' element is missing");
if (!VBoxExtPackIsValidName(pszName))
if (!pDescElm)
return new RTCString("The 'Description' element is missing");
return new RTCString("The 'Description' element is empty");
return new RTCString("The 'Description' must not contain control characters");
if (!pVersionElm)
return new RTCString("The 'Version' element is missing");
return new RTCString("The 'Version' element is empty");
uRevision = 0;
const char *pszEdition;
pszEdition = "";
if (!pMainModuleElm)
return new RTCString("The 'MainModule' element is missing");
return new RTCString("The 'MainModule' element is empty");
/*
* The VRDE module, optional.
* Accept both none and empty as tokens of no VRDE module.
*/
if (pVrdeModuleElm)
{
else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
}
/*
* Whether to show the license, optional. (presense is enough here)
*/
/*
* Parse plug-in descriptions (last because of the manual memory management).
*/
if (pstrRet)
{
return pstrRet;
}
/*
* Everything seems fine, fill in the return values and return successfully.
*/
return NULL;
}
/**
* Reads the extension pack descriptor.
*
* @returns NULL on success, pointer to an error message on failure (caller
* deletes it).
* @param a_pszDir The directory containing the description file.
* @param a_pExtPackDesc Where to store the extension pack descriptor.
* @param a_pObjInfo Where to store the object info for the file (unix
* attribs). Optional.
*/
RTCString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
{
/*
* Validate, open and parse the XML file.
*/
if (RT_FAILURE(vrc))
if (RT_FAILURE(vrc))
if (a_pObjInfo)
*a_pObjInfo = ObjInfo;
{
return new RTCString("The XML file is symlinked, that is not allowed");
}
{
try
{
}
{
}
}
/*
* Hand the xml doc over to the common code.
*/
}
/**
* Reads the extension pack descriptor.
*
* @returns NULL on success, pointer to an error message on failure (caller
* deletes it).
* @param a_pszDir The directory containing the description file.
* @param a_pExtPackDesc Where to store the extension pack descriptor.
* @param a_pObjInfo Where to store the object info for the file (unix
* attribs). Optional.
*/
RTCString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
{
/*
* Query the object info.
*/
if (RT_FAILURE(rc))
if (a_pObjInfo)
*a_pObjInfo = ObjInfo;
/*
* The simple approach, read the whole thing into memory and pass this to
* the XML parser.
*/
/* Check the file size. */
/* Rewind to the start of the file. */
if (RT_FAILURE(rc))
/* Allocate memory and read the file content into it. */
if (!pvFile)
if (RT_FAILURE(rc))
/*
* Parse the file.
*/
if (RT_SUCCESS(rc))
{
try
{
}
{
}
}
/*
* Hand the xml doc over to the common code.
*/
if (RT_SUCCESS(rc))
return pstrErr;
}
/**
* Frees all resources associated with a extension pack descriptor.
*
* @param a_pExtPackDesc The extension pack descriptor which members
* should be freed.
*/
{
if (!a_pExtPackDesc)
return;
a_pExtPackDesc->uRevision = 0;
a_pExtPackDesc->cPlugIns = 0;
a_pExtPackDesc->fShowLicense = false;
}
/**
* Extract the extension pack name from the tarball path.
*
* @returns String containing the name on success, the caller must delete it.
* NULL if no valid name was found or if we ran out of memory.
* @param pszTarball The path to the tarball.
*/
{
/*
* Skip ahead to the filename part and count the number of characters
* that matches the criteria for a mangled extension pack name.
*/
if (!pszSrc)
return NULL;
off++;
/*
* Check min and max name limits.
*/
if ( off > VBOX_EXTPACK_NAME_MAX_LEN
|| off < VBOX_EXTPACK_NAME_MIN_LEN)
return NULL;
/*
* Return the unmangled name.
*/
}
/**
* Validates the extension pack name.
*
* @returns true if valid, false if not.
* @param pszName The name to validate.
* @sa VBoxExtPackExtractNameFromTarballPath
*/
{
if (!pszName)
return false;
/*
* Check the characters making up the name, only english alphabet
* characters, decimal digits and spaces are allowed.
*/
{
return false;
off++;
}
/*
* Check min and max name limits.
*/
if ( off > VBOX_EXTPACK_NAME_MAX_LEN
|| off < VBOX_EXTPACK_NAME_MIN_LEN)
return false;
return true;
}
/**
* Checks if an alledged manged extension pack name.
*
* @returns true if valid, false if not.
* @param pszMangledName The mangled name to validate.
* @param cchMax The max number of chars to test.
* @sa VBoxExtPackMangleName
*/
{
if (!pszMangledName)
return false;
/*
* Check the characters making up the name, only english alphabet
* characters, decimal digits and underscores (=space) are allowed.
*/
{
return false;
off++;
}
/*
* Check min and max name limits.
*/
if ( off > VBOX_EXTPACK_NAME_MAX_LEN
|| off < VBOX_EXTPACK_NAME_MIN_LEN)
return false;
return true;
}
/**
* Mangle an extension pack name so it can be used by a directory or file name.
*
* @returns String containing the mangled name on success, the caller must
* delete it. NULL on failure.
* @param pszName The unmangled name.
* @sa VBoxExtPackUnmangleName, VBoxExtPackIsValidMangledName
*/
{
char ch;
{
if (ch == ' ')
ch = '_';
}
}
/**
* Unmangle an extension pack name (reverses VBoxExtPackMangleName).
*
* @returns String containing the mangled name on success, the caller must
* delete it. NULL on failure.
* @param pszMangledName The mangled name.
* @param cchMax The max name length. RTSTR_MAX is fine.
* @sa VBoxExtPackMangleName, VBoxExtPackIsValidMangledName
*/
{
char ch;
{
if (ch == '_')
ch = ' ';
else
}
}
/**
* Constructs the extension pack directory path.
*
* A combination of RTPathJoin and VBoxExtPackMangleName.
*
* @returns IPRT status code like RTPathJoin.
* @param pszExtPackDir Where to return the directory path.
* @param cbExtPackDir The size of the return buffer.
* @param pszParentDir The parent directory (".../Extensions").
* @param pszName The extension pack name, unmangled.
*/
int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName)
{
if (!pstrMangledName)
return VERR_INTERNAL_ERROR_4;
delete pstrMangledName;
return vrc;
}
/**
* Validates the extension pack version string.
*
* @returns true if valid, false if not.
* @param pszVersion The version string to validate.
*/
{
return false;
/* 1.x.y.z... */
for (;;)
{
if (!RT_C_IS_DIGIT(*pszVersion))
return false;
do
pszVersion++;
while (RT_C_IS_DIGIT(*pszVersion));
if (*pszVersion != '.')
break;
pszVersion++;
}
/* upper case string + numbers indicating the build type */
{
/** @todo Should probably restrict this to known build types (alpha,
* beta, rc, ++). */
do
pszVersion++;
while ( RT_C_IS_DIGIT(*pszVersion)
|| RT_C_IS_UPPER(*pszVersion)
|| *pszVersion == '-'
|| *pszVersion == '_');
}
return *pszVersion == '\0';
}
/**
* Validates the extension pack edition string.
*
* @returns true if valid, false if not.
* @param pszEdition The edition string to validate.
*/
{
if (*pszEdition)
{
if (!RT_C_IS_UPPER(*pszEdition))
return false;
do
pszEdition++;
while ( RT_C_IS_UPPER(*pszEdition)
|| RT_C_IS_DIGIT(*pszEdition)
|| *pszEdition == '-'
|| *pszEdition == '_');
}
return *pszEdition == '\0';
}
/**
* Validates an extension pack module string.
*
* @returns true if valid, false if not.
* @param pszModule The module string to validate.
*/
{
return false;
/* Restricted charset, no extensions (dots). */
while ( RT_C_IS_ALNUM(*pszModule)
|| *pszModule == '-'
|| *pszModule == '_')
pszModule++;
return *pszModule == '\0';
}
/**
* RTStrPrintfv wrapper.
*
* @returns @a rc
* @param rc The status code to return.
* @param pszError The error buffer.
* @param cbError The size of the buffer.
* @param pszFormat The error message format string.
* @param ... Format arguments.
*/
static int vboxExtPackReturnError(int rc, char *pszError, size_t cbError, const char *pszFormat, ...)
{
return rc;
}
/**
* RTStrPrintfv wrapper.
*
* @param pszError The error buffer.
* @param cbError The size of the buffer.
* @param pszFormat The error message format string.
* @param ... Format arguments.
*/
{
}
/**
* Verifies the manifest and its signature.
*
* @returns VBox status code, failures with message.
* @param hManifestFile The xml from the extension pack.
* @param pszExtPackName The expected extension pack name. This can be
* NULL, in which we don't have any expectations.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
static int vboxExtPackVerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName, char *pszError, size_t cbError)
{
/*
* Load the XML.
*/
if (pstrErr)
{
delete pstrErr;
return VERR_PARSE_ERROR;
}
/*
* Check the name.
*/
/** @todo drop this restriction after the old install interface is
* dropped. */
if ( pszExtPackName
"The name of the downloaded file and the name stored inside the extension pack does not match"
return rc;
}
/**
* Verifies the manifest and its signature.
*
* @returns VBox status code, failures with message.
* @param hOurManifest The manifest we compiled.
* @param hManifestFile The manifest file in the extension pack.
* @param hSignatureFile The manifest signature file.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile,
{
/*
* Read the manifest from the extension pack.
*/
if (RT_FAILURE(rc))
if (RT_FAILURE(rc))
if (RT_SUCCESS(rc))
{
/*
* Compare the manifests.
*/
static const char *s_apszIgnoreEntries[] =
{
};
RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
if (RT_SUCCESS(rc))
{
/*
* Validate the manifest file signature.
*/
/** @todo implement signature stuff */
}
else
#if 0
#endif
}
else
return rc;
}
/**
* Verifies the file digest (if specified) and returns the SHA-256 of the file.
*
* @returns
* @param hFileManifest Manifest containing a SHA-256 digest of the file
* that was calculated as the file was processed.
* @param pszFileDigest SHA-256 digest of the file.
* @param pStrDigest Where to return the SHA-256 digest. Optional.
* @param pszError Where to write an error message on failure.
* @param cbError The size of the @a pszError buffer.
*/
{
/*
* Extract the SHA-256 entry for the extpack file.
*/
int rc = RTManifestEntryQueryAttr(hFileManifest, "extpack", NULL /*no name*/, RTMANIFEST_ATTR_SHA256,
if (RT_SUCCESS(rc))
{
/*
* Convert the two strings to binary form before comparing.
* We convert the calculated hash even if we don't have anything to
* compare with, just to validate it.
*/
if (RT_SUCCESS(rc))
{
if ( pszFileDigest
&& *pszFileDigest != '\0')
{
if (RT_SUCCESS(rc))
{
{
rc = VERR_NOT_EQUAL;
}
}
else
}
/*
* Set the output hash on success.
*/
{
try
{
}
{
rc = VERR_NO_MEMORY;
}
}
}
else
}
else
return rc;
}
/**
* Validates a standard file.
*
* Generally all files are
*
* @returns VBox status code, failure message in @a pszError.
* @param pszAdjName The adjusted member name.
* @param enmType The VFS object type.
* @param phVfsObj The pointer to the VFS object handle variable.
* This is both input and output.
* @param phVfsFile Where to store the handle to the memorized
* file. This is NULL for license files.
* @param pszError Where to write an error message on failure.
* @param cbError The size of the @a pszError buffer.
*/
{
int rc;
/*
* Make sure it's a file and that it isn't too large.
*/
"There can only be one '%s'", pszAdjName);
"Standard member '%s' is not a file", pszAdjName);
else
{
if (RT_SUCCESS(rc))
{
"Standard member '%s' is not a file", pszAdjName);
"Standard member '%s' is too large: %'RU64 bytes (max 1 MB)",
else
{
/*
* Make an in memory copy of the stream and check that the file
* is UTF-8 clean.
*/
if (RT_SUCCESS(rc))
{
NULL);
if (RT_SUCCESS(rc))
{
/*
* Replace *phVfsObj with the memorized file.
*/
if (RT_SUCCESS(rc))
{
}
else
}
else
}
else
vboxExtPackSetError(pszError, cbError, "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszAdjName, rc);
}
}
else
}
return rc;
}
/**
* Validates a name in an extension pack.
*
* We restrict the charset to try make sure the extension pack can be unpacked
* on all file systems.
*
* @returns VBox status code, failures with message.
* @param pszName The name to validate.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
{
if (RTPathStartsWithRoot(pszName))
return vboxExtPackReturnError(VERR_PATH_IS_NOT_RELATIVE, pszError, cbError, "'%s': starts with root spec", pszName);
int ch;
{
/* Character set restrictions. */
{
pszErr = "Only 7-bit ASCII allowed";
break;
}
{
pszErr = "No control characters are not allowed";
break;
}
if (ch == '\\')
{
pszErr = "Only backward slashes are not allowed";
break;
}
{
pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
break;
}
/* Take the simple way out and ban all ".." sequences. */
if ( ch == '.'
{
pszErr = "Double dot sequence are not allowed";
break;
}
/* Keep the tree shallow or the hardening checks will fail. */
{
pszErr = "Too long";
break;
}
/* advance */
psz++;
}
if (pszErr)
return RTEXITCODE_SUCCESS;
}
/**
* Validates a file in an extension pack.
*
* @returns VBox status code, failures with message.
* @param pszName The name of the file.
* @param hVfsObj The VFS object.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
static int vboxExtPackValidateMemberFile(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
"'%s': too large (%'RU64 bytes)",
"The alleged file '%s' has a mode mask stating otherwise (%RTfmode)",
}
else
}
return rc;
}
/**
* Validates a directory in an extension pack.
*
* @returns VBox status code, failures with message.
* @param pszName The name of the directory.
* @param hVfsObj The VFS object.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
static int vboxExtPackValidateMemberDir(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
"The alleged directory '%s' has a mode mask saying differently (%RTfmode)",
}
else
}
return rc;
}
/**
* Validates a member of an extension pack.
*
* @returns VBox status code, failures with message.
* @param pszName The name of the directory.
* @param enmType The object type.
* @param hVfsObj The VFS object.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
*/
int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
{
*pszError = '\0';
int rc;
if ( enmType == RTVFSOBJTYPE_FILE
|| enmType == RTVFSOBJTYPE_IO_STREAM)
else if ( enmType == RTVFSOBJTYPE_DIR
|| enmType == RTVFSOBJTYPE_BASE)
else
return rc;
}
/**
* Rewinds the tarball file handle and creates a gunzip | tar chain that
* results in a filesystem stream.
*
* @returns VBox status code, failures with message.
* @param hTarballFile The handle to the tarball file.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
* @param phTarFss Where to return the filesystem stream handle.
* @param phFileManifest Where to return a manifest where the tarball is
* gettting hashed. The entry will be called
* "extpack" and be ready when the file system
* stream is at an end. Optional.
*/
int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss,
{
*pszError = '\0';
/*
* Rewind the file and set up a VFS chain for it.
*/
if (RT_FAILURE(rc))
return vboxExtPackReturnError(rc, pszError, cbError, "Failed seeking to the start of the tarball: %Rrc", rc);
rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
&hTarballIos);
if (RT_FAILURE(rc))
if (RT_SUCCESS(rc))
{
rc = RTManifestEntryAddPassthruIoStream(hFileManifest, hTarballIos, "extpack", RTMANIFEST_ATTR_SHA256,
true /*read*/, &hPtIos);
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (RT_SUCCESS(rc))
{
if (phFileManifest)
else
return VINF_SUCCESS;
}
}
else
}
else
}
else
return rc;
}
/**
* Validates the extension pack tarball prior to unpacking.
*
* Operations performed:
* - Mandatory files.
* - Manifest check.
* - Manifest seal check.
* - XML check, match name.
*
* @returns VBox status code, failures with message.
* @param hTarballFile The handle to open the @a pszTarball file.
* @param pszExtPackName The name of the extension pack name. NULL if
* the name is not fixed.
* @param pszTarball The name of the tarball in case we have to
* complain about something.
* @param pszTarballDigest The SHA-256 digest of the tarball. Empty string
* if no digest available.
* @param pszError Where to store an error message on failure.
* @param cbError The size of the buffer @a pszError points to.
* @param phValidManifest Where to optionally return the handle to fully
* validated the manifest for the extension pack.
* This includes all files.
* @param phXmlFile Where to optionally return the memorized XML
* file.
* @param pStrDigest Where to return the digest of the file.
* Optional.
*/
const char *pszTarball, const char *pszTarballDigest,
{
/*
* Clear return values.
*/
if (phValidManifest)
if (phXmlFile)
*pszError = '\0';
/*
* Open the tar.gz filesystem stream and set up an manifest in-memory file.
*/
if (RT_FAILURE(rc))
return rc;
if (RT_SUCCESS(rc))
{
/*
* Process the tarball (would be nice to move this to a function).
*/
for (;;)
{
/*
* Get the next stream object.
*/
char *pszName;
if (RT_FAILURE(rc))
{
else
rc = VINF_SUCCESS;
break;
}
/*
* Check the type & name validity, performing special tests on
* standard extension pack member files.
*
* N.B. We will always reach the end of the loop before breaking on
* failure - cleanup reasons.
*/
if (RT_SUCCESS(rc))
{
else if (!strncmp(pszAdjName, VBOX_EXTPACK_LICENSE_NAME_PREFIX, sizeof(VBOX_EXTPACK_LICENSE_NAME_PREFIX) - 1))
if (phVfsFile)
}
/*
* Add any I/O stream to the manifest
*/
if ( RT_SUCCESS(rc)
&& ( enmType == RTVFSOBJTYPE_FILE
|| enmType == RTVFSOBJTYPE_IO_STREAM))
{
rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
if (RT_FAILURE(rc))
vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc);
}
/*
* Clean up and break out on failure.
*/
if (RT_FAILURE(rc))
break;
}
/*
* Check the integrity of the tarball file.
*/
if (RT_SUCCESS(rc))
{
}
/*
* If we've successfully processed the tarball, verify that the
* mandatory files are present.
*/
if (RT_SUCCESS(rc))
{
if (hXmlFile == NIL_RTVFSFILE)
if (hManifestFile == NIL_RTVFSFILE)
if (hSignatureFile == NIL_RTVFSFILE)
}
/*
* Check the manifest and it's signature.
*/
if (RT_SUCCESS(rc))
rc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError);
/*
* Check the XML.
*/
if (RT_SUCCESS(rc))
/*
* Returns objects.
*/
if (RT_SUCCESS(rc))
{
if (phValidManifest)
{
}
if (phXmlFile)
{
}
}
/*
* Release our object references.
*/
}
else
return rc;
}