VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 20 months ago

Copyright year updates by scm.

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