VirtualBox

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

Last change on this file was 98322, checked in by vboxsync, 16 months ago

Runtime,Main: Remove the now unused and deprecated RTTar* API in favor of the VFS implementation, move the header declaring the tar headers to iprt/formats so the appliance code can access some required defines

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

© 2023 Oracle
ContactPrivacy policyTerms of Use