VirtualBox

source: vbox/trunk/src/VBox/Main/ExtPackUtil.cpp@ 34927

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

ExtPack: First part of the extension pack license code.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 41.5 KB
Line 
1/* $Id: ExtPackUtil.cpp 34893 2010-12-09 14:44:38Z vboxsync $ */
2/** @file
3 * VirtualBox Main - Extension Pack Utilities and definitions, VBoxC, VBoxSVC, ++.
4 */
5
6/*
7 * Copyright (C) 2010 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*******************************************************************************
20* Header Files *
21*******************************************************************************/
22#include "include/ExtPackUtil.h"
23
24#include <iprt/ctype.h>
25#include <iprt/dir.h>
26#include <iprt/file.h>
27#include <iprt/manifest.h>
28#include <iprt/param.h>
29#include <iprt/path.h>
30#include <iprt/string.h>
31#include <iprt/vfs.h>
32#include <iprt/tar.h>
33#include <iprt/zip.h>
34#include <iprt/cpp/xml.h>
35
36#include <VBox/log.h>
37
38
39/**
40 * Worker for VBoxExtPackLoadDesc that loads the plug-in descriptors.
41 *
42 * @returns Same as VBoxExtPackLoadDesc.
43 * @param pVBoxExtPackElm
44 * @param pcPlugIns Where to return the number of plug-ins in the
45 * array.
46 * @param paPlugIns Where to return the plug-in descriptor array.
47 * (RTMemFree it even on failure)
48 */
49static iprt::MiniString *
50vboxExtPackLoadPlugInDescs(const xml::ElementNode *pVBoxExtPackElm,
51 uint32_t *pcPlugIns, PVBOXEXTPACKPLUGINDESC *paPlugIns)
52{
53 *pcPlugIns = 0;
54 *paPlugIns = NULL;
55
56 /** @todo plug-ins */
57 NOREF(pVBoxExtPackElm);
58
59 return NULL;
60}
61
62/**
63 * Clears the extension pack descriptor.
64 *
65 * @param a_pExtPackDesc The descriptor to clear.
66 */
67static void vboxExtPackClearDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
68{
69 a_pExtPackDesc->strName.setNull();
70 a_pExtPackDesc->strDescription.setNull();
71 a_pExtPackDesc->strVersion.setNull();
72 a_pExtPackDesc->uRevision = 0;
73 a_pExtPackDesc->strMainModule.setNull();
74 a_pExtPackDesc->strVrdeModule.setNull();
75 a_pExtPackDesc->cPlugIns = 0;
76 a_pExtPackDesc->paPlugIns = NULL;
77 a_pExtPackDesc->fShowLicense = false;
78}
79
80/**
81 * Load the extension pack descriptor from an XML document.
82 *
83 * @returns NULL on success, pointer to an error message on failure (caller
84 * deletes it).
85 * @param a_pDoc Pointer to the the XML document.
86 * @param a_pExtPackDesc Where to store the extension pack descriptor.
87 */
88static iprt::MiniString *vboxExtPackLoadDescFromDoc(xml::Document *a_pDoc, PVBOXEXTPACKDESC a_pExtPackDesc)
89{
90 /*
91 * Get the main element and check its version.
92 */
93 const xml::ElementNode *pVBoxExtPackElm = a_pDoc->getRootElement();
94 if ( !pVBoxExtPackElm
95 || strcmp(pVBoxExtPackElm->getName(), "VirtualBoxExtensionPack") != 0)
96 return new iprt::MiniString("No VirtualBoxExtensionPack element");
97
98 iprt::MiniString strFormatVersion;
99 if (!pVBoxExtPackElm->getAttributeValue("version", strFormatVersion))
100 return new iprt::MiniString("Missing format version");
101 if (!strFormatVersion.equals("1.0"))
102 return &(new iprt::MiniString("Unsupported format version: "))->append(strFormatVersion);
103
104 /*
105 * Read and validate mandatory bits.
106 */
107 const xml::ElementNode *pNameElm = pVBoxExtPackElm->findChildElement("Name");
108 if (!pNameElm)
109 return new iprt::MiniString("The 'Name' element is missing");
110 const char *pszName = pNameElm->getValue();
111 if (!VBoxExtPackIsValidName(pszName))
112 return &(new iprt::MiniString("Invalid name: "))->append(pszName);
113
114 const xml::ElementNode *pDescElm = pVBoxExtPackElm->findChildElement("Description");
115 if (!pDescElm)
116 return new iprt::MiniString("The 'Description' element is missing");
117 const char *pszDesc = pDescElm->getValue();
118 if (!pszDesc || *pszDesc == '\0')
119 return new iprt::MiniString("The 'Description' element is empty");
120 if (strpbrk(pszDesc, "\n\r\t\v\b") != NULL)
121 return new iprt::MiniString("The 'Description' must not contain control characters");
122
123 const xml::ElementNode *pVersionElm = pVBoxExtPackElm->findChildElement("Version");
124 if (!pVersionElm)
125 return new iprt::MiniString("The 'Version' element is missing");
126 const char *pszVersion = pVersionElm->getValue();
127 if (!pszVersion || *pszVersion == '\0')
128 return new iprt::MiniString("The 'Version' element is empty");
129 if (!VBoxExtPackIsValidVersionString(pszVersion))
130 return &(new iprt::MiniString("Invalid version string: "))->append(pszVersion);
131
132 uint32_t uRevision;
133 if (!pVersionElm->getAttributeValue("revision", uRevision))
134 uRevision = 0;
135
136 const xml::ElementNode *pMainModuleElm = pVBoxExtPackElm->findChildElement("MainModule");
137 if (!pMainModuleElm)
138 return new iprt::MiniString("The 'MainModule' element is missing");
139 const char *pszMainModule = pMainModuleElm->getValue();
140 if (!pszMainModule || *pszMainModule == '\0')
141 return new iprt::MiniString("The 'MainModule' element is empty");
142 if (!VBoxExtPackIsValidModuleString(pszMainModule))
143 return &(new iprt::MiniString("Invalid main module string: "))->append(pszMainModule);
144
145 /*
146 * The VRDE module, optional.
147 * Accept both none and empty as tokens of no VRDE module.
148 */
149 const char *pszVrdeModule = NULL;
150 const xml::ElementNode *pVrdeModuleElm = pVBoxExtPackElm->findChildElement("VRDEModule");
151 if (pVrdeModuleElm)
152 {
153 pszVrdeModule = pVrdeModuleElm->getValue();
154 if (!pszVrdeModule || *pszVrdeModule == '\0')
155 pszVrdeModule = NULL;
156 else if (!VBoxExtPackIsValidModuleString(pszVrdeModule))
157 return &(new iprt::MiniString("Invalid VRDE module string: "))->append(pszVrdeModule);
158 }
159
160 /*
161 * Whether to show the license, optional. (presense is enough here)
162 */
163 const xml::ElementNode *pShowLicenseElm = pVBoxExtPackElm->findChildElement("ShowLicense");
164 bool fShowLicense = pShowLicenseElm != NULL;
165
166 /*
167 * Parse plug-in descriptions (last because of the manual memory management).
168 */
169 uint32_t cPlugIns = 0;
170 PVBOXEXTPACKPLUGINDESC paPlugIns = NULL;
171 iprt::MiniString *pstrRet = vboxExtPackLoadPlugInDescs(pVBoxExtPackElm, &cPlugIns, &paPlugIns);
172 if (pstrRet)
173 {
174 RTMemFree(paPlugIns);
175 return pstrRet;
176 }
177
178 /*
179 * Everything seems fine, fill in the return values and return successfully.
180 */
181 a_pExtPackDesc->strName = pszName;
182 a_pExtPackDesc->strDescription = pszDesc;
183 a_pExtPackDesc->strVersion = pszVersion;
184 a_pExtPackDesc->uRevision = uRevision;
185 a_pExtPackDesc->strMainModule = pszMainModule;
186 a_pExtPackDesc->strVrdeModule = pszVrdeModule;
187 a_pExtPackDesc->cPlugIns = cPlugIns;
188 a_pExtPackDesc->paPlugIns = paPlugIns;
189 a_pExtPackDesc->fShowLicense = fShowLicense;
190
191 return NULL;
192}
193
194/**
195 * Reads the extension pack descriptor.
196 *
197 * @returns NULL on success, pointer to an error message on failure (caller
198 * deletes it).
199 * @param a_pszDir The directory containing the description file.
200 * @param a_pExtPackDesc Where to store the extension pack descriptor.
201 * @param a_pObjInfo Where to store the object info for the file (unix
202 * attribs). Optional.
203 */
204iprt::MiniString *VBoxExtPackLoadDesc(const char *a_pszDir, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
205{
206 vboxExtPackClearDesc(a_pExtPackDesc);
207
208 /*
209 * Validate, open and parse the XML file.
210 */
211 char szFilePath[RTPATH_MAX];
212 int vrc = RTPathJoin(szFilePath, sizeof(szFilePath), a_pszDir, VBOX_EXTPACK_DESCRIPTION_NAME);
213 if (RT_FAILURE(vrc))
214 return new iprt::MiniString("RTPathJoin failed with %Rrc", vrc);
215
216 RTFSOBJINFO ObjInfo;
217 vrc = RTPathQueryInfoEx(szFilePath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK);
218 if (RT_FAILURE(vrc))
219 return &(new iprt::MiniString())->printf("RTPathQueryInfoEx failed with %Rrc", vrc);
220 if (a_pObjInfo)
221 *a_pObjInfo = ObjInfo;
222 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
223 {
224 if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode))
225 return new iprt::MiniString("The XML file is symlinked, that is not allowed");
226 return &(new iprt::MiniString)->printf("The XML file is not a file (fMode=%#x)", ObjInfo.Attr.fMode);
227 }
228
229 xml::Document Doc;
230 {
231 xml::XmlFileParser Parser;
232 try
233 {
234 Parser.read(szFilePath, Doc);
235 }
236 catch (xml::XmlError Err)
237 {
238 return new iprt::MiniString(Err.what());
239 }
240 }
241
242 /*
243 * Hand the xml doc over to the common code.
244 */
245 return vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
246}
247
248/**
249 * Reads the extension pack descriptor.
250 *
251 * @returns NULL on success, pointer to an error message on failure (caller
252 * deletes it).
253 * @param a_pszDir The directory containing the description file.
254 * @param a_pExtPackDesc Where to store the extension pack descriptor.
255 * @param a_pObjInfo Where to store the object info for the file (unix
256 * attribs). Optional.
257 */
258iprt::MiniString *VBoxExtPackLoadDescFromVfsFile(RTVFSFILE hVfsFile, PVBOXEXTPACKDESC a_pExtPackDesc, PRTFSOBJINFO a_pObjInfo)
259{
260 vboxExtPackClearDesc(a_pExtPackDesc);
261
262 /*
263 * Query the object info.
264 */
265 RTFSOBJINFO ObjInfo;
266 int rc = RTVfsFileQueryInfo(hVfsFile, &ObjInfo, RTFSOBJATTRADD_UNIX);
267 if (RT_FAILURE(rc))
268 return &(new iprt::MiniString)->printf("RTVfsFileQueryInfo failed: %Rrc", rc);
269 if (a_pObjInfo)
270 *a_pObjInfo = ObjInfo;
271
272 /*
273 * The simple approach, read the whole thing into memory and pass this to
274 * the XML parser.
275 */
276
277 /* Check the file size. */
278 if (ObjInfo.cbObject > _1M || ObjInfo.cbObject < 0)
279 return &(new iprt::MiniString)->printf("The XML file is too large (%'RU64 bytes)", ObjInfo.cbObject);
280 size_t const cbFile = (size_t)ObjInfo.cbObject;
281
282 /* Rewind to the start of the file. */
283 rc = RTVfsFileSeek(hVfsFile, 0, RTFILE_SEEK_BEGIN, NULL);
284 if (RT_FAILURE(rc))
285 return &(new iprt::MiniString)->printf("RTVfsFileSeek(,0,BEGIN) failed: %Rrc", rc);
286
287 /* Allocate memory and read the file content into it. */
288 void *pvFile = RTMemTmpAlloc(cbFile);
289 if (!pvFile)
290 return &(new iprt::MiniString)->printf("RTMemTmpAlloc(%zu) failed", cbFile);
291
292 iprt::MiniString *pstrErr = NULL;
293 rc = RTVfsFileRead(hVfsFile, pvFile, cbFile, NULL);
294 if (RT_FAILURE(rc))
295 pstrErr = &(new iprt::MiniString)->printf("RTVfsFileRead failed: %Rrc", rc);
296
297 /*
298 * Parse the file.
299 */
300 xml::Document Doc;
301 if (RT_SUCCESS(rc))
302 {
303 xml::XmlMemParser Parser;
304 iprt::MiniString strFileName = VBOX_EXTPACK_DESCRIPTION_NAME;
305 try
306 {
307 Parser.read(pvFile, cbFile, strFileName, Doc);
308 }
309 catch (xml::XmlError Err)
310 {
311 pstrErr = new iprt::MiniString(Err.what());
312 rc = VERR_PARSE_ERROR;
313 }
314 }
315 RTMemTmpFree(pvFile);
316
317 /*
318 * Hand the xml doc over to the common code.
319 */
320 if (RT_SUCCESS(rc))
321 pstrErr = vboxExtPackLoadDescFromDoc(&Doc, a_pExtPackDesc);
322
323 return pstrErr;
324}
325
326/**
327 * Frees all resources associated with a extension pack descriptor.
328 *
329 * @param a_pExtPackDesc The extension pack descriptor which members
330 * should be freed.
331 */
332void VBoxExtPackFreeDesc(PVBOXEXTPACKDESC a_pExtPackDesc)
333{
334 if (!a_pExtPackDesc)
335 return;
336
337 a_pExtPackDesc->strName.setNull();
338 a_pExtPackDesc->strDescription.setNull();
339 a_pExtPackDesc->strVersion.setNull();
340 a_pExtPackDesc->uRevision = 0;
341 a_pExtPackDesc->strMainModule.setNull();
342 a_pExtPackDesc->strVrdeModule.setNull();
343 a_pExtPackDesc->cPlugIns = 0;
344 RTMemFree(a_pExtPackDesc->paPlugIns);
345 a_pExtPackDesc->paPlugIns = NULL;
346 a_pExtPackDesc->fShowLicense = false;
347}
348
349/**
350 * Extract the extension pack name from the tarball path.
351 *
352 * @returns String containing the name on success, the caller must delete it.
353 * NULL if no valid name was found or if we ran out of memory.
354 * @param pszTarball The path to the tarball.
355 */
356iprt::MiniString *VBoxExtPackExtractNameFromTarballPath(const char *pszTarball)
357{
358 /*
359 * Skip ahead to the filename part and count the number of characters
360 * that matches the criteria for a mangled extension pack name.
361 */
362 const char *pszSrc = RTPathFilename(pszTarball);
363 if (!pszSrc)
364 return NULL;
365
366 size_t off = 0;
367 while (RT_C_IS_ALNUM(pszSrc[off]) || pszSrc[off] == '_')
368 off++;
369
370 /*
371 * Check min and max name limits.
372 */
373 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
374 || off < VBOX_EXTPACK_NAME_MIN_LEN)
375 return NULL;
376
377 /*
378 * Return the unmangled name.
379 */
380 return VBoxExtPackUnmangleName(pszSrc, off);
381}
382
383/**
384 * Validates the extension pack name.
385 *
386 * @returns true if valid, false if not.
387 * @param pszName The name to validate.
388 * @sa VBoxExtPackExtractNameFromTarballPath
389 */
390bool VBoxExtPackIsValidName(const char *pszName)
391{
392 if (!pszName)
393 return false;
394
395 /*
396 * Check the characters making up the name, only english alphabet
397 * characters, decimal digits and spaces are allowed.
398 */
399 size_t off = 0;
400 while (pszName[off])
401 {
402 if (!RT_C_IS_ALNUM(pszName[off]) && pszName[off] != ' ')
403 return false;
404 off++;
405 }
406
407 /*
408 * Check min and max name limits.
409 */
410 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
411 || off < VBOX_EXTPACK_NAME_MIN_LEN)
412 return false;
413
414 return true;
415}
416
417/**
418 * Checks if an alledged manged extension pack name.
419 *
420 * @returns true if valid, false if not.
421 * @param pszMangledName The mangled name to validate.
422 * @param cchMax The max number of chars to test.
423 * @sa VBoxExtPackMangleName
424 */
425bool VBoxExtPackIsValidMangledName(const char *pszMangledName, size_t cchMax /*= RTSTR_MAX*/)
426{
427 if (!pszMangledName)
428 return false;
429
430 /*
431 * Check the characters making up the name, only english alphabet
432 * characters, decimal digits and underscores (=space) are allowed.
433 */
434 size_t off = 0;
435 while (off < cchMax && pszMangledName[off])
436 {
437 if (!RT_C_IS_ALNUM(pszMangledName[off]) && pszMangledName[off] != '_')
438 return false;
439 off++;
440 }
441
442 /*
443 * Check min and max name limits.
444 */
445 if ( off > VBOX_EXTPACK_NAME_MAX_LEN
446 || off < VBOX_EXTPACK_NAME_MIN_LEN)
447 return false;
448
449 return true;
450}
451
452/**
453 * Mangle an extension pack name so it can be used by a directory or file name.
454 *
455 * @returns String containing the mangled name on success, the caller must
456 * delete it. NULL on failure.
457 * @param pszName The unmangled name.
458 * @sa VBoxExtPackUnmangleName, VBoxExtPackIsValidMangledName
459 */
460iprt::MiniString *VBoxExtPackMangleName(const char *pszName)
461{
462 AssertReturn(VBoxExtPackIsValidName(pszName), NULL);
463
464 char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
465 size_t off = 0;
466 char ch;
467 while ((ch = pszName[off]) != '\0')
468 {
469 if (ch == ' ')
470 ch = '_';
471 szTmp[off++] = ch;
472 }
473 szTmp[off] = '\0';
474 Assert(VBoxExtPackIsValidMangledName(szTmp));
475
476 return new iprt::MiniString(szTmp, off);
477}
478
479/**
480 * Unmangle an extension pack name (reverses VBoxExtPackMangleName).
481 *
482 * @returns String containing the mangled name on success, the caller must
483 * delete it. NULL on failure.
484 * @param pszMangledName The mangled name.
485 * @param cchMax The max name length. RTSTR_MAX is fine.
486 * @sa VBoxExtPackMangleName, VBoxExtPackIsValidMangledName
487 */
488iprt::MiniString *VBoxExtPackUnmangleName(const char *pszMangledName, size_t cchMax)
489{
490 AssertReturn(VBoxExtPackIsValidMangledName(pszMangledName, cchMax), NULL);
491
492 char szTmp[VBOX_EXTPACK_NAME_MAX_LEN + 1];
493 size_t off = 0;
494 char ch;
495 while ( off < cchMax
496 && (ch = pszMangledName[off]) != '\0')
497 {
498 if (ch == '_')
499 ch = ' ';
500 else
501 AssertReturn(RT_C_IS_ALNUM(ch) || ch == ' ', NULL);
502 szTmp[off++] = ch;
503 }
504 szTmp[off] = '\0';
505 AssertReturn(VBoxExtPackIsValidName(szTmp), NULL);
506
507 return new iprt::MiniString(szTmp, off);
508}
509
510/**
511 * Constructs the extension pack directory path.
512 *
513 * A combination of RTPathJoin and VBoxExtPackMangleName.
514 *
515 * @returns IPRT status code like RTPathJoin.
516 * @param pszExtPackDir Where to return the directory path.
517 * @param cbExtPackDir The size of the return buffer.
518 * @param pszParentDir The parent directory (".../Extensions").
519 * @param pszName The extension pack name, unmangled.
520 */
521int VBoxExtPackCalcDir(char *pszExtPackDir, size_t cbExtPackDir, const char *pszParentDir, const char *pszName)
522{
523 AssertReturn(VBoxExtPackIsValidName(pszName), VERR_INTERNAL_ERROR_5);
524
525 iprt::MiniString *pstrMangledName = VBoxExtPackMangleName(pszName);
526 if (!pstrMangledName)
527 return VERR_INTERNAL_ERROR_4;
528
529 int vrc = RTPathJoin(pszExtPackDir, cbExtPackDir, pszParentDir, pstrMangledName->c_str());
530 delete pstrMangledName;
531
532 return vrc;
533}
534
535
536/**
537 * Validates the extension pack version string.
538 *
539 * @returns true if valid, false if not.
540 * @param pszVersion The version string to validate.
541 */
542bool VBoxExtPackIsValidVersionString(const char *pszVersion)
543{
544 if (!pszVersion || *pszVersion == '\0')
545 return false;
546
547 /* 1.x.y.z... */
548 for (;;)
549 {
550 if (!RT_C_IS_DIGIT(*pszVersion))
551 return false;
552 do
553 pszVersion++;
554 while (RT_C_IS_DIGIT(*pszVersion));
555 if (*pszVersion != '.')
556 break;
557 pszVersion++;
558 }
559
560 /* upper case string + numbers indicating the build type */
561 if (*pszVersion == '-' || *pszVersion == '_')
562 {
563 do
564 pszVersion++;
565 while ( RT_C_IS_DIGIT(*pszVersion)
566 || RT_C_IS_UPPER(*pszVersion)
567 || *pszVersion == '-'
568 || *pszVersion == '_');
569 }
570
571 /* revision or nothing */
572 if (*pszVersion != '\0')
573 {
574 if (*pszVersion != 'r')
575 return false;
576 do
577 pszVersion++;
578 while (RT_C_IS_DIGIT(*pszVersion));
579 }
580
581 return *pszVersion == '\0';
582}
583
584/**
585 * Validates an extension pack module string.
586 *
587 * @returns true if valid, false if not.
588 * @param pszModule The module string to validate.
589 */
590bool VBoxExtPackIsValidModuleString(const char *pszModule)
591{
592 if (!pszModule || *pszModule == '\0')
593 return false;
594
595 /* Restricted charset, no extensions (dots). */
596 while ( RT_C_IS_ALNUM(*pszModule)
597 || *pszModule == '-'
598 || *pszModule == '_')
599 pszModule++;
600
601 return *pszModule == '\0';
602}
603
604/**
605 * RTStrPrintfv wrapper.
606 *
607 * @returns @a rc
608 * @param rc The status code to return.
609 * @param pszError The error buffer.
610 * @param cbError The size of the buffer.
611 * @param pszFormat The error message format string.
612 * @param ... Format arguments.
613 */
614static int vboxExtPackReturnError(int rc, char *pszError, size_t cbError, const char *pszFormat, ...)
615{
616 va_list va;
617 va_start(va, pszFormat);
618 RTStrPrintfV(pszError, cbError, pszFormat, va);
619 va_end(va);
620 return rc;
621}
622
623/**
624 * RTStrPrintfv wrapper.
625 *
626 * @param pszError The error buffer.
627 * @param cbError The size of the buffer.
628 * @param pszFormat The error message format string.
629 * @param ... Format arguments.
630 */
631static void vboxExtPackSetError(char *pszError, size_t cbError, const char *pszFormat, ...)
632{
633 va_list va;
634 va_start(va, pszFormat);
635 RTStrPrintfV(pszError, cbError, pszFormat, va);
636 va_end(va);
637}
638
639/**
640 * Verifies the manifest and its signature.
641 *
642 * @returns VBox status code, failures with message.
643 * @param hManifestFile The xml from the extension pack.
644 * @param pszExtPackName The expected extension pack name. This can be
645 * NULL, in which we don't have any expectations.
646 * @param pszError Where to store an error message on failure.
647 * @param cbError The size of the buffer @a pszError points to.
648 */
649static int vboxExtPackVerifyXml(RTVFSFILE hXmlFile, const char *pszExtPackName, char *pszError, size_t cbError)
650{
651 /*
652 * Load the XML.
653 */
654 VBOXEXTPACKDESC ExtPackDesc;
655 iprt::MiniString *pstrErr = VBoxExtPackLoadDescFromVfsFile(hXmlFile, &ExtPackDesc, NULL);
656 if (pstrErr)
657 {
658 RTStrCopy(pszError, cbError, pstrErr->c_str());
659 delete pstrErr;
660 return VERR_PARSE_ERROR;
661 }
662
663 /*
664 * Check the name.
665 */
666 /** @todo drop this restriction after the old install interface is
667 * dropped. */
668 int rc = VINF_SUCCESS;
669 if ( pszExtPackName
670 && !ExtPackDesc.strName.equalsIgnoreCase(pszExtPackName))
671 rc = vboxExtPackReturnError(VERR_NOT_EQUAL, pszError, cbError,
672 "The name of the downloaded file and the name stored inside the extension pack does not match"
673 " (xml='%s' file='%s')", ExtPackDesc.strName.c_str(), pszExtPackName);
674 return rc;
675}
676
677/**
678 * Verifies the manifest and its signature.
679 *
680 * @returns VBox status code, failures with message.
681 * @param hOurManifest The manifest we compiled.
682 * @param hManifestFile The manifest file in the extension pack.
683 * @param hSignatureFile The manifest signature file.
684 * @param pszError Where to store an error message on failure.
685 * @param cbError The size of the buffer @a pszError points to.
686 */
687static int vboxExtPackVerifyManifestAndSignature(RTMANIFEST hOurManifest, RTVFSFILE hManifestFile, RTVFSFILE hSignatureFile,
688 char *pszError, size_t cbError)
689{
690 /*
691 * Read the manifest from the extension pack.
692 */
693 int rc = RTVfsFileSeek(hManifestFile, 0, RTFILE_SEEK_BEGIN, NULL);
694 if (RT_FAILURE(rc))
695 return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsFileSeek failed: %Rrc", rc);
696
697 RTMANIFEST hTheirManifest;
698 rc = RTManifestCreate(0 /*fFlags*/, &hTheirManifest);
699 if (RT_FAILURE(rc))
700 return vboxExtPackReturnError(rc, pszError, cbError, "RTManifestCreate failed: %Rrc", rc);
701
702 RTVFSIOSTREAM hVfsIos = RTVfsFileToIoStream(hManifestFile);
703 rc = RTManifestReadStandard(hTheirManifest, hVfsIos);
704 RTVfsIoStrmRelease(hVfsIos);
705 if (RT_SUCCESS(rc))
706 {
707 /*
708 * Compare the manifests.
709 */
710 static const char *s_apszIgnoreEntries[] =
711 {
712 VBOX_EXTPACK_MANIFEST_NAME,
713 VBOX_EXTPACK_SIGNATURE_NAME,
714 "./" VBOX_EXTPACK_MANIFEST_NAME,
715 "./" VBOX_EXTPACK_SIGNATURE_NAME,
716 NULL
717 };
718 char szError[RTPATH_MAX];
719 rc = RTManifestEqualsEx(hOurManifest, hTheirManifest, &s_apszIgnoreEntries[0], NULL,
720 RTMANIFEST_EQUALS_IGN_MISSING_ATTRS /*fFlags*/,
721 szError, sizeof(szError));
722 if (RT_SUCCESS(rc))
723 {
724 /*
725 * Validate the manifest file signature.
726 */
727 /** @todo implement signature stuff */
728 NOREF(hSignatureFile);
729
730 }
731 else if (rc == VERR_NOT_EQUAL && szError[0])
732 vboxExtPackSetError(pszError, cbError, "Manifest mismatch: %s", szError);
733 else
734 vboxExtPackSetError(pszError, cbError, "RTManifestEqualsEx failed: %Rrc", rc);
735#if 0
736 RTVFSIOSTREAM hVfsIosStdOut = NIL_RTVFSIOSTREAM;
737 RTVfsIoStrmFromStdHandle(RTHANDLESTD_OUTPUT, RTFILE_O_WRITE, true, &hVfsIosStdOut);
738 RTVfsIoStrmWrite(hVfsIosStdOut, "Our:\n", sizeof("Our:\n") - 1, true, NULL);
739 RTManifestWriteStandard(hOurManifest, hVfsIosStdOut);
740 RTVfsIoStrmWrite(hVfsIosStdOut, "Their:\n", sizeof("Their:\n") - 1, true, NULL);
741 RTManifestWriteStandard(hTheirManifest, hVfsIosStdOut);
742#endif
743 }
744 else
745 vboxExtPackSetError(pszError, cbError, "Error parsing '%s': %Rrc", VBOX_EXTPACK_MANIFEST_NAME, rc);
746
747 RTManifestRelease(hTheirManifest);
748 return rc;
749}
750
751
752/**
753 * Validates a name in an extension pack.
754 *
755 * We restrict the charset to try make sure the extension pack can be unpacked
756 * on all file systems.
757 *
758 * @returns VBox status code, failures with message.
759 * @param pszName The name to validate.
760 * @param pszError Where to store an error message on failure.
761 * @param cbError The size of the buffer @a pszError points to.
762 */
763static int vboxExtPackValidateMemberName(const char *pszName, char *pszError, size_t cbError)
764{
765 if (RTPathStartsWithRoot(pszName))
766 return vboxExtPackReturnError(VERR_PATH_IS_NOT_RELATIVE, pszError, cbError, "'%s': starts with root spec", pszName);
767
768 const char *pszErr = NULL;
769 const char *psz = pszName;
770 int ch;
771 while ((ch = *psz) != '\0')
772 {
773 /* Character set restrictions. */
774 if (ch < 0 || ch >= 128)
775 {
776 pszErr = "Only 7-bit ASCII allowed";
777 break;
778 }
779 if (ch <= 31 || ch == 127)
780 {
781 pszErr = "No control characters are not allowed";
782 break;
783 }
784 if (ch == '\\')
785 {
786 pszErr = "Only backward slashes are not allowed";
787 break;
788 }
789 if (strchr("'\":;*?|[]<>(){}", ch))
790 {
791 pszErr = "The characters ', \", :, ;, *, ?, |, [, ], <, >, (, ), { and } are not allowed";
792 break;
793 }
794
795 /* Take the simple way out and ban all ".." sequences. */
796 if ( ch == '.'
797 && psz[1] == '.')
798 {
799 pszErr = "Double dot sequence are not allowed";
800 break;
801 }
802
803 /* Keep the tree shallow or the hardening checks will fail. */
804 if (psz - pszName > VBOX_EXTPACK_MAX_MEMBER_NAME_LENGTH)
805 {
806 pszErr = "Too long";
807 break;
808 }
809
810 /* advance */
811 psz++;
812 }
813
814 if (pszErr)
815 return vboxExtPackReturnError(VERR_INVALID_NAME, pszError, cbError,
816 "Bad member name '%s' (pos %zu): %s", pszName, (size_t)(psz - pszName), pszErr);
817 return RTEXITCODE_SUCCESS;
818}
819
820
821/**
822 * Validates a file in an extension pack.
823 *
824 * @returns VBox status code, failures with message.
825 * @param pszName The name of the file.
826 * @param hVfsObj The VFS object.
827 * @param pszError Where to store an error message on failure.
828 * @param cbError The size of the buffer @a pszError points to.
829 */
830static int vboxExtPackValidateMemberFile(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
831{
832 int rc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
833 if (RT_SUCCESS(rc))
834 {
835 RTFSOBJINFO ObjInfo;
836 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
837 if (RT_SUCCESS(rc))
838 {
839 if (ObjInfo.cbObject >= 9*_1G64)
840 rc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
841 "'%s': too large (%'RU64 bytes)",
842 pszName, (uint64_t)ObjInfo.cbObject);
843 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
844 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
845 "The alleged file '%s' has a mode mask stating otherwise (%RTfmode)",
846 pszName, ObjInfo.Attr.fMode);
847 }
848 else
849 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
850 }
851 return rc;
852}
853
854
855/**
856 * Validates a directory in an extension pack.
857 *
858 * @returns VBox status code, failures with message.
859 * @param pszName The name of the directory.
860 * @param hVfsObj The VFS object.
861 * @param pszError Where to store an error message on failure.
862 * @param cbError The size of the buffer @a pszError points to.
863 */
864static int vboxExtPackValidateMemberDir(const char *pszName, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
865{
866 int rc = vboxExtPackValidateMemberName(pszName, pszError, cbError);
867 if (RT_SUCCESS(rc))
868 {
869 RTFSOBJINFO ObjInfo;
870 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
871 if (RT_SUCCESS(rc))
872 {
873 if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode))
874 rc = vboxExtPackReturnError(VERR_NOT_A_DIRECTORY, pszError, cbError,
875 "The alleged directory '%s' has a mode mask saying differently (%RTfmode)",
876 pszName, ObjInfo.Attr.fMode);
877 }
878 else
879 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
880 }
881 return rc;
882}
883
884/**
885 * Validates a member of an extension pack.
886 *
887 * @returns VBox status code, failures with message.
888 * @param pszName The name of the directory.
889 * @param enmType The object type.
890 * @param hVfsObj The VFS object.
891 * @param pszError Where to store an error message on failure.
892 * @param cbError The size of the buffer @a pszError points to.
893 */
894int VBoxExtPackValidateMember(const char *pszName, RTVFSOBJTYPE enmType, RTVFSOBJ hVfsObj, char *pszError, size_t cbError)
895{
896 Assert(cbError > 0);
897 *pszError = '\0';
898
899 int rc;
900 if ( enmType == RTVFSOBJTYPE_FILE
901 || enmType == RTVFSOBJTYPE_IO_STREAM)
902 rc = vboxExtPackValidateMemberFile(pszName, hVfsObj, pszError, cbError);
903 else if ( enmType == RTVFSOBJTYPE_DIR
904 || enmType == RTVFSOBJTYPE_BASE)
905 rc = vboxExtPackValidateMemberDir(pszName, hVfsObj, pszError, cbError);
906 else
907 rc = vboxExtPackReturnError(VERR_UNEXPECTED_FS_OBJ_TYPE, pszError, cbError,
908 "'%s' is not a file or directory (enmType=%d)", pszName, enmType);
909 return rc;
910}
911
912
913/**
914 * Rewinds the tarball file handle and creates a gunzip | tar chain that
915 * results in a filesystem stream.
916 *
917 * @returns VBox status code, failures with message.
918 * @param hTarballFile The handle to the tarball file.
919 * @param pszError Where to store an error message on failure.
920 * @param cbError The size of the buffer @a pszError points to.
921 * @param phTarFss Where to return the filesystem stream handle.
922 */
923int VBoxExtPackOpenTarFss(RTFILE hTarballFile, char *pszError, size_t cbError, PRTVFSFSSTREAM phTarFss)
924{
925 Assert(cbError > 0);
926 *pszError = '\0';
927 *phTarFss = NIL_RTVFSFSSTREAM;
928
929 /*
930 * Rewind the file and set up a VFS chain for it.
931 */
932 int rc = RTFileSeek(hTarballFile, 0, RTFILE_SEEK_BEGIN, NULL);
933 if (RT_FAILURE(rc))
934 return vboxExtPackReturnError(rc, pszError, cbError, "Failed seeking to the start of the tarball: %Rrc", rc);
935
936 RTVFSIOSTREAM hTarballIos;
937 rc = RTVfsIoStrmFromRTFile(hTarballFile, RTFILE_O_READ | RTFILE_O_DENY_WRITE | RTFILE_O_OPEN, true /*fLeaveOpen*/,
938 &hTarballIos);
939 if (RT_FAILURE(rc))
940 return vboxExtPackReturnError(rc, pszError, cbError, "RTVfsIoStrmFromRTFile failed: %Rrc", rc);
941
942 RTVFSIOSTREAM hGunzipIos;
943 rc = RTZipGzipDecompressIoStream(hTarballIos, 0 /*fFlags*/, &hGunzipIos);
944 if (RT_SUCCESS(rc))
945 {
946 RTVFSFSSTREAM hTarFss;
947 rc = RTZipTarFsStreamFromIoStream(hGunzipIos, 0 /*fFlags*/, &hTarFss);
948 if (RT_SUCCESS(rc))
949 {
950 RTVfsIoStrmRelease(hGunzipIos);
951 RTVfsIoStrmRelease(hTarballIos);
952 *phTarFss = hTarFss;
953 return VINF_SUCCESS;
954 }
955 vboxExtPackSetError(pszError, cbError, "RTZipTarFsStreamFromIoStream failed: %Rrc", rc);
956 RTVfsIoStrmRelease(hGunzipIos);
957 }
958 else
959 vboxExtPackSetError(pszError, cbError, "RTZipGzipDecompressIoStream failed: %Rrc", rc);
960 RTVfsIoStrmRelease(hTarballIos);
961 return rc;
962}
963
964
965/**
966 * Validates the extension pack tarball prior to unpacking.
967 *
968 * Operations performed:
969 * - Mandatory files.
970 * - Manifest check.
971 * - Manifest seal check.
972 * - XML check, match name.
973 *
974 * @returns VBox status code, failures with message.
975 * @param hTarballFile The handle to open the @a pszTarball file.
976 * @param pszExtPackName The name of the extension pack name. NULL if
977 * the name is not fixed.
978 * @param pszTarball The name of the tarball in case we have to
979 * complain about something.
980 * @param pszError Where to store an error message on failure.
981 * @param cbError The size of the buffer @a pszError points to.
982 * @param phValidManifest Where to optionally return the handle to fully
983 * validated the manifest for the extension pack.
984 * This includes all files.
985 * @param phXmlFile Where to optionally return the memorized XML
986 * file.
987 *
988 * @todo This function is a bit too long and should be split up if possible.
989 */
990int VBoxExtPackValidateTarball(RTFILE hTarballFile, const char *pszExtPackName, const char *pszTarball,
991 char *pszError, size_t cbError, PRTMANIFEST phValidManifest, PRTVFSFILE phXmlFile)
992{
993 /*
994 * Clear return values.
995 */
996 if (phValidManifest)
997 *phValidManifest = NIL_RTMANIFEST;
998 if (phXmlFile)
999 *phXmlFile = NIL_RTVFSFILE;
1000 Assert(cbError > 1);
1001 *pszError = '\0';
1002 NOREF(pszTarball);
1003
1004 /*
1005 * Open the tar.gz filesystem stream and set up an manifest in-memory file.
1006 */
1007 RTVFSFSSTREAM hTarFss;
1008 int rc = VBoxExtPackOpenTarFss(hTarballFile, pszError, cbError, &hTarFss);
1009 if (RT_FAILURE(rc))
1010 return rc;
1011
1012 RTMANIFEST hOurManifest;
1013 rc = RTManifestCreate(0 /*fFlags*/, &hOurManifest);
1014 if (RT_SUCCESS(rc))
1015 {
1016 /*
1017 * Process the tarball (would be nice to move this to a function).
1018 */
1019 RTVFSFILE hXmlFile = NIL_RTVFSFILE;
1020 RTVFSFILE hManifestFile = NIL_RTVFSFILE;
1021 RTVFSFILE hSignatureFile= NIL_RTVFSFILE;
1022 for (;;)
1023 {
1024 /*
1025 * Get the next stream object.
1026 */
1027 char *pszName;
1028 RTVFSOBJ hVfsObj;
1029 RTVFSOBJTYPE enmType;
1030 rc = RTVfsFsStrmNext(hTarFss, &pszName, &enmType, &hVfsObj);
1031 if (RT_FAILURE(rc))
1032 {
1033 if (rc != VERR_EOF)
1034 vboxExtPackSetError(pszError, cbError, "RTVfsFsStrmNext failed: %Rrc", rc);
1035 else
1036 rc = VINF_SUCCESS;
1037 break;
1038 }
1039 const char *pszAdjName = pszName[0] == '.' && pszName[1] == '/' ? &pszName[2] : pszName;
1040
1041 /*
1042 * Check the type & name validity.
1043 *
1044 * N.B. We will always reach the end of the loop before breaking on
1045 * failure - cleanup reasons.
1046 */
1047 rc = VBoxExtPackValidateMember(pszName, enmType, hVfsObj, pszError, cbError);
1048 if (RT_SUCCESS(rc))
1049 {
1050 /*
1051 * Check if this is one of the standard files.
1052 */
1053 PRTVFSFILE phVfsFile;
1054 if (!strcmp(pszAdjName, VBOX_EXTPACK_DESCRIPTION_NAME))
1055 phVfsFile = &hXmlFile;
1056 else if (!strcmp(pszAdjName, VBOX_EXTPACK_MANIFEST_NAME))
1057 phVfsFile = &hManifestFile;
1058 else if (!strcmp(pszAdjName, VBOX_EXTPACK_SIGNATURE_NAME))
1059 phVfsFile = &hSignatureFile;
1060 else
1061 phVfsFile = NULL;
1062 if (phVfsFile)
1063 {
1064 /*
1065 * Make sure it's a file and that it isn't too large.
1066 */
1067 if (*phVfsFile != NIL_RTVFSFILE)
1068 rc = vboxExtPackReturnError(VERR_DUPLICATE, pszError, cbError,
1069 "There can only be one '%s'", pszAdjName);
1070 else if (enmType != RTVFSOBJTYPE_IO_STREAM && enmType != RTVFSOBJTYPE_FILE)
1071 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
1072 "Standard member '%s' is not a file", pszAdjName);
1073 else
1074 {
1075 RTFSOBJINFO ObjInfo;
1076 rc = RTVfsObjQueryInfo(hVfsObj, &ObjInfo, RTFSOBJATTRADD_NOTHING);
1077 if (RT_SUCCESS(rc))
1078 {
1079 if (!RTFS_IS_FILE(ObjInfo.Attr.fMode))
1080 rc = vboxExtPackReturnError(VERR_NOT_A_FILE, pszError, cbError,
1081 "Standard member '%s' is not a file", pszAdjName);
1082 else if (ObjInfo.cbObject >= _1M)
1083 rc = vboxExtPackReturnError(VERR_OUT_OF_RANGE, pszError, cbError,
1084 "Standard member '%s' is too large: %'RU64 bytes (max 1 MB)",
1085 pszAdjName, (uint64_t)ObjInfo.cbObject);
1086 else
1087 {
1088 /*
1089 * Make an in memory copy of the stream.
1090 */
1091 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1092 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsFile);
1093 if (RT_SUCCESS(rc))
1094 {
1095 /*
1096 * To simplify the code below, replace
1097 * hVfsObj with the memorized file.
1098 */
1099 RTVfsObjRelease(hVfsObj);
1100 hVfsObj = RTVfsObjFromFile(*phVfsFile);
1101 }
1102 else
1103 vboxExtPackSetError(pszError, cbError, "RTVfsMemorizeIoStreamAsFile failed on '%s': %Rrc", pszName, rc);
1104 RTVfsIoStrmRelease(hVfsIos);
1105 }
1106 }
1107 else
1108 vboxExtPackSetError(pszError, cbError, "RTVfsObjQueryInfo failed on '%s': %Rrc", pszName, rc);
1109 }
1110 }
1111 }
1112
1113 /*
1114 * Add any I/O stream to the manifest
1115 */
1116 if ( RT_SUCCESS(rc)
1117 && ( enmType == RTVFSOBJTYPE_FILE
1118 || enmType == RTVFSOBJTYPE_IO_STREAM))
1119 {
1120 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
1121 rc = RTManifestEntryAddIoStream(hOurManifest, hVfsIos, pszAdjName, RTMANIFEST_ATTR_SIZE | RTMANIFEST_ATTR_SHA256);
1122 if (RT_FAILURE(rc))
1123 vboxExtPackSetError(pszError, cbError, "RTManifestEntryAddIoStream failed on '%s': %Rrc", pszAdjName, rc);
1124 RTVfsIoStrmRelease(hVfsIos);
1125 }
1126
1127 /*
1128 * Clean up and break out on failure.
1129 */
1130 RTVfsObjRelease(hVfsObj);
1131 RTStrFree(pszName);
1132 if (RT_FAILURE(rc))
1133 break;
1134 }
1135
1136 /*
1137 * If we've successfully processed the tarball, verify that the
1138 * mandatory files are present.
1139 */
1140 if (RT_SUCCESS(rc))
1141 {
1142 if (hXmlFile == NIL_RTVFSFILE)
1143 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_DESCRIPTION_NAME);
1144 if (hManifestFile == NIL_RTVFSFILE)
1145 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_MANIFEST_NAME);
1146 if (hSignatureFile == NIL_RTVFSFILE)
1147 rc = vboxExtPackReturnError(VERR_MISSING, pszError, cbError, "Mandator file '%s' is missing", VBOX_EXTPACK_SIGNATURE_NAME);
1148 }
1149
1150 /*
1151 * Check the manifest and it's signature.
1152 */
1153 if (RT_SUCCESS(rc))
1154 rc = vboxExtPackVerifyManifestAndSignature(hOurManifest, hManifestFile, hSignatureFile, pszError, cbError);
1155
1156 /*
1157 * Check the XML.
1158 */
1159 if (RT_SUCCESS(rc))
1160 rc = vboxExtPackVerifyXml(hXmlFile, pszExtPackName, pszError, cbError);
1161
1162 /*
1163 * Returns objects.
1164 */
1165 if (RT_SUCCESS(rc))
1166 {
1167 if (phValidManifest)
1168 {
1169 RTManifestRetain(hOurManifest);
1170 *phValidManifest = hOurManifest;
1171 }
1172 if (phXmlFile)
1173 {
1174 RTVfsFileRetain(hXmlFile);
1175 *phXmlFile = hXmlFile;
1176 }
1177 }
1178
1179 /*
1180 * Release our object references.
1181 */
1182 RTManifestRelease(hOurManifest);
1183 RTVfsFileRelease(hXmlFile);
1184 RTVfsFileRelease(hManifestFile);
1185 RTVfsFileRelease(hSignatureFile);
1186 }
1187 else
1188 vboxExtPackSetError(pszError, cbError, "RTManifestCreate failed: %Rrc", rc);
1189 RTVfsFsStrmRelease(hTarFss);
1190
1191 return rc;
1192}
1193
Note: See TracBrowser for help on using the repository browser.

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