ExtPackUtil.cpp revision f7d0672fb68919a6a824f47d7ef72b9b7bd0c266
/* $Id$ */
/** @file
* VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
*/
/*
* Copyright (C) 2010 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 iprt::MiniString *
{
*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;
}
/**
* 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 the XML document.
* @param a_pExtPackDesc Where to store the extension pack descriptor.
*/
static iprt::MiniString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
{
/*
* Get the main element and check its version.
*/
if ( !pVBoxExtPackElm
/*
* Read and validate mandatory bits.
*/
if (!pNameElm)
if (!VBoxExtPackIsValidName(pszName))
if (!pDescElm)
if (!pVersionElm)
uRevision = 0;
if (!pMainModuleElm)
/*
* The VRDE module, optional.
* Accept both none and empty as tokens of no VRDE module.
*/
const char *pszVrdeModule = NULL;
if (pVrdeModuleElm)
{
else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
}
/*
* Parse plug-in descriptions.
*/
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.
*/
iprt::MiniString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
{
/*
* Validate, open and parse the XML file.
*/
char szFilePath[RTPATH_MAX];
if (RT_FAILURE(vrc))
if (RT_FAILURE(vrc))
if (a_pObjInfo)
*a_pObjInfo = ObjInfo;
{
return &(new iprt::MiniString)->printf("The XML file is not a file (fMode=%#x)", ObjInfo.Attr.fMode);
}
{
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.
*/
iprt::MiniString *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. */
return &(new iprt::MiniString)->printf("The XML file is too large (%'RU64 bytes)", ObjInfo.cbObject);
/* 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;
}
/**
* 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
*/
bool VBoxExtPackIsValidName(const char *pszName)
{
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.
*/
bool VBoxExtPackIsValidVersionString(const char *pszVersion)
{
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 */
{
do
pszVersion++;
while ( RT_C_IS_DIGIT(*pszVersion)
|| RT_C_IS_UPPER(*pszVersion)
|| *pszVersion == '-'
|| *pszVersion == '_');
}
/* revision or nothing */
if (*pszVersion != '\0')
{
if (*pszVersion != 'r')
return false;
do
pszVersion++;
while (RT_C_IS_DIGIT(*pszVersion));
}
return *pszVersion == '\0';
}
/**
* Validates an extension pack module string.
*
* @returns true if valid, false if not.
* @param pszModule The module string to validate.
*/
bool VBoxExtPackIsValidModuleString(const char *pszModule)
{
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. */
int rc = VINF_SUCCESS;
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[] =
{
};
char szError[RTPATH_MAX];
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;
}
/**
* 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.
*/
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))
{
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
}
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 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.
*
* @todo This function is a bit too long and should be split up if possible.
*/
int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
{
/*
* 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.
*
* N.B. We will always reach the end of the loop before breaking on
* failure - cleanup reasons.
*/
if (RT_SUCCESS(rc))
{
/*
* Check if this is one of the standard files.
*/
else
if (phVfsFile)
{
/*
* Make sure it's a file and that it isn't too large.
*/
if (*phVfsFile != NIL_RTVFSFILE)
"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.
*/
if (RT_SUCCESS(rc))
{
/*
* To simplify the code below, replace
* hVfsObj with the memorized file.
*/
}
else
vboxExtPackSetError(pszError, cbError, "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszName, rc);
}
}
else
}
}
}
/*
* 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;
}
/*
* If we've successfully processed the tarball, verify that the
* mandatory files are present.
*/
if (RT_SUCCESS(rc))
{
if (hXmlFile == NIL_RTVFSFILE)
rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_DESCRIPTION_NAME);
if (hManifestFile == NIL_RTVFSFILE)
rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_MANIFEST_NAME);
if (hSignatureFile == NIL_RTVFSFILE)
rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_SIGNATURE_NAME);
}
/*
* 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;
}