VirtualBox

source: vbox/trunk/src/VBox/Main/src-all/ExtPackUtil.cpp@ 72168

Last change on this file since 72168 was 68828, checked in by vboxsync, 7 years ago

ExtPack: Split up main module of extension pack, have a mandatory one for VBoxSVC and an optional one for the VM process. This finally eliminates the need to drag VBoxVMM into VBoxSVC on some platforms. Many other small cleanups, including reliably calling the unload hook from within a VM process, copy/paste with forgotten adjustments (e.g. extpacks still talking about skeleton) and spelling fixes.

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