VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxManage/VBoxManageAppliance.cpp@ 96313

Last change on this file since 96313 was 95597, checked in by vboxsync, 2 years ago

IPRT/Pkcs7: Added a parameter to RTCrPkcs7SimpleSignSignedData (not yet implemented). [build fix] bugref:8691

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 142.1 KB
Line 
1/* $Id: VBoxManageAppliance.cpp 95597 2022-07-12 02:26:57Z vboxsync $ */
2/** @file
3 * VBoxManage - The appliance-related commands.
4 */
5
6/*
7 * Copyright (C) 2009-2022 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#include <VBox/com/com.h>
23#include <VBox/com/string.h>
24#include <VBox/com/Guid.h>
25#include <VBox/com/array.h>
26#include <VBox/com/ErrorInfo.h>
27#include <VBox/com/errorprint.h>
28#include <VBox/com/VirtualBox.h>
29#include <VBox/log.h>
30#include <VBox/param.h>
31
32#include <VBox/version.h>
33
34#include <list>
35#include <map>
36
37#include <iprt/getopt.h>
38#include <iprt/ctype.h>
39#include <iprt/path.h>
40#include <iprt/file.h>
41#include <iprt/err.h>
42#include <iprt/zip.h>
43#include <iprt/stream.h>
44#include <iprt/vfs.h>
45#include <iprt/manifest.h>
46#include <iprt/crypto/digest.h>
47#include <iprt/crypto/x509.h>
48#include <iprt/crypto/pkcs7.h>
49#include <iprt/crypto/store.h>
50#include <iprt/crypto/spc.h>
51#include <iprt/crypto/key.h>
52#include <iprt/crypto/pkix.h>
53
54
55
56#include "VBoxManage.h"
57using namespace com;
58
59DECLARE_TRANSLATION_CONTEXT(Appliance);
60
61
62// funcs
63///////////////////////////////////////////////////////////////////////////////
64
65typedef std::map<Utf8Str, Utf8Str> ArgsMap; // pairs of strings like "vmname" => "newvmname"
66typedef std::map<uint32_t, ArgsMap> ArgsMapsMap; // map of maps, one for each virtual system, sorted by index
67
68typedef std::map<uint32_t, bool> IgnoresMap; // pairs of numeric description entry indices
69typedef std::map<uint32_t, IgnoresMap> IgnoresMapsMap; // map of maps, one for each virtual system, sorted by index
70
71static bool findArgValue(Utf8Str &strOut,
72 ArgsMap *pmapArgs,
73 const Utf8Str &strKey)
74{
75 if (pmapArgs)
76 {
77 ArgsMap::iterator it;
78 it = pmapArgs->find(strKey);
79 if (it != pmapArgs->end())
80 {
81 strOut = it->second;
82 pmapArgs->erase(it);
83 return true;
84 }
85 }
86
87 return false;
88}
89
90static int parseImportOptions(const char *psz, com::SafeArray<ImportOptions_T> *options)
91{
92 int rc = VINF_SUCCESS;
93 while (psz && *psz && RT_SUCCESS(rc))
94 {
95 size_t len;
96 const char *pszComma = strchr(psz, ',');
97 if (pszComma)
98 len = pszComma - psz;
99 else
100 len = strlen(psz);
101 if (len > 0)
102 {
103 if (!RTStrNICmp(psz, "KeepAllMACs", len))
104 options->push_back(ImportOptions_KeepAllMACs);
105 else if (!RTStrNICmp(psz, "KeepNATMACs", len))
106 options->push_back(ImportOptions_KeepNATMACs);
107 else if (!RTStrNICmp(psz, "ImportToVDI", len))
108 options->push_back(ImportOptions_ImportToVDI);
109 else
110 rc = VERR_PARSE_ERROR;
111 }
112 if (pszComma)
113 psz += len + 1;
114 else
115 psz += len;
116 }
117
118 return rc;
119}
120
121/**
122 * Helper routine to parse the ExtraData Utf8Str for a storage controller's
123 * value or channel value.
124 *
125 * @param aExtraData The ExtraData string which can have a format of
126 * either 'controller=13;channel=3' or '11'.
127 * @param pszKey The string being looked up, usually either 'controller'
128 * or 'channel' but can be NULL or empty.
129 * @param puVal The integer value of the 'controller=' or 'channel='
130 * key (or the controller number when there is no key) in
131 * the ExtraData string.
132 * @returns COM status code.
133 */
134static int getStorageControllerDetailsFromStr(const com::Utf8Str &aExtraData, const char *pszKey, uint32_t *puVal)
135{
136 int vrc;
137
138 if (pszKey && *pszKey)
139 {
140 size_t posKey = aExtraData.find(pszKey);
141 if (posKey == Utf8Str::npos)
142 return VERR_INVALID_PARAMETER;
143 vrc = RTStrToUInt32Ex(aExtraData.c_str() + posKey + strlen(pszKey), NULL, 0, puVal);
144 }
145 else
146 {
147 vrc = RTStrToUInt32Ex(aExtraData.c_str(), NULL, 0, puVal);
148 }
149
150 if (vrc == VWRN_NUMBER_TOO_BIG || vrc == VWRN_NEGATIVE_UNSIGNED)
151 return VERR_INVALID_PARAMETER;
152
153 return vrc;
154}
155
156static bool isStorageControllerType(VirtualSystemDescriptionType_T avsdType)
157{
158 switch (avsdType)
159 {
160 case VirtualSystemDescriptionType_HardDiskControllerIDE:
161 case VirtualSystemDescriptionType_HardDiskControllerSATA:
162 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
163 case VirtualSystemDescriptionType_HardDiskControllerSAS:
164 case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
165 return true;
166 default:
167 return false;
168 }
169}
170
171static const RTGETOPTDEF g_aImportApplianceOptions[] =
172{
173 { "--dry-run", 'n', RTGETOPT_REQ_NOTHING },
174 { "-dry-run", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
175 { "--dryrun", 'n', RTGETOPT_REQ_NOTHING },
176 { "-dryrun", 'n', RTGETOPT_REQ_NOTHING }, // deprecated
177 { "--detailed-progress", 'P', RTGETOPT_REQ_NOTHING },
178 { "-detailed-progress", 'P', RTGETOPT_REQ_NOTHING }, // deprecated
179 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
180 { "-vsys", 's', RTGETOPT_REQ_UINT32 }, // deprecated
181 { "--ostype", 'o', RTGETOPT_REQ_STRING },
182 { "-ostype", 'o', RTGETOPT_REQ_STRING }, // deprecated
183 { "--vmname", 'V', RTGETOPT_REQ_STRING },
184 { "-vmname", 'V', RTGETOPT_REQ_STRING }, // deprecated
185 { "--settingsfile", 'S', RTGETOPT_REQ_STRING },
186 { "--basefolder", 'p', RTGETOPT_REQ_STRING },
187 { "--group", 'g', RTGETOPT_REQ_STRING },
188 { "--memory", 'm', RTGETOPT_REQ_STRING },
189 { "-memory", 'm', RTGETOPT_REQ_STRING }, // deprecated
190 { "--cpus", 'c', RTGETOPT_REQ_STRING },
191 { "--description", 'd', RTGETOPT_REQ_STRING },
192 { "--eula", 'L', RTGETOPT_REQ_STRING },
193 { "-eula", 'L', RTGETOPT_REQ_STRING }, // deprecated
194 { "--unit", 'u', RTGETOPT_REQ_UINT32 },
195 { "-unit", 'u', RTGETOPT_REQ_UINT32 }, // deprecated
196 { "--ignore", 'x', RTGETOPT_REQ_NOTHING },
197 { "-ignore", 'x', RTGETOPT_REQ_NOTHING }, // deprecated
198 { "--scsitype", 'T', RTGETOPT_REQ_UINT32 },
199 { "-scsitype", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
200 { "--type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
201 { "-type", 'T', RTGETOPT_REQ_UINT32 }, // deprecated
202 { "--controller", 'C', RTGETOPT_REQ_STRING },
203 { "--port", 'E', RTGETOPT_REQ_STRING },
204 { "--disk", 'D', RTGETOPT_REQ_STRING },
205 { "--options", 'O', RTGETOPT_REQ_STRING },
206
207 { "--cloud", 'j', RTGETOPT_REQ_NOTHING},
208 { "--cloudprofile", 'k', RTGETOPT_REQ_STRING },
209 { "--cloudinstanceid", 'l', RTGETOPT_REQ_STRING },
210 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING }
211};
212
213typedef enum APPLIANCETYPE
214{
215 NOT_SET, LOCAL, CLOUD
216} APPLIANCETYPE;
217
218RTEXITCODE handleImportAppliance(HandlerArg *arg)
219{
220 HRESULT hrc = S_OK;
221 APPLIANCETYPE enmApplType = NOT_SET;
222 Utf8Str strOvfFilename;
223 bool fExecute = true; // if true, then we actually do the import
224 com::SafeArray<ImportOptions_T> options;
225 uint32_t ulCurVsys = (uint32_t)-1;
226 uint32_t ulCurUnit = (uint32_t)-1;
227 // for each --vsys X command, maintain a map of command line items
228 // (we'll parse them later after interpreting the OVF, when we can
229 // actually check whether they make sense semantically)
230 ArgsMapsMap mapArgsMapsPerVsys;
231 IgnoresMapsMap mapIgnoresMapsPerVsys;
232
233 int c;
234 RTGETOPTUNION ValueUnion;
235 RTGETOPTSTATE GetState;
236 // start at 0 because main() has hacked both the argc and argv given to us
237 RTGetOptInit(&GetState, arg->argc, arg->argv, g_aImportApplianceOptions, RT_ELEMENTS(g_aImportApplianceOptions),
238 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
239 while ((c = RTGetOpt(&GetState, &ValueUnion)))
240 {
241 switch (c)
242 {
243 case 'n': // --dry-run
244 fExecute = false;
245 break;
246
247 case 'P': // --detailed-progress
248 g_fDetailedProgress = true;
249 break;
250
251 case 's': // --vsys
252 if (enmApplType == NOT_SET)
253 enmApplType = LOCAL;
254
255 if (enmApplType != LOCAL)
256 return errorSyntax(Appliance::tr("Option \"%s\" can't be used together with \"--cloud\" option."),
257 GetState.pDef->pszLong);
258 if (ValueUnion.u32 == (uint32_t)-1)
259 return errorSyntax(Appliance::tr("Value of option \"%s\" is out of range."),
260 GetState.pDef->pszLong);
261
262 ulCurVsys = ValueUnion.u32;
263 ulCurUnit = (uint32_t)-1;
264 break;
265
266 case 'o': // --ostype
267 if (enmApplType == NOT_SET)
268 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
269 GetState.pDef->pszLong);
270 mapArgsMapsPerVsys[ulCurVsys]["ostype"] = ValueUnion.psz;
271 break;
272
273 case 'V': // --vmname
274 if (enmApplType == NOT_SET)
275 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
276 GetState.pDef->pszLong);
277 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
278 break;
279
280 case 'S': // --settingsfile
281 if (enmApplType != LOCAL)
282 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
283 GetState.pDef->pszLong);
284 mapArgsMapsPerVsys[ulCurVsys]["settingsfile"] = ValueUnion.psz;
285 break;
286
287 case 'p': // --basefolder
288 if (enmApplType == NOT_SET)
289 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
290 GetState.pDef->pszLong);
291 mapArgsMapsPerVsys[ulCurVsys]["basefolder"] = ValueUnion.psz;
292 break;
293
294 case 'g': // --group
295 if (enmApplType != LOCAL)
296 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
297 GetState.pDef->pszLong);
298 mapArgsMapsPerVsys[ulCurVsys]["group"] = ValueUnion.psz;
299 break;
300
301 case 'd': // --description
302 if (enmApplType == NOT_SET)
303 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
304 GetState.pDef->pszLong);
305 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
306 break;
307
308 case 'L': // --eula
309 if (enmApplType != LOCAL)
310 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
311 GetState.pDef->pszLong);
312 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
313 break;
314
315 case 'm': // --memory
316 if (enmApplType == NOT_SET)
317 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
318 GetState.pDef->pszLong);
319 mapArgsMapsPerVsys[ulCurVsys]["memory"] = ValueUnion.psz;
320 break;
321
322 case 'c': // --cpus
323 if (enmApplType == NOT_SET)
324 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
325 GetState.pDef->pszLong);
326 mapArgsMapsPerVsys[ulCurVsys]["cpus"] = ValueUnion.psz;
327 break;
328
329 case 'u': // --unit
330 if (enmApplType != LOCAL)
331 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
332 GetState.pDef->pszLong);
333 if (ValueUnion.u32 == (uint32_t)-1)
334 return errorSyntax(Appliance::tr("Value of option \"%s\" is out of range."),
335 GetState.pDef->pszLong);
336
337 ulCurUnit = ValueUnion.u32;
338 break;
339
340 case 'x': // --ignore
341 if (enmApplType != LOCAL)
342 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
343 GetState.pDef->pszLong);
344 if (ulCurUnit == (uint32_t)-1)
345 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --unit option."),
346 GetState.pDef->pszLong);
347 mapIgnoresMapsPerVsys[ulCurVsys][ulCurUnit] = true;
348 break;
349
350 case 'T': // --scsitype
351 if (enmApplType != LOCAL)
352 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
353 GetState.pDef->pszLong);
354 if (ulCurUnit == (uint32_t)-1)
355 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --unit option."),
356 GetState.pDef->pszLong);
357 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("scsitype%u", ulCurUnit)] = ValueUnion.psz;
358 break;
359
360 case 'C': // --controller
361 if (enmApplType != LOCAL)
362 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
363 GetState.pDef->pszLong);
364 if (ulCurUnit == (uint32_t)-1)
365 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --unit option."),
366 GetState.pDef->pszLong);
367 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("controller%u", ulCurUnit)] = ValueUnion.psz;
368 break;
369
370 case 'E': // --port
371 if (enmApplType != LOCAL)
372 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
373 GetState.pDef->pszLong);
374 if (ulCurUnit == (uint32_t)-1)
375 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --unit option."),
376 GetState.pDef->pszLong);
377 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("port%u", ulCurUnit)] = ValueUnion.psz;
378 break;
379
380 case 'D': // --disk
381 if (enmApplType != LOCAL)
382 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
383 GetState.pDef->pszLong);
384 if (ulCurUnit == (uint32_t)-1)
385 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --unit option."),
386 GetState.pDef->pszLong);
387 mapArgsMapsPerVsys[ulCurVsys][Utf8StrFmt("disk%u", ulCurUnit)] = ValueUnion.psz;
388 break;
389
390 case 'O': // --options
391 if (RT_FAILURE(parseImportOptions(ValueUnion.psz, &options)))
392 return errorArgument(Appliance::tr("Invalid import options '%s'\n"), ValueUnion.psz);
393 break;
394
395 /*--cloud and --vsys are orthogonal, only one must be presented*/
396 case 'j': // --cloud
397 if (enmApplType == NOT_SET)
398 enmApplType = CLOUD;
399
400 if (enmApplType != CLOUD)
401 return errorSyntax(Appliance::tr("Option \"%s\" can't be used together with \"--vsys\" option."),
402 GetState.pDef->pszLong);
403
404 ulCurVsys = 0;
405 break;
406
407 /* Cloud export settings */
408 case 'k': // --cloudprofile
409 if (enmApplType != CLOUD)
410 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
411 GetState.pDef->pszLong);
412 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
413 break;
414
415 case 'l': // --cloudinstanceid
416 if (enmApplType != CLOUD)
417 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
418 GetState.pDef->pszLong);
419 mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"] = ValueUnion.psz;
420 break;
421
422 case 'B': // --cloudbucket
423 if (enmApplType != CLOUD)
424 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
425 GetState.pDef->pszLong);
426 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
427 break;
428
429 case VINF_GETOPT_NOT_OPTION:
430 if (strOvfFilename.isEmpty())
431 strOvfFilename = ValueUnion.psz;
432 else
433 return errorSyntax(Appliance::tr("Invalid parameter '%s'"), ValueUnion.psz);
434 break;
435
436 default:
437 if (c > 0)
438 {
439 if (RT_C_IS_PRINT(c))
440 return errorSyntax(Appliance::tr("Invalid option -%c"), c);
441 else
442 return errorSyntax(Appliance::tr("Invalid option case %i"), c);
443 }
444 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
445 return errorSyntax(Appliance::tr("unknown option: %s\n"), ValueUnion.psz);
446 else if (ValueUnion.pDef)
447 return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c);
448 else
449 return errorSyntax(Appliance::tr("error: %Rrs"), c);
450 }
451 }
452
453 /* Last check after parsing all arguments */
454 if (strOvfFilename.isEmpty())
455 return errorSyntax(Appliance::tr("Not enough arguments for \"import\" command."));
456
457 if (enmApplType == NOT_SET)
458 enmApplType = LOCAL;
459
460 do
461 {
462 ComPtr<IAppliance> pAppliance;
463 CHECK_ERROR_BREAK(arg->virtualBox, CreateAppliance(pAppliance.asOutParam()));
464 //in the case of Cloud, append the instance id here because later it's harder to do
465 if (enmApplType == CLOUD)
466 {
467 try
468 {
469 /* Check presence of cloudprofile and cloudinstanceid in the map.
470 * If there isn't the exception is triggered. It's standard std:map logic.*/
471 ArgsMap a = mapArgsMapsPerVsys[ulCurVsys];
472 (void)a.at("cloudprofile");
473 (void)a.at("cloudinstanceid");
474 }
475 catch (...)
476 {
477 return errorSyntax(Appliance::tr("Not enough arguments for import from the Cloud."));
478 }
479
480 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"]);
481 strOvfFilename.append("/");
482 strOvfFilename.append(mapArgsMapsPerVsys[ulCurVsys]["cloudinstanceid"]);
483 }
484
485 char *pszAbsFilePath;
486 if (strOvfFilename.startsWith("S3://", RTCString::CaseInsensitive) ||
487 strOvfFilename.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
488 strOvfFilename.startsWith("webdav://", RTCString::CaseInsensitive) ||
489 strOvfFilename.startsWith("OCI://", RTCString::CaseInsensitive))
490 pszAbsFilePath = RTStrDup(strOvfFilename.c_str());
491 else
492 pszAbsFilePath = RTPathAbsDup(strOvfFilename.c_str());
493
494 ComPtr<IProgress> progressRead;
495 CHECK_ERROR_BREAK(pAppliance, Read(Bstr(pszAbsFilePath).raw(),
496 progressRead.asOutParam()));
497 RTStrFree(pszAbsFilePath);
498
499 hrc = showProgress(progressRead);
500 CHECK_PROGRESS_ERROR_RET(progressRead, (Appliance::tr("Appliance read failed")), RTEXITCODE_FAILURE);
501
502 Bstr path; /* fetch the path, there is stuff like username/password removed if any */
503 CHECK_ERROR_BREAK(pAppliance, COMGETTER(Path)(path.asOutParam()));
504
505 size_t cVirtualSystemDescriptions = 0;
506 com::SafeIfaceArray<IVirtualSystemDescription> aVirtualSystemDescriptions;
507
508 if (enmApplType == LOCAL)
509 {
510 // call interpret(); this can yield both warnings and errors, so we need
511 // to tinker with the error info a bit
512 RTStrmPrintf(g_pStdErr, Appliance::tr("Interpreting %ls...\n"), path.raw());
513 hrc = pAppliance->Interpret();
514 com::ErrorInfoKeeper eik;
515
516 /** @todo r=klaus Eliminate this special way of signalling
517 * warnings which should be part of the ErrorInfo. */
518 com::SafeArray<BSTR> aWarnings;
519 if (SUCCEEDED(pAppliance->GetWarnings(ComSafeArrayAsOutParam(aWarnings))))
520 {
521 size_t cWarnings = aWarnings.size();
522 for (unsigned i = 0; i < cWarnings; ++i)
523 {
524 Bstr bstrWarning(aWarnings[i]);
525 RTMsgWarning("%ls.", bstrWarning.raw());
526 }
527 }
528
529 eik.restore();
530 if (FAILED(hrc)) // during interpret, after printing warnings
531 {
532 com::GlueHandleComError(pAppliance, "Interpret()", hrc, __FILE__, __LINE__);
533 break;
534 }
535
536 RTStrmPrintf(g_pStdErr, "OK.\n");
537
538 // fetch all disks
539 com::SafeArray<BSTR> retDisks;
540 CHECK_ERROR_BREAK(pAppliance,
541 COMGETTER(Disks)(ComSafeArrayAsOutParam(retDisks)));
542 if (retDisks.size() > 0)
543 {
544 RTPrintf(Appliance::tr("Disks:\n"));
545 for (unsigned i = 0; i < retDisks.size(); i++)
546 RTPrintf(" %ls\n", retDisks[i]);
547 RTPrintf("\n");
548 }
549
550 // fetch virtual system descriptions
551 CHECK_ERROR_BREAK(pAppliance,
552 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
553
554 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
555
556 // match command line arguments with virtual system descriptions;
557 // this is only to sort out invalid indices at this time
558 ArgsMapsMap::const_iterator it;
559 for (it = mapArgsMapsPerVsys.begin();
560 it != mapArgsMapsPerVsys.end();
561 ++it)
562 {
563 uint32_t ulVsys = it->first;
564 if (ulVsys >= cVirtualSystemDescriptions)
565 return errorSyntax(Appliance::tr("Invalid index %RI32 with -vsys option; the OVF contains only %zu virtual system(s).",
566 "", cVirtualSystemDescriptions),
567 ulVsys, cVirtualSystemDescriptions);
568 }
569 }
570 else if (enmApplType == CLOUD)
571 {
572 /* In the Cloud case the call of interpret() isn't needed because there isn't any OVF XML file.
573 * All info is got from the Cloud and VSD is filled inside IAppliance::read(). */
574 // fetch virtual system descriptions
575 CHECK_ERROR_BREAK(pAppliance,
576 COMGETTER(VirtualSystemDescriptions)(ComSafeArrayAsOutParam(aVirtualSystemDescriptions)));
577
578 cVirtualSystemDescriptions = aVirtualSystemDescriptions.size();
579 }
580
581 uint32_t cLicensesInTheWay = 0;
582
583 // dump virtual system descriptions and match command-line arguments
584 if (cVirtualSystemDescriptions > 0)
585 {
586 for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
587 {
588 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
589 com::SafeArray<BSTR> aRefs;
590 com::SafeArray<BSTR> aOvfValues;
591 com::SafeArray<BSTR> aVBoxValues;
592 com::SafeArray<BSTR> aExtraConfigValues;
593 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
594 GetDescription(ComSafeArrayAsOutParam(retTypes),
595 ComSafeArrayAsOutParam(aRefs),
596 ComSafeArrayAsOutParam(aOvfValues),
597 ComSafeArrayAsOutParam(aVBoxValues),
598 ComSafeArrayAsOutParam(aExtraConfigValues)));
599
600 RTPrintf(Appliance::tr("Virtual system %u:\n"), i);
601
602 // look up the corresponding command line options, if any
603 ArgsMap *pmapArgs = NULL;
604 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
605 if (itm != mapArgsMapsPerVsys.end())
606 pmapArgs = &itm->second;
607
608 // this collects the final values for setFinalValues()
609 com::SafeArray<BOOL> aEnabled(retTypes.size());
610 com::SafeArray<BSTR> aFinalValues(retTypes.size());
611
612 for (unsigned a = 0; a < retTypes.size(); ++a)
613 {
614 VirtualSystemDescriptionType_T t = retTypes[a];
615
616 Utf8Str strOverride;
617
618 Bstr bstrFinalValue = aVBoxValues[a];
619
620 bool fIgnoreThis = mapIgnoresMapsPerVsys[i][a];
621
622 aEnabled[a] = true;
623
624 switch (t)
625 {
626 case VirtualSystemDescriptionType_OS:
627 if (findArgValue(strOverride, pmapArgs, "ostype"))
628 {
629 bstrFinalValue = strOverride;
630 RTPrintf(Appliance::tr("%2u: OS type specified with --ostype: \"%ls\"\n"),
631 a, bstrFinalValue.raw());
632 }
633 else
634 RTPrintf(Appliance::tr("%2u: Suggested OS type: \"%ls\"\n"
635 " (change with \"--vsys %u --ostype <type>\"; use \"list ostypes\" to list all possible values)\n"),
636 a, bstrFinalValue.raw(), i);
637 break;
638
639 case VirtualSystemDescriptionType_Name:
640 if (findArgValue(strOverride, pmapArgs, "vmname"))
641 {
642 bstrFinalValue = strOverride;
643 RTPrintf(Appliance::tr("%2u: VM name specified with --vmname: \"%ls\"\n"),
644 a, bstrFinalValue.raw());
645 }
646 else
647 RTPrintf(Appliance::tr("%2u: Suggested VM name \"%ls\"\n"
648 " (change with \"--vsys %u --vmname <name>\")\n"),
649 a, bstrFinalValue.raw(), i);
650 break;
651
652 case VirtualSystemDescriptionType_Product:
653 RTPrintf(Appliance::tr("%2u: Product (ignored): %ls\n"),
654 a, aVBoxValues[a]);
655 break;
656
657 case VirtualSystemDescriptionType_ProductUrl:
658 RTPrintf(Appliance::tr("%2u: ProductUrl (ignored): %ls\n"),
659 a, aVBoxValues[a]);
660 break;
661
662 case VirtualSystemDescriptionType_Vendor:
663 RTPrintf(Appliance::tr("%2u: Vendor (ignored): %ls\n"),
664 a, aVBoxValues[a]);
665 break;
666
667 case VirtualSystemDescriptionType_VendorUrl:
668 RTPrintf(Appliance::tr("%2u: VendorUrl (ignored): %ls\n"),
669 a, aVBoxValues[a]);
670 break;
671
672 case VirtualSystemDescriptionType_Version:
673 RTPrintf(Appliance::tr("%2u: Version (ignored): %ls\n"),
674 a, aVBoxValues[a]);
675 break;
676
677 case VirtualSystemDescriptionType_Description:
678 if (findArgValue(strOverride, pmapArgs, "description"))
679 {
680 bstrFinalValue = strOverride;
681 RTPrintf(Appliance::tr("%2u: Description specified with --description: \"%ls\"\n"),
682 a, bstrFinalValue.raw());
683 }
684 else
685 RTPrintf(Appliance::tr("%2u: Description \"%ls\"\n"
686 " (change with \"--vsys %u --description <desc>\")\n"),
687 a, bstrFinalValue.raw(), i);
688 break;
689
690 case VirtualSystemDescriptionType_License:
691 ++cLicensesInTheWay;
692 if (findArgValue(strOverride, pmapArgs, "eula"))
693 {
694 if (strOverride == "show")
695 {
696 RTPrintf(Appliance::tr("%2u: End-user license agreement\n"
697 " (accept with \"--vsys %u --eula accept\"):\n"
698 "\n%ls\n\n"),
699 a, i, bstrFinalValue.raw());
700 }
701 else if (strOverride == "accept")
702 {
703 RTPrintf(Appliance::tr("%2u: End-user license agreement (accepted)\n"),
704 a);
705 --cLicensesInTheWay;
706 }
707 else
708 return errorSyntax(Appliance::tr("Argument to --eula must be either \"show\" or \"accept\"."));
709 }
710 else
711 RTPrintf(Appliance::tr("%2u: End-user license agreement\n"
712 " (display with \"--vsys %u --eula show\";\n"
713 " accept with \"--vsys %u --eula accept\")\n"),
714 a, i, i);
715 break;
716
717 case VirtualSystemDescriptionType_CPU:
718 if (findArgValue(strOverride, pmapArgs, "cpus"))
719 {
720 uint32_t cCPUs;
721 if ( strOverride.toInt(cCPUs) == VINF_SUCCESS
722 && cCPUs >= VMM_MIN_CPU_COUNT
723 && cCPUs <= VMM_MAX_CPU_COUNT
724 )
725 {
726 bstrFinalValue = strOverride;
727 RTPrintf(Appliance::tr("%2u: No. of CPUs specified with --cpus: %ls\n"),
728 a, bstrFinalValue.raw());
729 }
730 else
731 return errorSyntax(Appliance::tr("Argument to --cpus option must be a number greater than %d and less than %d."),
732 VMM_MIN_CPU_COUNT - 1, VMM_MAX_CPU_COUNT + 1);
733 }
734 else
735 RTPrintf(Appliance::tr("%2u: Number of CPUs: %ls\n (change with \"--vsys %u --cpus <n>\")\n"),
736 a, bstrFinalValue.raw(), i);
737 break;
738
739 case VirtualSystemDescriptionType_Memory:
740 {
741 if (findArgValue(strOverride, pmapArgs, "memory"))
742 {
743 uint32_t ulMemMB;
744 if (VINF_SUCCESS == strOverride.toInt(ulMemMB))
745 {
746 bstrFinalValue = strOverride;
747 RTPrintf(Appliance::tr("%2u: Guest memory specified with --memory: %ls MB\n"),
748 a, bstrFinalValue.raw());
749 }
750 else
751 return errorSyntax(Appliance::tr("Argument to --memory option must be a non-negative number."));
752 }
753 else
754 RTPrintf(Appliance::tr("%2u: Guest memory: %ls MB\n (change with \"--vsys %u --memory <MB>\")\n"),
755 a, bstrFinalValue.raw(), i);
756 break;
757 }
758
759 case VirtualSystemDescriptionType_HardDiskControllerIDE:
760 if (fIgnoreThis)
761 {
762 RTPrintf(Appliance::tr("%2u: IDE controller, type %ls -- disabled\n"),
763 a,
764 aVBoxValues[a]);
765 aEnabled[a] = false;
766 }
767 else
768 RTPrintf(Appliance::tr("%2u: IDE controller, type %ls\n"
769 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
770 a,
771 aVBoxValues[a],
772 i, a);
773 break;
774
775 case VirtualSystemDescriptionType_HardDiskControllerSATA:
776 if (fIgnoreThis)
777 {
778 RTPrintf(Appliance::tr("%2u: SATA controller, type %ls -- disabled\n"),
779 a,
780 aVBoxValues[a]);
781 aEnabled[a] = false;
782 }
783 else
784 RTPrintf(Appliance::tr("%2u: SATA controller, type %ls\n"
785 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
786 a,
787 aVBoxValues[a],
788 i, a);
789 break;
790
791 case VirtualSystemDescriptionType_HardDiskControllerSAS:
792 if (fIgnoreThis)
793 {
794 RTPrintf(Appliance::tr("%2u: SAS controller, type %ls -- disabled\n"),
795 a,
796 aVBoxValues[a]);
797 aEnabled[a] = false;
798 }
799 else
800 RTPrintf(Appliance::tr("%2u: SAS controller, type %ls\n"
801 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
802 a,
803 aVBoxValues[a],
804 i, a);
805 break;
806
807 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
808 if (fIgnoreThis)
809 {
810 RTPrintf(Appliance::tr("%2u: SCSI controller, type %ls -- disabled\n"),
811 a,
812 aVBoxValues[a]);
813 aEnabled[a] = false;
814 }
815 else
816 {
817 Utf8StrFmt strTypeArg("scsitype%u", a);
818 if (findArgValue(strOverride, pmapArgs, strTypeArg))
819 {
820 bstrFinalValue = strOverride;
821 RTPrintf(Appliance::tr("%2u: SCSI controller, type set with --unit %u --scsitype: \"%ls\"\n"),
822 a,
823 a,
824 bstrFinalValue.raw());
825 }
826 else
827 RTPrintf(Appliance::tr("%2u: SCSI controller, type %ls\n"
828 " (change with \"--vsys %u --unit %u --scsitype {BusLogic|LsiLogic}\";\n"
829 " disable with \"--vsys %u --unit %u --ignore\")\n"),
830 a,
831 aVBoxValues[a],
832 i, a, i, a);
833 }
834 break;
835
836 case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
837 if (fIgnoreThis)
838 {
839 RTPrintf(Appliance::tr("%2u: VirtioSCSI controller, type %ls -- disabled\n"),
840 a,
841 aVBoxValues[a]);
842 aEnabled[a] = false;
843 }
844 else
845 RTPrintf(Appliance::tr("%2u: VirtioSCSI controller, type %ls\n"
846 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
847 a,
848 aVBoxValues[a],
849 i, a);
850 break;
851
852 case VirtualSystemDescriptionType_HardDiskImage:
853 if (fIgnoreThis)
854 {
855 RTPrintf(Appliance::tr("%2u: Hard disk image: source image=%ls -- disabled\n"),
856 a,
857 aOvfValues[a]);
858 aEnabled[a] = false;
859 }
860 else
861 {
862 Utf8StrFmt strTypeArg("disk%u", a);
863 bool fDiskChanged = false;
864 int vrc;
865 RTCList<ImportOptions_T> optionsList = options.toList();
866
867 if (findArgValue(strOverride, pmapArgs, strTypeArg))
868 {
869 if (optionsList.contains(ImportOptions_ImportToVDI))
870 return errorSyntax(Appliance::tr("Option --ImportToVDI can not be used together with a manually set target path."));
871 RTUUID uuid;
872 /* Check if this is a uuid. If so, don't touch. */
873 vrc = RTUuidFromStr(&uuid, strOverride.c_str());
874 if (vrc != VINF_SUCCESS)
875 {
876 /* Make the path absolute. */
877 if (!RTPathStartsWithRoot(strOverride.c_str()))
878 {
879 char pszPwd[RTPATH_MAX];
880 vrc = RTPathGetCurrent(pszPwd, RTPATH_MAX);
881 if (RT_SUCCESS(vrc))
882 strOverride = Utf8Str(pszPwd).append(RTPATH_SLASH).append(strOverride);
883 }
884 }
885 bstrFinalValue = strOverride;
886 fDiskChanged = true;
887 }
888
889 strTypeArg.printf("controller%u", a);
890 bool fControllerChanged = false;
891 uint32_t uTargetController = (uint32_t)-1;
892 VirtualSystemDescriptionType_T vsdControllerType = VirtualSystemDescriptionType_Ignore;
893 Utf8Str strExtraConfigValue;
894 if (findArgValue(strOverride, pmapArgs, strTypeArg))
895 {
896 vrc = getStorageControllerDetailsFromStr(strOverride, NULL, &uTargetController);
897 if (RT_FAILURE(vrc))
898 return errorSyntax(Appliance::tr("Invalid controller value: '%s'"),
899 strOverride.c_str());
900
901 vsdControllerType = retTypes[uTargetController];
902 if (!isStorageControllerType(vsdControllerType))
903 return errorSyntax(Appliance::tr("Invalid storage controller specified: %u"),
904 uTargetController);
905
906 fControllerChanged = true;
907 }
908
909 strTypeArg.printf("port%u", a);
910 bool fControllerPortChanged = false;
911 uint32_t uTargetControllerPort = (uint32_t)-1;;
912 if (findArgValue(strOverride, pmapArgs, strTypeArg))
913 {
914 vrc = getStorageControllerDetailsFromStr(strOverride, NULL, &uTargetControllerPort);
915 if (RT_FAILURE(vrc))
916 return errorSyntax(Appliance::tr("Invalid port value: '%s'"),
917 strOverride.c_str());
918
919 fControllerPortChanged = true;
920 }
921
922 /*
923 * aExtraConfigValues[a] has a format of 'controller=12;channel=0' and is set by
924 * Appliance::interpret() so any parsing errors here aren't due to user-supplied
925 * values so different error messages here.
926 */
927 uint32_t uOrigController;
928 Utf8Str strOrigController(Bstr(aExtraConfigValues[a]).raw());
929 vrc = getStorageControllerDetailsFromStr(strOrigController, "controller=", &uOrigController);
930 if (RT_FAILURE(vrc))
931 return RTMsgErrorExitFailure(Appliance::tr("Failed to extract controller value from ExtraConfig: '%s'"),
932 strOrigController.c_str());
933
934 uint32_t uOrigControllerPort;
935 vrc = getStorageControllerDetailsFromStr(strOrigController, "channel=", &uOrigControllerPort);
936 if (RT_FAILURE(vrc))
937 return RTMsgErrorExitFailure(Appliance::tr("Failed to extract channel value from ExtraConfig: '%s'"),
938 strOrigController.c_str());
939
940 /*
941 * The 'strExtraConfigValue' string is used to display the storage controller and
942 * port details for each virtual hard disk using the more accurate 'controller=' and
943 * 'port=' labels. The aExtraConfigValues[a] string has a format of
944 * 'controller=%u;channel=%u' from Appliance::interpret() which is required as per
945 * the API but for consistency and clarity with the CLI options --controller and
946 * --port we instead use strExtraConfigValue in the output below.
947 */
948 strExtraConfigValue = Utf8StrFmt("controller=%u;port=%u", uOrigController, uOrigControllerPort);
949
950 if (fControllerChanged || fControllerPortChanged)
951 {
952 /*
953 * Verify that the new combination of controller and controller port is valid.
954 * cf. StorageController::i_checkPortAndDeviceValid()
955 */
956 if (uTargetControllerPort == (uint32_t)-1)
957 uTargetControllerPort = uOrigControllerPort;
958 if (uTargetController == (uint32_t)-1)
959 uTargetController = uOrigController;
960
961 if ( uOrigController == uTargetController
962 && uOrigControllerPort == uTargetControllerPort)
963 return errorSyntax(Appliance::tr("Device already attached to controller %u at this port (%u) location."),
964 uTargetController,
965 uTargetControllerPort);
966
967 if (vsdControllerType == VirtualSystemDescriptionType_Ignore)
968 vsdControllerType = retTypes[uOrigController];
969 if (!isStorageControllerType(vsdControllerType))
970 return errorSyntax(Appliance::tr("Invalid storage controller specified: %u"),
971 uOrigController);
972
973 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
974 ComPtr<ISystemProperties> systemProperties;
975 CHECK_ERROR(pVirtualBox, COMGETTER(SystemProperties)(systemProperties.asOutParam()));
976 ULONG maxPorts = 0;
977 StorageBus_T enmStorageBus = StorageBus_Null;;
978 switch (vsdControllerType)
979 {
980 case VirtualSystemDescriptionType_HardDiskControllerIDE:
981 enmStorageBus = StorageBus_IDE;
982 break;
983 case VirtualSystemDescriptionType_HardDiskControllerSATA:
984 enmStorageBus = StorageBus_SATA;
985 break;
986 case VirtualSystemDescriptionType_HardDiskControllerSCSI:
987 enmStorageBus = StorageBus_SCSI;
988 break;
989 case VirtualSystemDescriptionType_HardDiskControllerSAS:
990 enmStorageBus = StorageBus_SAS;
991 break;
992 case VirtualSystemDescriptionType_HardDiskControllerVirtioSCSI:
993 enmStorageBus = StorageBus_VirtioSCSI;
994 break;
995 default: // Not reached since vsdControllerType validated above but silence gcc.
996 break;
997 }
998 CHECK_ERROR_RET(systemProperties, GetMaxPortCountForStorageBus(enmStorageBus, &maxPorts),
999 RTEXITCODE_FAILURE);
1000 if (uTargetControllerPort >= maxPorts)
1001 return errorSyntax(Appliance::tr("Illegal port value: %u. For %ls controllers the only valid values are 0 to %lu (inclusive)"),
1002 uTargetControllerPort,
1003 aVBoxValues[uTargetController],
1004 maxPorts);
1005
1006 /*
1007 * The 'strOverride' string will be mapped to the strExtraConfigCurrent value in
1008 * VirtualSystemDescription::setFinalValues() which is then used in the appliance
1009 * import routines i_importVBoxMachine()/i_importMachineGeneric() later. This
1010 * aExtraConfigValues[] array entry must have a format of
1011 * 'controller=<index>;channel=<c>' as per the API documentation.
1012 */
1013 strExtraConfigValue = Utf8StrFmt("controller=%u;port=%u", uTargetController,
1014 uTargetControllerPort);
1015 strOverride = Utf8StrFmt("controller=%u;channel=%u", uTargetController,
1016 uTargetControllerPort);
1017 Bstr bstrExtraConfigValue = strOverride;
1018 bstrExtraConfigValue.detachTo(&aExtraConfigValues[a]);
1019 }
1020
1021 if (fDiskChanged && !fControllerChanged && !fControllerPortChanged)
1022 {
1023 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --disk: source image=%ls, target path=%ls, %s\n"
1024 " (change controller with \"--vsys %u --unit %u --controller <index>\";\n"
1025 " change controller port with \"--vsys %u --unit %u --port <n>\")\n"),
1026 a,
1027 aOvfValues[a],
1028 bstrFinalValue.raw(),
1029 strExtraConfigValue.c_str(),
1030 i, a,
1031 i, a);
1032 }
1033 else if (fDiskChanged && fControllerChanged && !fControllerPortChanged)
1034 {
1035 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --disk and --controller: source image=%ls, target path=%ls, %s\n"
1036 " (change controller port with \"--vsys %u --unit %u --port <n>\")\n"),
1037 a,
1038 aOvfValues[a],
1039 bstrFinalValue.raw(),
1040 strExtraConfigValue.c_str(),
1041 i, a);
1042 }
1043 else if (fDiskChanged && !fControllerChanged && fControllerPortChanged)
1044 {
1045 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --disk and --port: source image=%ls, target path=%ls, %s\n"
1046 " (change controller with \"--vsys %u --unit %u --controller <index>\")\n"),
1047 a,
1048 aOvfValues[a],
1049 bstrFinalValue.raw(),
1050 strExtraConfigValue.c_str(),
1051 i, a);
1052 }
1053 else if (!fDiskChanged && fControllerChanged && fControllerPortChanged)
1054 {
1055 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --controller and --port: source image=%ls, target path=%ls, %s\n"
1056 " (change target path with \"--vsys %u --unit %u --disk path\")\n"),
1057 a,
1058 aOvfValues[a],
1059 bstrFinalValue.raw(),
1060 strExtraConfigValue.c_str(),
1061 i, a);
1062 }
1063 else if (!fDiskChanged && !fControllerChanged && fControllerPortChanged)
1064 {
1065 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --port: source image=%ls, target path=%ls, %s\n"
1066 " (change target path with \"--vsys %u --unit %u --disk path\";\n"
1067 " change controller with \"--vsys %u --unit %u --controller <index>\")\n"),
1068 a,
1069 aOvfValues[a],
1070 bstrFinalValue.raw(),
1071 strExtraConfigValue.c_str(),
1072 i, a,
1073 i, a);
1074 }
1075 else if (!fDiskChanged && fControllerChanged && !fControllerPortChanged)
1076 {
1077 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --controller: source image=%ls, target path=%ls, %s\n"
1078 " (change target path with \"--vsys %u --unit %u --disk path\";\n"
1079 " change controller port with \"--vsys %u --unit %u --port <n>\")\n"),
1080 a,
1081 aOvfValues[a],
1082 bstrFinalValue.raw(),
1083 strExtraConfigValue.c_str(),
1084 i, a,
1085 i, a);
1086 }
1087 else if (fDiskChanged && fControllerChanged && fControllerPortChanged)
1088 {
1089 RTPrintf(Appliance::tr("%2u: Hard disk image specified with --disk and --controller and --port: source image=%ls, target path=%ls, %s\n"),
1090 a,
1091 aOvfValues[a],
1092 bstrFinalValue.raw(),
1093 strExtraConfigValue.c_str());
1094 }
1095 else
1096 {
1097 strOverride = aVBoxValues[a];
1098
1099 /*
1100 * Current solution isn't optimal.
1101 * Better way is to provide API call for function
1102 * Appliance::i_findMediumFormatFromDiskImage()
1103 * and creating one new function which returns
1104 * struct ovf::DiskImage for currently processed disk.
1105 */
1106
1107 /*
1108 * if user wants to convert all imported disks to VDI format
1109 * we need to replace files extensions to "vdi"
1110 * except CD/DVD disks
1111 */
1112 if (optionsList.contains(ImportOptions_ImportToVDI))
1113 {
1114 ComPtr<IVirtualBox> pVirtualBox = arg->virtualBox;
1115 ComPtr<ISystemProperties> systemProperties;
1116 com::SafeIfaceArray<IMediumFormat> mediumFormats;
1117 Bstr bstrFormatName;
1118
1119 CHECK_ERROR(pVirtualBox,
1120 COMGETTER(SystemProperties)(systemProperties.asOutParam()));
1121
1122 CHECK_ERROR(systemProperties,
1123 COMGETTER(MediumFormats)(ComSafeArrayAsOutParam(mediumFormats)));
1124
1125 /* go through all supported media formats and store files extensions only for RAW */
1126 com::SafeArray<BSTR> extensions;
1127
1128 for (unsigned j = 0; j < mediumFormats.size(); ++j)
1129 {
1130 com::SafeArray<DeviceType_T> deviceType;
1131 ComPtr<IMediumFormat> mediumFormat = mediumFormats[j];
1132 CHECK_ERROR(mediumFormat, COMGETTER(Name)(bstrFormatName.asOutParam()));
1133 Utf8Str strFormatName = Utf8Str(bstrFormatName);
1134
1135 if (strFormatName.compare("RAW", Utf8Str::CaseInsensitive) == 0)
1136 {
1137 /* getting files extensions for "RAW" format */
1138 CHECK_ERROR(mediumFormat,
1139 DescribeFileExtensions(ComSafeArrayAsOutParam(extensions),
1140 ComSafeArrayAsOutParam(deviceType)));
1141 break;
1142 }
1143 }
1144
1145 /* go through files extensions for RAW format and compare them with
1146 * extension of current file
1147 */
1148 bool fReplace = true;
1149
1150 const char *pszExtension = RTPathSuffix(strOverride.c_str());
1151 if (pszExtension)
1152 pszExtension++;
1153
1154 for (unsigned j = 0; j < extensions.size(); ++j)
1155 {
1156 Bstr bstrExt(extensions[j]);
1157 Utf8Str strExtension(bstrExt);
1158 if(strExtension.compare(pszExtension, Utf8Str::CaseInsensitive) == 0)
1159 {
1160 fReplace = false;
1161 break;
1162 }
1163 }
1164
1165 if (fReplace)
1166 {
1167 strOverride = strOverride.stripSuffix();
1168 strOverride = strOverride.append(".").append("vdi");
1169 }
1170 }
1171
1172 bstrFinalValue = strOverride;
1173
1174 RTPrintf(Appliance::tr("%2u: Hard disk image: source image=%ls, target path=%ls, %s\n"
1175 " (change target path with \"--vsys %u --unit %u --disk path\";\n"
1176 " change controller with \"--vsys %u --unit %u --controller <index>\";\n"
1177 " change controller port with \"--vsys %u --unit %u --port <n>\";\n"
1178 " disable with \"--vsys %u --unit %u --ignore\")\n"),
1179 a, aOvfValues[a], bstrFinalValue.raw(), strExtraConfigValue.c_str(),
1180 i, a,
1181 i, a,
1182 i, a,
1183 i, a);
1184 }
1185 }
1186 break;
1187
1188 case VirtualSystemDescriptionType_CDROM:
1189 if (fIgnoreThis)
1190 {
1191 RTPrintf(Appliance::tr("%2u: CD-ROM -- disabled\n"),
1192 a);
1193 aEnabled[a] = false;
1194 }
1195 else
1196 RTPrintf(Appliance::tr("%2u: CD-ROM\n"
1197 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
1198 a, i, a);
1199 break;
1200
1201 case VirtualSystemDescriptionType_Floppy:
1202 if (fIgnoreThis)
1203 {
1204 RTPrintf(Appliance::tr("%2u: Floppy -- disabled\n"),
1205 a);
1206 aEnabled[a] = false;
1207 }
1208 else
1209 RTPrintf(Appliance::tr("%2u: Floppy\n"
1210 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
1211 a, i, a);
1212 break;
1213
1214 case VirtualSystemDescriptionType_NetworkAdapter:
1215 RTPrintf(Appliance::tr("%2u: Network adapter: orig %ls, config %ls, extra %ls\n"), /// @todo implement once we have a plan for the back-end
1216 a,
1217 aOvfValues[a],
1218 aVBoxValues[a],
1219 aExtraConfigValues[a]);
1220 break;
1221
1222 case VirtualSystemDescriptionType_USBController:
1223 if (fIgnoreThis)
1224 {
1225 RTPrintf(Appliance::tr("%2u: USB controller -- disabled\n"),
1226 a);
1227 aEnabled[a] = false;
1228 }
1229 else
1230 RTPrintf(Appliance::tr("%2u: USB controller\n"
1231 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
1232 a, i, a);
1233 break;
1234
1235 case VirtualSystemDescriptionType_SoundCard:
1236 if (fIgnoreThis)
1237 {
1238 RTPrintf(Appliance::tr("%2u: Sound card \"%ls\" -- disabled\n"),
1239 a,
1240 aOvfValues[a]);
1241 aEnabled[a] = false;
1242 }
1243 else
1244 RTPrintf(Appliance::tr("%2u: Sound card (appliance expects \"%ls\", can change on import)\n"
1245 " (disable with \"--vsys %u --unit %u --ignore\")\n"),
1246 a,
1247 aOvfValues[a],
1248 i,
1249 a);
1250 break;
1251
1252 case VirtualSystemDescriptionType_SettingsFile:
1253 if (findArgValue(strOverride, pmapArgs, "settingsfile"))
1254 {
1255 bstrFinalValue = strOverride;
1256 RTPrintf(Appliance::tr("%2u: VM settings file name specified with --settingsfile: \"%ls\"\n"),
1257 a, bstrFinalValue.raw());
1258 }
1259 else
1260 RTPrintf(Appliance::tr("%2u: Suggested VM settings file name \"%ls\"\n"
1261 " (change with \"--vsys %u --settingsfile <filename>\")\n"),
1262 a, bstrFinalValue.raw(), i);
1263 break;
1264
1265 case VirtualSystemDescriptionType_BaseFolder:
1266 if (findArgValue(strOverride, pmapArgs, "basefolder"))
1267 {
1268 bstrFinalValue = strOverride;
1269 RTPrintf(Appliance::tr("%2u: VM base folder specified with --basefolder: \"%ls\"\n"),
1270 a, bstrFinalValue.raw());
1271 }
1272 else
1273 RTPrintf(Appliance::tr("%2u: Suggested VM base folder \"%ls\"\n"
1274 " (change with \"--vsys %u --basefolder <path>\")\n"),
1275 a, bstrFinalValue.raw(), i);
1276 break;
1277
1278 case VirtualSystemDescriptionType_PrimaryGroup:
1279 if (findArgValue(strOverride, pmapArgs, "group"))
1280 {
1281 bstrFinalValue = strOverride;
1282 RTPrintf(Appliance::tr("%2u: VM group specified with --group: \"%ls\"\n"),
1283 a, bstrFinalValue.raw());
1284 }
1285 else
1286 RTPrintf(Appliance::tr("%2u: Suggested VM group \"%ls\"\n"
1287 " (change with \"--vsys %u --group <group>\")\n"),
1288 a, bstrFinalValue.raw(), i);
1289 break;
1290
1291 case VirtualSystemDescriptionType_CloudInstanceShape:
1292 RTPrintf(Appliance::tr("%2u: Suggested cloud shape \"%ls\"\n"),
1293 a, bstrFinalValue.raw());
1294 break;
1295
1296 case VirtualSystemDescriptionType_CloudBucket:
1297 if (findArgValue(strOverride, pmapArgs, "cloudbucket"))
1298 {
1299 bstrFinalValue = strOverride;
1300 RTPrintf(Appliance::tr("%2u: Cloud bucket id specified with --cloudbucket: \"%ls\"\n"),
1301 a, bstrFinalValue.raw());
1302 }
1303 else
1304 RTPrintf(Appliance::tr("%2u: Suggested cloud bucket id \"%ls\"\n"
1305 " (change with \"--cloud %u --cloudbucket <id>\")\n"),
1306 a, bstrFinalValue.raw(), i);
1307 break;
1308
1309 case VirtualSystemDescriptionType_CloudProfileName:
1310 if (findArgValue(strOverride, pmapArgs, "cloudprofile"))
1311 {
1312 bstrFinalValue = strOverride;
1313 RTPrintf(Appliance::tr("%2u: Cloud profile name specified with --cloudprofile: \"%ls\"\n"),
1314 a, bstrFinalValue.raw());
1315 }
1316 else
1317 RTPrintf(Appliance::tr("%2u: Suggested cloud profile name \"%ls\"\n"
1318 " (change with \"--cloud %u --cloudprofile <id>\")\n"),
1319 a, bstrFinalValue.raw(), i);
1320 break;
1321
1322 case VirtualSystemDescriptionType_CloudInstanceId:
1323 if (findArgValue(strOverride, pmapArgs, "cloudinstanceid"))
1324 {
1325 bstrFinalValue = strOverride;
1326 RTPrintf(Appliance::tr("%2u: Cloud instance id specified with --cloudinstanceid: \"%ls\"\n"),
1327 a, bstrFinalValue.raw());
1328 }
1329 else
1330 RTPrintf(Appliance::tr("%2u: Suggested cloud instance id \"%ls\"\n"
1331 " (change with \"--cloud %u --cloudinstanceid <id>\")\n"),
1332 a, bstrFinalValue.raw(), i);
1333 break;
1334
1335 case VirtualSystemDescriptionType_CloudImageId:
1336 RTPrintf(Appliance::tr("%2u: Suggested cloud base image id \"%ls\"\n"),
1337 a, bstrFinalValue.raw());
1338 break;
1339 case VirtualSystemDescriptionType_CloudDomain:
1340 case VirtualSystemDescriptionType_CloudBootDiskSize:
1341 case VirtualSystemDescriptionType_CloudOCIVCN:
1342 case VirtualSystemDescriptionType_CloudPublicIP:
1343 case VirtualSystemDescriptionType_CloudOCISubnet:
1344 case VirtualSystemDescriptionType_CloudKeepObject:
1345 case VirtualSystemDescriptionType_CloudLaunchInstance:
1346 case VirtualSystemDescriptionType_CloudInstanceState:
1347 case VirtualSystemDescriptionType_CloudImageState:
1348 case VirtualSystemDescriptionType_Miscellaneous:
1349 case VirtualSystemDescriptionType_CloudInstanceDisplayName:
1350 case VirtualSystemDescriptionType_CloudImageDisplayName:
1351 case VirtualSystemDescriptionType_CloudOCILaunchMode:
1352 case VirtualSystemDescriptionType_CloudPrivateIP:
1353 case VirtualSystemDescriptionType_CloudBootVolumeId:
1354 case VirtualSystemDescriptionType_CloudOCIVCNCompartment:
1355 case VirtualSystemDescriptionType_CloudOCISubnetCompartment:
1356 case VirtualSystemDescriptionType_CloudPublicSSHKey:
1357 case VirtualSystemDescriptionType_BootingFirmware:
1358 case VirtualSystemDescriptionType_CloudInitScriptPath:
1359 case VirtualSystemDescriptionType_CloudCompartmentId:
1360 case VirtualSystemDescriptionType_CloudShapeCpus:
1361 case VirtualSystemDescriptionType_CloudShapeMemory:
1362 /** @todo VirtualSystemDescriptionType_Miscellaneous? */
1363 break;
1364
1365 case VirtualSystemDescriptionType_Ignore:
1366#ifdef VBOX_WITH_XPCOM_CPP_ENUM_HACK
1367 case VirtualSystemDescriptionType_32BitHack:
1368#endif
1369 break;
1370 }
1371
1372 bstrFinalValue.detachTo(&aFinalValues[a]);
1373 }
1374
1375 if (fExecute)
1376 CHECK_ERROR_BREAK(aVirtualSystemDescriptions[i],
1377 SetFinalValues(ComSafeArrayAsInParam(aEnabled),
1378 ComSafeArrayAsInParam(aFinalValues),
1379 ComSafeArrayAsInParam(aExtraConfigValues)));
1380
1381 } // for (unsigned i = 0; i < cVirtualSystemDescriptions; ++i)
1382
1383 if (cLicensesInTheWay == 1)
1384 RTMsgError(Appliance::tr("Cannot import until the license agreement listed above is accepted."));
1385 else if (cLicensesInTheWay > 1)
1386 RTMsgError(Appliance::tr("Cannot import until the %c license agreements listed above are accepted."),
1387 cLicensesInTheWay);
1388
1389 if (!cLicensesInTheWay && fExecute)
1390 {
1391 // go!
1392 ComPtr<IProgress> progress;
1393 CHECK_ERROR_BREAK(pAppliance,
1394 ImportMachines(ComSafeArrayAsInParam(options), progress.asOutParam()));
1395
1396 hrc = showProgress(progress);
1397 CHECK_PROGRESS_ERROR_RET(progress, (Appliance::tr("Appliance import failed")), RTEXITCODE_FAILURE);
1398
1399 if (SUCCEEDED(hrc))
1400 RTPrintf(Appliance::tr("Successfully imported the appliance.\n"));
1401 }
1402 } // end if (aVirtualSystemDescriptions.size() > 0)
1403 } while (0);
1404
1405 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
1406}
1407
1408static int parseExportOptions(const char *psz, com::SafeArray<ExportOptions_T> *options)
1409{
1410 int rc = VINF_SUCCESS;
1411 while (psz && *psz && RT_SUCCESS(rc))
1412 {
1413 size_t len;
1414 const char *pszComma = strchr(psz, ',');
1415 if (pszComma)
1416 len = pszComma - psz;
1417 else
1418 len = strlen(psz);
1419 if (len > 0)
1420 {
1421 if (!RTStrNICmp(psz, "CreateManifest", len))
1422 options->push_back(ExportOptions_CreateManifest);
1423 else if (!RTStrNICmp(psz, "manifest", len))
1424 options->push_back(ExportOptions_CreateManifest);
1425 else if (!RTStrNICmp(psz, "ExportDVDImages", len))
1426 options->push_back(ExportOptions_ExportDVDImages);
1427 else if (!RTStrNICmp(psz, "iso", len))
1428 options->push_back(ExportOptions_ExportDVDImages);
1429 else if (!RTStrNICmp(psz, "StripAllMACs", len))
1430 options->push_back(ExportOptions_StripAllMACs);
1431 else if (!RTStrNICmp(psz, "nomacs", len))
1432 options->push_back(ExportOptions_StripAllMACs);
1433 else if (!RTStrNICmp(psz, "StripAllNonNATMACs", len))
1434 options->push_back(ExportOptions_StripAllNonNATMACs);
1435 else if (!RTStrNICmp(psz, "nomacsbutnat", len))
1436 options->push_back(ExportOptions_StripAllNonNATMACs);
1437 else
1438 rc = VERR_PARSE_ERROR;
1439 }
1440 if (pszComma)
1441 psz += len + 1;
1442 else
1443 psz += len;
1444 }
1445
1446 return rc;
1447}
1448
1449static const RTGETOPTDEF g_aExportOptions[] =
1450{
1451 { "--output", 'o', RTGETOPT_REQ_STRING },
1452 { "--legacy09", 'l', RTGETOPT_REQ_NOTHING },
1453 { "--ovf09", 'l', RTGETOPT_REQ_NOTHING },
1454 { "--ovf10", '1', RTGETOPT_REQ_NOTHING },
1455 { "--ovf20", '2', RTGETOPT_REQ_NOTHING },
1456 { "--opc10", 'c', RTGETOPT_REQ_NOTHING },
1457 { "--manifest", 'm', RTGETOPT_REQ_NOTHING }, // obsoleted by --options
1458 { "--vsys", 's', RTGETOPT_REQ_UINT32 },
1459 { "--vmname", 'V', RTGETOPT_REQ_STRING },
1460 { "--product", 'p', RTGETOPT_REQ_STRING },
1461 { "--producturl", 'P', RTGETOPT_REQ_STRING },
1462 { "--vendor", 'n', RTGETOPT_REQ_STRING },
1463 { "--vendorurl", 'N', RTGETOPT_REQ_STRING },
1464 { "--version", 'v', RTGETOPT_REQ_STRING },
1465 { "--description", 'd', RTGETOPT_REQ_STRING },
1466 { "--eula", 'e', RTGETOPT_REQ_STRING },
1467 { "--eulafile", 'E', RTGETOPT_REQ_STRING },
1468 { "--options", 'O', RTGETOPT_REQ_STRING },
1469 { "--cloud", 'C', RTGETOPT_REQ_UINT32 },
1470 { "--cloudshape", 'S', RTGETOPT_REQ_STRING },
1471 { "--clouddomain", 'D', RTGETOPT_REQ_STRING },
1472 { "--clouddisksize", 'R', RTGETOPT_REQ_STRING },
1473 { "--cloudbucket", 'B', RTGETOPT_REQ_STRING },
1474 { "--cloudocivcn", 'Q', RTGETOPT_REQ_STRING },
1475 { "--cloudpublicip", 'A', RTGETOPT_REQ_STRING },
1476 { "--cloudprofile", 'F', RTGETOPT_REQ_STRING },
1477 { "--cloudocisubnet", 'T', RTGETOPT_REQ_STRING },
1478 { "--cloudkeepobject", 'K', RTGETOPT_REQ_STRING },
1479 { "--cloudlaunchinstance", 'L', RTGETOPT_REQ_STRING },
1480 { "--cloudlaunchmode", 'M', RTGETOPT_REQ_STRING },
1481 { "--cloudprivateip", 'i', RTGETOPT_REQ_STRING },
1482 { "--cloudinitscriptpath", 'I', RTGETOPT_REQ_STRING },
1483};
1484
1485RTEXITCODE handleExportAppliance(HandlerArg *a)
1486{
1487 HRESULT hrc = S_OK;
1488
1489 Utf8Str strOutputFile;
1490 Utf8Str strOvfFormat("ovf-1.0"); // the default export version
1491 bool fManifest = false; // the default
1492 APPLIANCETYPE enmApplType = NOT_SET;
1493 bool fExportISOImages = false; // the default
1494 com::SafeArray<ExportOptions_T> options;
1495 std::list< ComPtr<IMachine> > llMachines;
1496
1497 uint32_t ulCurVsys = (uint32_t)-1;
1498 // for each --vsys X command, maintain a map of command line items
1499 ArgsMapsMap mapArgsMapsPerVsys;
1500 do
1501 {
1502 int c;
1503
1504 RTGETOPTUNION ValueUnion;
1505 RTGETOPTSTATE GetState;
1506 // start at 0 because main() has hacked both the argc and argv given to us
1507 RTGetOptInit(&GetState, a->argc, a->argv, g_aExportOptions,
1508 RT_ELEMENTS(g_aExportOptions), 0, RTGETOPTINIT_FLAGS_NO_STD_OPTS);
1509
1510 Utf8Str strProductUrl;
1511 while ((c = RTGetOpt(&GetState, &ValueUnion)))
1512 {
1513 switch (c)
1514 {
1515 case 'o': // --output
1516 if (strOutputFile.length())
1517 return errorSyntax(Appliance::tr("You can only specify --output once."));
1518 else
1519 strOutputFile = ValueUnion.psz;
1520 break;
1521
1522 case 'l': // --legacy09/--ovf09
1523 strOvfFormat = "ovf-0.9";
1524 break;
1525
1526 case '1': // --ovf10
1527 strOvfFormat = "ovf-1.0";
1528 break;
1529
1530 case '2': // --ovf20
1531 strOvfFormat = "ovf-2.0";
1532 break;
1533
1534 case 'c': // --opc
1535 strOvfFormat = "opc-1.0";
1536 break;
1537
1538// case 'I': // --iso
1539// fExportISOImages = true;
1540// break;
1541
1542 case 'm': // --manifest
1543 fManifest = true;
1544 break;
1545
1546 case 's': // --vsys
1547 if (enmApplType == NOT_SET)
1548 enmApplType = LOCAL;
1549
1550 if (enmApplType != LOCAL)
1551 return errorSyntax(Appliance::tr("Option \"%s\" can't be used together with \"--cloud\" option."),
1552 GetState.pDef->pszLong);
1553 if (ValueUnion.u32 == (uint32_t)-1)
1554 return errorSyntax(Appliance::tr("Value of option \"%s\" is out of range."),
1555 GetState.pDef->pszLong);
1556
1557 ulCurVsys = ValueUnion.u32;
1558 break;
1559
1560 case 'V': // --vmname
1561 if (enmApplType == NOT_SET)
1562 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys or --cloud option."),
1563 GetState.pDef->pszLong);
1564 mapArgsMapsPerVsys[ulCurVsys]["vmname"] = ValueUnion.psz;
1565 break;
1566
1567 case 'p': // --product
1568 if (enmApplType != LOCAL)
1569 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1570 GetState.pDef->pszLong);
1571 mapArgsMapsPerVsys[ulCurVsys]["product"] = ValueUnion.psz;
1572 break;
1573
1574 case 'P': // --producturl
1575 if (enmApplType != LOCAL)
1576 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1577 GetState.pDef->pszLong);
1578 mapArgsMapsPerVsys[ulCurVsys]["producturl"] = ValueUnion.psz;
1579 break;
1580
1581 case 'n': // --vendor
1582 if (enmApplType != LOCAL)
1583 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1584 GetState.pDef->pszLong);
1585 mapArgsMapsPerVsys[ulCurVsys]["vendor"] = ValueUnion.psz;
1586 break;
1587
1588 case 'N': // --vendorurl
1589 if (enmApplType != LOCAL)
1590 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1591 GetState.pDef->pszLong);
1592 mapArgsMapsPerVsys[ulCurVsys]["vendorurl"] = ValueUnion.psz;
1593 break;
1594
1595 case 'v': // --version
1596 if (enmApplType != LOCAL)
1597 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1598 GetState.pDef->pszLong);
1599 mapArgsMapsPerVsys[ulCurVsys]["version"] = ValueUnion.psz;
1600 break;
1601
1602 case 'd': // --description
1603 if (enmApplType != LOCAL)
1604 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1605 GetState.pDef->pszLong);
1606 mapArgsMapsPerVsys[ulCurVsys]["description"] = ValueUnion.psz;
1607 break;
1608
1609 case 'e': // --eula
1610 if (enmApplType != LOCAL)
1611 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1612 GetState.pDef->pszLong);
1613 mapArgsMapsPerVsys[ulCurVsys]["eula"] = ValueUnion.psz;
1614 break;
1615
1616 case 'E': // --eulafile
1617 if (enmApplType != LOCAL)
1618 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --vsys option."),
1619 GetState.pDef->pszLong);
1620 mapArgsMapsPerVsys[ulCurVsys]["eulafile"] = ValueUnion.psz;
1621 break;
1622
1623 case 'O': // --options
1624 if (RT_FAILURE(parseExportOptions(ValueUnion.psz, &options)))
1625 return errorArgument(Appliance::tr("Invalid export options '%s'\n"), ValueUnion.psz);
1626 break;
1627
1628 /*--cloud and --vsys are orthogonal, only one must be presented*/
1629 case 'C': // --cloud
1630 if (enmApplType == NOT_SET)
1631 enmApplType = CLOUD;
1632
1633 if (enmApplType != CLOUD)
1634 return errorSyntax(Appliance::tr("Option \"%s\" can't be used together with \"--vsys\" option."),
1635 GetState.pDef->pszLong);
1636 if (ValueUnion.u32 == (uint32_t)-1)
1637 return errorSyntax(Appliance::tr("Value of option \"%s\" is out of range."),
1638 GetState.pDef->pszLong);
1639
1640 ulCurVsys = ValueUnion.u32;
1641 break;
1642
1643 /* Cloud export settings */
1644 case 'S': // --cloudshape
1645 if (enmApplType != CLOUD)
1646 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1647 GetState.pDef->pszLong);
1648 mapArgsMapsPerVsys[ulCurVsys]["cloudshape"] = ValueUnion.psz;
1649 break;
1650
1651 case 'D': // --clouddomain
1652 if (enmApplType != CLOUD)
1653 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1654 GetState.pDef->pszLong);
1655 mapArgsMapsPerVsys[ulCurVsys]["clouddomain"] = ValueUnion.psz;
1656 break;
1657
1658 case 'R': // --clouddisksize
1659 if (enmApplType != CLOUD)
1660 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1661 GetState.pDef->pszLong);
1662 mapArgsMapsPerVsys[ulCurVsys]["clouddisksize"] = ValueUnion.psz;
1663 break;
1664
1665 case 'B': // --cloudbucket
1666 if (enmApplType != CLOUD)
1667 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1668 GetState.pDef->pszLong);
1669 mapArgsMapsPerVsys[ulCurVsys]["cloudbucket"] = ValueUnion.psz;
1670 break;
1671
1672 case 'Q': // --cloudocivcn
1673 if (enmApplType != CLOUD)
1674 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1675 GetState.pDef->pszLong);
1676 mapArgsMapsPerVsys[ulCurVsys]["cloudocivcn"] = ValueUnion.psz;
1677 break;
1678
1679 case 'A': // --cloudpublicip
1680 if (enmApplType != CLOUD)
1681 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1682 GetState.pDef->pszLong);
1683 mapArgsMapsPerVsys[ulCurVsys]["cloudpublicip"] = ValueUnion.psz;
1684 break;
1685
1686 case 'i': /* --cloudprivateip */
1687 if (enmApplType != CLOUD)
1688 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1689 GetState.pDef->pszLong);
1690 mapArgsMapsPerVsys[ulCurVsys]["cloudprivateip"] = ValueUnion.psz;
1691 break;
1692
1693 case 'F': // --cloudprofile
1694 if (enmApplType != CLOUD)
1695 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1696 GetState.pDef->pszLong);
1697 mapArgsMapsPerVsys[ulCurVsys]["cloudprofile"] = ValueUnion.psz;
1698 break;
1699
1700 case 'T': // --cloudocisubnet
1701 if (enmApplType != CLOUD)
1702 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1703 GetState.pDef->pszLong);
1704 mapArgsMapsPerVsys[ulCurVsys]["cloudocisubnet"] = ValueUnion.psz;
1705 break;
1706
1707 case 'K': // --cloudkeepobject
1708 if (enmApplType != CLOUD)
1709 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1710 GetState.pDef->pszLong);
1711 mapArgsMapsPerVsys[ulCurVsys]["cloudkeepobject"] = ValueUnion.psz;
1712 break;
1713
1714 case 'L': // --cloudlaunchinstance
1715 if (enmApplType != CLOUD)
1716 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1717 GetState.pDef->pszLong);
1718 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchinstance"] = ValueUnion.psz;
1719 break;
1720
1721 case 'M': /* --cloudlaunchmode */
1722 if (enmApplType != CLOUD)
1723 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1724 GetState.pDef->pszLong);
1725 mapArgsMapsPerVsys[ulCurVsys]["cloudlaunchmode"] = ValueUnion.psz;
1726 break;
1727
1728 case 'I': // --cloudinitscriptpath
1729 if (enmApplType != CLOUD)
1730 return errorSyntax(Appliance::tr("Option \"%s\" requires preceding --cloud option."),
1731 GetState.pDef->pszLong);
1732 mapArgsMapsPerVsys[ulCurVsys]["cloudinitscriptpath"] = ValueUnion.psz;
1733 break;
1734
1735 case VINF_GETOPT_NOT_OPTION:
1736 {
1737 Utf8Str strMachine(ValueUnion.psz);
1738 // must be machine: try UUID or name
1739 ComPtr<IMachine> machine;
1740 CHECK_ERROR_BREAK(a->virtualBox, FindMachine(Bstr(strMachine).raw(),
1741 machine.asOutParam()));
1742 if (machine)
1743 llMachines.push_back(machine);
1744 break;
1745 }
1746
1747 default:
1748 if (c > 0)
1749 {
1750 if (RT_C_IS_GRAPH(c))
1751 return errorSyntax(Appliance::tr("unhandled option: -%c"), c);
1752 else
1753 return errorSyntax(Appliance::tr("unhandled option: %i"), c);
1754 }
1755 else if (c == VERR_GETOPT_UNKNOWN_OPTION)
1756 return errorSyntax(Appliance::tr("unknown option: %s"), ValueUnion.psz);
1757 else if (ValueUnion.pDef)
1758 return errorSyntax("%s: %Rrs", ValueUnion.pDef->pszLong, c);
1759 else
1760 return errorSyntax("%Rrs", c);
1761 }
1762
1763 if (FAILED(hrc))
1764 break;
1765 }
1766
1767 if (FAILED(hrc))
1768 break;
1769
1770 if (llMachines.empty())
1771 return errorSyntax(Appliance::tr("At least one machine must be specified with the export command."));
1772
1773 /* Last check after parsing all arguments */
1774 if (strOutputFile.isEmpty())
1775 return errorSyntax(Appliance::tr("Missing --output argument with export command."));
1776
1777 if (enmApplType == NOT_SET)
1778 enmApplType = LOCAL;
1779
1780 // match command line arguments with the machines count
1781 // this is only to sort out invalid indices at this time
1782 ArgsMapsMap::const_iterator it;
1783 for (it = mapArgsMapsPerVsys.begin();
1784 it != mapArgsMapsPerVsys.end();
1785 ++it)
1786 {
1787 uint32_t ulVsys = it->first;
1788 if (ulVsys >= llMachines.size())
1789 return errorSyntax(Appliance::tr("Invalid index %RI32 with -vsys option; you specified only %zu virtual system(s).",
1790 "", llMachines.size()),
1791 ulVsys, llMachines.size());
1792 }
1793
1794 ComPtr<IAppliance> pAppliance;
1795 CHECK_ERROR_BREAK(a->virtualBox, CreateAppliance(pAppliance.asOutParam()));
1796
1797 char *pszAbsFilePath = 0;
1798 if (strOutputFile.startsWith("S3://", RTCString::CaseInsensitive) ||
1799 strOutputFile.startsWith("SunCloud://", RTCString::CaseInsensitive) ||
1800 strOutputFile.startsWith("webdav://", RTCString::CaseInsensitive) ||
1801 strOutputFile.startsWith("OCI://", RTCString::CaseInsensitive))
1802 pszAbsFilePath = RTStrDup(strOutputFile.c_str());
1803 else
1804 pszAbsFilePath = RTPathAbsDup(strOutputFile.c_str());
1805
1806 /*
1807 * The first stage - export machine/s to the Cloud or into the
1808 * OVA/OVF format on the local host.
1809 */
1810
1811 /* VSDList is needed for the second stage where we launch the cloud instances if it was requested by user */
1812 std::list< ComPtr<IVirtualSystemDescription> > VSDList;
1813 std::list< ComPtr<IMachine> >::iterator itM;
1814 uint32_t i=0;
1815 for (itM = llMachines.begin();
1816 itM != llMachines.end();
1817 ++itM, ++i)
1818 {
1819 ComPtr<IMachine> pMachine = *itM;
1820 ComPtr<IVirtualSystemDescription> pVSD;
1821 CHECK_ERROR_BREAK(pMachine, ExportTo(pAppliance, Bstr(pszAbsFilePath).raw(), pVSD.asOutParam()));
1822
1823 // Add additional info to the virtual system description if the user wants so
1824 ArgsMap *pmapArgs = NULL;
1825 ArgsMapsMap::iterator itm = mapArgsMapsPerVsys.find(i);
1826 if (itm != mapArgsMapsPerVsys.end())
1827 pmapArgs = &itm->second;
1828 if (pmapArgs)
1829 {
1830 ArgsMap::iterator itD;
1831 for (itD = pmapArgs->begin();
1832 itD != pmapArgs->end();
1833 ++itD)
1834 {
1835 if (itD->first == "vmname")
1836 {
1837 //remove default value if user has specified new name (default value is set in the ExportTo())
1838// pVSD->RemoveDescriptionByType(VirtualSystemDescriptionType_Name);
1839 pVSD->AddDescription(VirtualSystemDescriptionType_Name,
1840 Bstr(itD->second).raw(), NULL);
1841 }
1842 else if (itD->first == "product")
1843 pVSD->AddDescription(VirtualSystemDescriptionType_Product,
1844 Bstr(itD->second).raw(), NULL);
1845 else if (itD->first == "producturl")
1846 pVSD->AddDescription(VirtualSystemDescriptionType_ProductUrl,
1847 Bstr(itD->second).raw(), NULL);
1848 else if (itD->first == "vendor")
1849 pVSD->AddDescription(VirtualSystemDescriptionType_Vendor,
1850 Bstr(itD->second).raw(), NULL);
1851 else if (itD->first == "vendorurl")
1852 pVSD->AddDescription(VirtualSystemDescriptionType_VendorUrl,
1853 Bstr(itD->second).raw(), NULL);
1854 else if (itD->first == "version")
1855 pVSD->AddDescription(VirtualSystemDescriptionType_Version,
1856 Bstr(itD->second).raw(), NULL);
1857 else if (itD->first == "description")
1858 pVSD->AddDescription(VirtualSystemDescriptionType_Description,
1859 Bstr(itD->second).raw(), NULL);
1860 else if (itD->first == "eula")
1861 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1862 Bstr(itD->second).raw(), NULL);
1863 else if (itD->first == "eulafile")
1864 {
1865 Utf8Str strContent;
1866 void *pvFile;
1867 size_t cbFile;
1868 int irc = RTFileReadAll(itD->second.c_str(), &pvFile, &cbFile);
1869 if (RT_SUCCESS(irc))
1870 {
1871 Bstr bstrContent((char*)pvFile, cbFile);
1872 pVSD->AddDescription(VirtualSystemDescriptionType_License,
1873 bstrContent.raw(), NULL);
1874 RTFileReadAllFree(pvFile, cbFile);
1875 }
1876 else
1877 {
1878 RTMsgError(Appliance::tr("Cannot read license file \"%s\" which should be included in the virtual system %u."),
1879 itD->second.c_str(), i);
1880 return RTEXITCODE_FAILURE;
1881 }
1882 }
1883 /* add cloud export settings */
1884 else if (itD->first == "cloudshape")
1885 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInstanceShape,
1886 Bstr(itD->second).raw(), NULL);
1887 else if (itD->first == "clouddomain")
1888 pVSD->AddDescription(VirtualSystemDescriptionType_CloudDomain,
1889 Bstr(itD->second).raw(), NULL);
1890 else if (itD->first == "clouddisksize")
1891 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBootDiskSize,
1892 Bstr(itD->second).raw(), NULL);
1893 else if (itD->first == "cloudbucket")
1894 pVSD->AddDescription(VirtualSystemDescriptionType_CloudBucket,
1895 Bstr(itD->second).raw(), NULL);
1896 else if (itD->first == "cloudocivcn")
1897 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCIVCN,
1898 Bstr(itD->second).raw(), NULL);
1899 else if (itD->first == "cloudpublicip")
1900 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPublicIP,
1901 Bstr(itD->second).raw(), NULL);
1902 else if (itD->first == "cloudprivateip")
1903 pVSD->AddDescription(VirtualSystemDescriptionType_CloudPrivateIP,
1904 Bstr(itD->second).raw(), NULL);
1905 else if (itD->first == "cloudprofile")
1906 pVSD->AddDescription(VirtualSystemDescriptionType_CloudProfileName,
1907 Bstr(itD->second).raw(), NULL);
1908 else if (itD->first == "cloudocisubnet")
1909 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCISubnet,
1910 Bstr(itD->second).raw(), NULL);
1911 else if (itD->first == "cloudkeepobject")
1912 pVSD->AddDescription(VirtualSystemDescriptionType_CloudKeepObject,
1913 Bstr(itD->second).raw(), NULL);
1914 else if (itD->first == "cloudlaunchmode")
1915 pVSD->AddDescription(VirtualSystemDescriptionType_CloudOCILaunchMode,
1916 Bstr(itD->second).raw(), NULL);
1917 else if (itD->first == "cloudlaunchinstance")
1918 pVSD->AddDescription(VirtualSystemDescriptionType_CloudLaunchInstance,
1919 Bstr(itD->second).raw(), NULL);
1920 else if (itD->first == "cloudinitscriptpath")
1921 pVSD->AddDescription(VirtualSystemDescriptionType_CloudInitScriptPath,
1922 Bstr(itD->second).raw(), NULL);
1923
1924 }
1925 }
1926
1927 VSDList.push_back(pVSD);//store vsd for the possible second stage
1928 }
1929
1930 if (FAILED(hrc))
1931 break;
1932
1933 /* Query required passwords and supply them to the appliance. */
1934 com::SafeArray<BSTR> aIdentifiers;
1935
1936 CHECK_ERROR_BREAK(pAppliance, GetPasswordIds(ComSafeArrayAsOutParam(aIdentifiers)));
1937
1938 if (aIdentifiers.size() > 0)
1939 {
1940 com::SafeArray<BSTR> aPasswords(aIdentifiers.size());
1941 RTPrintf(Appliance::tr("Enter the passwords for the following identifiers to export the apppliance:\n"));
1942 for (unsigned idxId = 0; idxId < aIdentifiers.size(); idxId++)
1943 {
1944 com::Utf8Str strPassword;
1945 Bstr bstrPassword;
1946 Bstr bstrId = aIdentifiers[idxId];
1947
1948 RTEXITCODE rcExit = readPasswordFromConsole(&strPassword, Appliance::tr("Password ID %s:"),
1949 Utf8Str(bstrId).c_str());
1950 if (rcExit == RTEXITCODE_FAILURE)
1951 {
1952 RTStrFree(pszAbsFilePath);
1953 return rcExit;
1954 }
1955
1956 bstrPassword = strPassword;
1957 bstrPassword.detachTo(&aPasswords[idxId]);
1958 }
1959
1960 CHECK_ERROR_BREAK(pAppliance, AddPasswords(ComSafeArrayAsInParam(aIdentifiers),
1961 ComSafeArrayAsInParam(aPasswords)));
1962 }
1963
1964 if (fManifest)
1965 options.push_back(ExportOptions_CreateManifest);
1966
1967 if (fExportISOImages)
1968 options.push_back(ExportOptions_ExportDVDImages);
1969
1970 ComPtr<IProgress> progress;
1971 CHECK_ERROR_BREAK(pAppliance, Write(Bstr(strOvfFormat).raw(),
1972 ComSafeArrayAsInParam(options),
1973 Bstr(pszAbsFilePath).raw(),
1974 progress.asOutParam()));
1975 RTStrFree(pszAbsFilePath);
1976
1977 hrc = showProgress(progress);
1978 CHECK_PROGRESS_ERROR_RET(progress, (Appliance::tr("Appliance write failed")), RTEXITCODE_FAILURE);
1979
1980 if (SUCCEEDED(hrc))
1981 RTPrintf(Appliance::tr("Successfully exported %d machine(s).\n", "", llMachines.size()), llMachines.size());
1982
1983 /*
1984 * The second stage for the cloud case
1985 */
1986 if (enmApplType == CLOUD)
1987 {
1988 /* Launch the exported VM if the appropriate flag had been set on the first stage */
1989 for (std::list< ComPtr<IVirtualSystemDescription> >::iterator itVSD = VSDList.begin();
1990 itVSD != VSDList.end();
1991 ++itVSD)
1992 {
1993 ComPtr<IVirtualSystemDescription> pVSD = *itVSD;
1994
1995 com::SafeArray<VirtualSystemDescriptionType_T> retTypes;
1996 com::SafeArray<BSTR> aRefs;
1997 com::SafeArray<BSTR> aOvfValues;
1998 com::SafeArray<BSTR> aVBoxValues;
1999 com::SafeArray<BSTR> aExtraConfigValues;
2000
2001 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudLaunchInstance,
2002 ComSafeArrayAsOutParam(retTypes),
2003 ComSafeArrayAsOutParam(aRefs),
2004 ComSafeArrayAsOutParam(aOvfValues),
2005 ComSafeArrayAsOutParam(aVBoxValues),
2006 ComSafeArrayAsOutParam(aExtraConfigValues)));
2007
2008 Utf8Str flagCloudLaunchInstance(Bstr(aVBoxValues[0]).raw());
2009 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
2010
2011 if (flagCloudLaunchInstance.equals("true"))
2012 {
2013 /* Getting the short provider name */
2014 Bstr bstrCloudProviderShortName(strOutputFile.c_str(), strOutputFile.find("://"));
2015
2016 ComPtr<IVirtualBox> pVirtualBox = a->virtualBox;
2017 ComPtr<ICloudProviderManager> pCloudProviderManager;
2018 CHECK_ERROR_BREAK(pVirtualBox, COMGETTER(CloudProviderManager)(pCloudProviderManager.asOutParam()));
2019
2020 ComPtr<ICloudProvider> pCloudProvider;
2021 CHECK_ERROR_BREAK(pCloudProviderManager,
2022 GetProviderByShortName(bstrCloudProviderShortName.raw(), pCloudProvider.asOutParam()));
2023
2024 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudProfileName,
2025 ComSafeArrayAsOutParam(retTypes),
2026 ComSafeArrayAsOutParam(aRefs),
2027 ComSafeArrayAsOutParam(aOvfValues),
2028 ComSafeArrayAsOutParam(aVBoxValues),
2029 ComSafeArrayAsOutParam(aExtraConfigValues)));
2030
2031 ComPtr<ICloudProfile> pCloudProfile;
2032 CHECK_ERROR_BREAK(pCloudProvider, GetProfileByName(Bstr(aVBoxValues[0]).raw(), pCloudProfile.asOutParam()));
2033 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
2034
2035 ComObjPtr<ICloudClient> oCloudClient;
2036 CHECK_ERROR_BREAK(pCloudProfile, CreateCloudClient(oCloudClient.asOutParam()));
2037 RTPrintf(Appliance::tr("Creating a cloud instance...\n"));
2038
2039 ComPtr<IProgress> progress1;
2040 CHECK_ERROR_BREAK(oCloudClient, LaunchVM(pVSD, progress1.asOutParam()));
2041 hrc = showProgress(progress1);
2042 CHECK_PROGRESS_ERROR_RET(progress1, (Appliance::tr("Creating the cloud instance failed")),
2043 RTEXITCODE_FAILURE);
2044
2045 if (SUCCEEDED(hrc))
2046 {
2047 CHECK_ERROR_BREAK(pVSD, GetDescriptionByType(VirtualSystemDescriptionType_CloudInstanceId,
2048 ComSafeArrayAsOutParam(retTypes),
2049 ComSafeArrayAsOutParam(aRefs),
2050 ComSafeArrayAsOutParam(aOvfValues),
2051 ComSafeArrayAsOutParam(aVBoxValues),
2052 ComSafeArrayAsOutParam(aExtraConfigValues)));
2053
2054 RTPrintf(Appliance::tr("A cloud instance with id '%s' (provider '%s') was created\n"),
2055 Utf8Str(Bstr(aVBoxValues[0]).raw()).c_str(),
2056 Utf8Str(bstrCloudProviderShortName.raw()).c_str());
2057 retTypes.setNull(); aRefs.setNull(); aOvfValues.setNull(); aVBoxValues.setNull(); aExtraConfigValues.setNull();
2058 }
2059 }
2060 }
2061 }
2062 } while (0);
2063
2064 return SUCCEEDED(hrc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2065}
2066
2067
2068/*********************************************************************************************************************************
2069* signova *
2070*********************************************************************************************************************************/
2071
2072/**
2073 * Reads the OVA and saves the manifest and signed status.
2074 *
2075 * @returns VBox status code (fully messaged).
2076 * @param pszOva The name of the OVA.
2077 * @param iVerbosity The noise level.
2078 * @param fReSign Whether it is acceptable to have an existing signature
2079 * in the OVA or not.
2080 * @param phVfsFssOva Where to return the OVA file system stream handle.
2081 * This has been opened for updating and we're positioned
2082 * at the end of the stream.
2083 * @param pStrManifestName Where to return the manifest name.
2084 * @param phVfsManifest Where to return the manifest file handle (copy in mem).
2085 * @param phVfsOldSignature Where to return the handle to the old signature object.
2086 *
2087 * @note Caller must clean up return values on failure too!
2088 */
2089static int openOvaAndGetManifestAndOldSignature(const char *pszOva, unsigned iVerbosity, bool fReSign,
2090 PRTVFSFSSTREAM phVfsFssOva, Utf8Str *pStrManifestName,
2091 PRTVFSFILE phVfsManifest, PRTVFSOBJ phVfsOldSignature)
2092{
2093 /*
2094 * Clear return values.
2095 */
2096 *phVfsFssOva = NIL_RTVFSFSSTREAM;
2097 pStrManifestName->setNull();
2098 *phVfsManifest = NIL_RTVFSFILE;
2099 *phVfsOldSignature = NIL_RTVFSOBJ;
2100
2101 /*
2102 * Open the file as a tar file system stream.
2103 */
2104 RTVFSFILE hVfsFileOva;
2105 int rc = RTVfsFileOpenNormal(pszOva, RTFILE_O_OPEN | RTFILE_O_READWRITE | RTFILE_O_DENY_WRITE, &hVfsFileOva);
2106 if (RT_FAILURE(rc))
2107 return RTMsgErrorExitFailure(Appliance::tr("Failed to open OVA '%s' for updating: %Rrc"), pszOva, rc);
2108
2109 RTVFSFSSTREAM hVfsFssOva;
2110 rc = RTZipTarFsStreamForFile(hVfsFileOva, RTZIPTARFORMAT_DEFAULT, RTZIPTAR_C_UPDATE, &hVfsFssOva);
2111 RTVfsFileRelease(hVfsFileOva);
2112 if (RT_FAILURE(rc))
2113 return RTMsgErrorExitFailure(Appliance::tr("Failed to open OVA '%s' as a TAR file: %Rrc"), pszOva, rc);
2114 *phVfsFssOva = hVfsFssOva;
2115
2116 /*
2117 * Scan the objects in the stream and locate the manifest and any existing cert file.
2118 */
2119 if (iVerbosity >= 2)
2120 RTMsgInfo(Appliance::tr("Scanning OVA '%s' for a manifest and signature..."), pszOva);
2121 char *pszSignatureName = NULL;
2122 for (;;)
2123 {
2124 /*
2125 * Retrive the next object.
2126 */
2127 char *pszName;
2128 RTVFSOBJTYPE enmType;
2129 RTVFSOBJ hVfsObj;
2130 rc = RTVfsFsStrmNext(hVfsFssOva, &pszName, &enmType, &hVfsObj);
2131 if (RT_FAILURE(rc))
2132 {
2133 if (rc == VERR_EOF)
2134 rc = VINF_SUCCESS;
2135 else
2136 RTMsgError(Appliance::tr("RTVfsFsStrmNext returned %Rrc"), rc);
2137 break;
2138 }
2139
2140 if (iVerbosity > 2)
2141 RTMsgInfo(" %s %s\n", RTVfsTypeName(enmType), pszName);
2142
2143 /*
2144 * Should we process this entry?
2145 */
2146 const char *pszSuffix = RTPathSuffix(pszName);
2147 if ( pszSuffix
2148 && RTStrICmpAscii(pszSuffix, ".mf") == 0
2149 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
2150 {
2151 if (*phVfsManifest != NIL_RTVFSFILE)
2152 rc = RTMsgErrorRc(VERR_DUPLICATE, Appliance::tr("OVA contains multiple manifests! first: %s second: %s"),
2153 pStrManifestName->c_str(), pszName);
2154 else if (pszSignatureName)
2155 rc = RTMsgErrorRc(VERR_WRONG_ORDER,
2156 Appliance::tr("Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'."),
2157 pszSignatureName, pszName);
2158 else
2159 {
2160 if (iVerbosity >= 2)
2161 RTMsgInfo(Appliance::tr("Found manifest file: %s"), pszName);
2162 rc = pStrManifestName->assignNoThrow(pszName);
2163 if (RT_SUCCESS(rc))
2164 {
2165 RTVFSIOSTREAM hVfsIos = RTVfsObjToIoStream(hVfsObj);
2166 Assert(hVfsIos != NIL_RTVFSIOSTREAM);
2167 rc = RTVfsMemorizeIoStreamAsFile(hVfsIos, RTFILE_O_READ, phVfsManifest);
2168 RTVfsIoStrmRelease(hVfsIos); /* consumes stream handle. */
2169 if (RT_FAILURE(rc))
2170 rc = RTMsgErrorRc(VERR_DUPLICATE, Appliance::tr("Failed to memorize the manifest: %Rrc"), rc);
2171 }
2172 else
2173 RTMsgError(Appliance::tr("Out of memory!"));
2174 }
2175 }
2176 else if ( pszSuffix
2177 && RTStrICmpAscii(pszSuffix, ".cert") == 0
2178 && (enmType == RTVFSOBJTYPE_IO_STREAM || enmType == RTVFSOBJTYPE_FILE))
2179 {
2180 if (*phVfsOldSignature != NIL_RTVFSOBJ)
2181 rc = RTMsgErrorRc(VERR_WRONG_ORDER, Appliance::tr("Multiple signature files! (%s)"), pszName);
2182 else
2183 {
2184 if (iVerbosity >= 2)
2185 RTMsgInfo(Appliance::tr("Found existing signature file: %s"), pszName);
2186 pszSignatureName = pszName;
2187 *phVfsOldSignature = hVfsObj;
2188 pszName = NULL;
2189 hVfsObj = NIL_RTVFSOBJ;
2190 }
2191 }
2192 else if (pszSignatureName)
2193 rc = RTMsgErrorRc(VERR_WRONG_ORDER,
2194 Appliance::tr("Unsupported OVA file ordering! Signature file ('%s') as succeeded by '%s'."),
2195 pszSignatureName, pszName);
2196
2197 /*
2198 * Release the current object and string.
2199 */
2200 RTVfsObjRelease(hVfsObj);
2201 RTStrFree(pszName);
2202 if (RT_FAILURE(rc))
2203 break;
2204 }
2205
2206 /*
2207 * Complain if no manifest.
2208 */
2209 if (RT_SUCCESS(rc) && *phVfsManifest == NIL_RTVFSFILE)
2210 rc = RTMsgErrorRc(VERR_NOT_FOUND, Appliance::tr("The OVA contains no manifest and cannot be signed!"));
2211 else if (RT_SUCCESS(rc) && *phVfsOldSignature != NIL_RTVFSOBJ && !fReSign)
2212 rc = RTMsgErrorRc(VERR_ALREADY_EXISTS,
2213 Appliance::tr("The OVA is already signed ('%s')! (Use the --force option to force re-signing it.)"),
2214 pszSignatureName);
2215
2216 RTStrFree(pszSignatureName);
2217 return rc;
2218}
2219
2220
2221/**
2222 * Continues where openOvaAndGetManifestAndOldSignature() left off and writes
2223 * the signature file to the OVA.
2224 *
2225 * When @a hVfsOldSignature isn't NIL, the old signature it represent will be
2226 * replaced. The open function has already made sure there isn't anything
2227 * following the .cert file in that case.
2228 */
2229static int updateTheOvaSignature(RTVFSFSSTREAM hVfsFssOva, const char *pszOva, const char *pszSignatureName,
2230 RTVFSFILE hVfsFileSignature, RTVFSOBJ hVfsOldSignature, unsigned iVerbosity)
2231{
2232 if (iVerbosity > 1)
2233 RTMsgInfo(Appliance::tr("Writing '%s' to the OVA..."), pszSignatureName);
2234
2235 /*
2236 * Truncate the file at the old signature, if present.
2237 */
2238 int rc;
2239 if (hVfsOldSignature != NIL_RTVFSOBJ)
2240 {
2241 rc = RTZipTarFsStreamTruncate(hVfsFssOva, hVfsOldSignature, false /*fAfter*/);
2242 if (RT_FAILURE(rc))
2243 return RTMsgErrorRc(rc, Appliance::tr("RTZipTarFsStreamTruncate failed on '%s': %Rrc"), pszOva, rc);
2244 }
2245
2246 /*
2247 * Append the signature file. We have to rewind it first or
2248 * we'll end up with VERR_EOF, probably not a great idea...
2249 */
2250 rc = RTVfsFileSeek(hVfsFileSignature, 0, RTFILE_SEEK_BEGIN, NULL);
2251 if (RT_FAILURE(rc))
2252 return RTMsgErrorRc(rc, Appliance::tr("RTVfsFileSeek(hVfsFileSignature) failed: %Rrc"), rc);
2253
2254 RTVFSOBJ hVfsObj = RTVfsObjFromFile(hVfsFileSignature);
2255 rc = RTVfsFsStrmAdd(hVfsFssOva, pszSignatureName, hVfsObj, 0 /*fFlags*/);
2256 RTVfsObjRelease(hVfsObj);
2257 if (RT_FAILURE(rc))
2258 return RTMsgErrorRc(rc, Appliance::tr("RTVfsFsStrmAdd('%s') failed on '%s': %Rrc"), pszSignatureName, pszOva, rc);
2259
2260 /*
2261 * Terminate the file system stream.
2262 */
2263 rc = RTVfsFsStrmEnd(hVfsFssOva);
2264 if (RT_FAILURE(rc))
2265 return RTMsgErrorRc(rc, Appliance::tr("RTVfsFsStrmEnd failed on '%s': %Rrc"), pszOva, rc);
2266
2267 return VINF_SUCCESS;
2268}
2269
2270
2271/**
2272 * Worker for doCheckPkcs7Signature.
2273 */
2274static int doCheckPkcs7SignatureWorker(PRTCRPKCS7CONTENTINFO pContentInfo, void const *pvManifest, size_t cbManifest,
2275 unsigned iVerbosity, const char *pszTag, PRTERRINFOSTATIC pErrInfo)
2276{
2277 int rc;
2278
2279 /*
2280 * It must be signedData.
2281 */
2282 if (RTCrPkcs7ContentInfo_IsSignedData(pContentInfo))
2283 {
2284 PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData;
2285
2286 /*
2287 * Inside the signedData there must be just 'data'.
2288 */
2289 if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID))
2290 {
2291 /*
2292 * Check that things add up.
2293 */
2294 rc = RTCrPkcs7SignedData_CheckSanity(pSignedData,
2295 RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH
2296 | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT,
2297 RTErrInfoInitStatic(pErrInfo), "SD");
2298 if (RT_SUCCESS(rc))
2299 {
2300 if (iVerbosity > 2 && pszTag == NULL)
2301 RTMsgInfo(Appliance::tr(" Successfully decoded the PKCS#7/CMS signature..."));
2302
2303 /*
2304 * Check that we can verify the signed data, but skip certificate validate as
2305 * we probably don't necessarily have the correct root certs handy here.
2306 */
2307 RTTIMESPEC Now;
2308 rc = RTCrPkcs7VerifySignedDataWithExternalData(pContentInfo, RTCRPKCS7VERIFY_SD_F_TRUST_ALL_CERTS,
2309 NIL_RTCRSTORE /*hAdditionalCerts*/,
2310 NIL_RTCRSTORE /*hTrustedCerts*/,
2311 RTTimeNow(&Now),
2312 NULL /*pfnVerifyCert*/, NULL /*pvUser*/,
2313 pvManifest, cbManifest, RTErrInfoInitStatic(pErrInfo));
2314 if (RT_SUCCESS(rc))
2315 {
2316 if (iVerbosity > 1 && pszTag != NULL)
2317 RTMsgInfo(Appliance::tr(" Successfully verified the PKCS#7/CMS signature"));
2318 }
2319 else
2320 rc = RTMsgErrorRc(rc, Appliance::tr("Failed to verify the PKCS#7/CMS signature: %Rrc%RTeim"),
2321 rc, &pErrInfo->Core);
2322 }
2323 else
2324 RTMsgError(Appliance::tr("RTCrPkcs7SignedData_CheckSanity failed on PKCS#7/CMS signature: %Rrc%RTeim"),
2325 rc, &pErrInfo->Core);
2326
2327 }
2328 else
2329 rc = RTMsgErrorRc(VERR_WRONG_TYPE, Appliance::tr("PKCS#7/CMS signature inner ContentType isn't 'data' but: %s"),
2330 pSignedData->ContentInfo.ContentType.szObjId);
2331 }
2332 else
2333 rc = RTMsgErrorRc(VERR_WRONG_TYPE, Appliance::tr("PKCS#7/CMD signature is not 'signedData': %s"),
2334 pContentInfo->ContentType.szObjId);
2335 return rc;
2336}
2337
2338/**
2339 * For testing the decoding side.
2340 */
2341static int doCheckPkcs7Signature(void const *pvSignature, size_t cbSignature, PCRTCRX509CERTIFICATE pCertificate,
2342 RTCRSTORE hIntermediateCerts, void const *pvManifest, size_t cbManifest,
2343 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo)
2344{
2345 RT_NOREF(pCertificate, hIntermediateCerts);
2346
2347 RTASN1CURSORPRIMARY PrimaryCursor;
2348 RTAsn1CursorInitPrimary(&PrimaryCursor, pvSignature, (uint32_t)cbSignature, RTErrInfoInitStatic(pErrInfo),
2349 &g_RTAsn1DefaultAllocator, 0, "Signature");
2350
2351 RTCRPKCS7CONTENTINFO ContentInfo;
2352 RT_ZERO(ContentInfo);
2353 int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &ContentInfo, "CI");
2354 if (RT_SUCCESS(rc))
2355 {
2356 if (iVerbosity > 5)
2357 RTAsn1Dump(&ContentInfo.SeqCore.Asn1Core, 0 /*fFlags*/, 0 /*uLevel*/, RTStrmDumpPrintfV, g_pStdOut);
2358
2359 rc = doCheckPkcs7SignatureWorker(&ContentInfo, pvManifest, cbManifest, iVerbosity, NULL, pErrInfo);
2360 if (RT_SUCCESS(rc))
2361 {
2362 /*
2363 * Clone it and repeat. This is to catch IPRT paths assuming
2364 * that encoded data is always on hand.
2365 */
2366 RTCRPKCS7CONTENTINFO ContentInfo2;
2367 rc = RTCrPkcs7ContentInfo_Clone(&ContentInfo2, &ContentInfo, &g_RTAsn1DefaultAllocator);
2368 if (RT_SUCCESS(rc))
2369 {
2370 rc = doCheckPkcs7SignatureWorker(&ContentInfo2, pvManifest, cbManifest, iVerbosity, "cloned", pErrInfo);
2371 RTCrPkcs7ContentInfo_Delete(&ContentInfo2);
2372 }
2373 else
2374 rc = RTMsgErrorRc(rc, Appliance::tr("RTCrPkcs7ContentInfo_Clone failed: %Rrc"), rc);
2375 }
2376 }
2377 else
2378 RTMsgError(Appliance::tr("RTCrPkcs7ContentInfo_DecodeAsn1 failed to decode PKCS#7/CMS signature: %Rrc%RTemi"),
2379 rc, &pErrInfo->Core);
2380
2381 RTCrPkcs7ContentInfo_Delete(&ContentInfo);
2382 return rc;
2383}
2384
2385
2386/**
2387 * Creates a PKCS\#7 signature and appends it to the signature file in PEM
2388 * format.
2389 */
2390static int doAddPkcs7Signature(PCRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2391 unsigned cIntermediateCerts, const char **papszIntermediateCerts, RTVFSFILE hVfsFileManifest,
2392 unsigned iVerbosity, PRTERRINFOSTATIC pErrInfo, RTVFSFILE hVfsFileSignature)
2393{
2394 /*
2395 * Add a blank line, just for good measure.
2396 */
2397 int rc = RTVfsFileWrite(hVfsFileSignature, "\n", 1, NULL);
2398 if (RT_FAILURE(rc))
2399 return RTMsgErrorRc(rc, "RTVfsFileWrite/signature: %Rrc", rc);
2400
2401 /*
2402 * Read the manifest into a single memory block.
2403 */
2404 uint64_t cbManifest;
2405 rc = RTVfsFileQuerySize(hVfsFileManifest, &cbManifest);
2406 if (RT_FAILURE(rc))
2407 return RTMsgErrorRc(rc, "RTVfsFileQuerySize/manifest: %Rrc", rc);
2408 if (cbManifest > _4M)
2409 return RTMsgErrorRc(VERR_OUT_OF_RANGE, Appliance::tr("Manifest is too big: %#RX64 bytes, max 4MiB", "", cbManifest),
2410 cbManifest);
2411
2412 void *pvManifest = RTMemAllocZ(cbManifest + 1);
2413 if (!pvManifest)
2414 return RTMsgErrorRc(VERR_NO_MEMORY, Appliance::tr("Out of memory!"));
2415
2416 rc = RTVfsFileReadAt(hVfsFileManifest, 0, pvManifest, (size_t)cbManifest, NULL);
2417 if (RT_SUCCESS(rc))
2418 {
2419 /*
2420 * Load intermediate certificates.
2421 */
2422 RTCRSTORE hIntermediateCerts = NIL_RTCRSTORE;
2423 if (cIntermediateCerts)
2424 {
2425 rc = RTCrStoreCreateInMem(&hIntermediateCerts, cIntermediateCerts);
2426 if (RT_SUCCESS(rc))
2427 {
2428 for (unsigned i = 0; i < cIntermediateCerts; i++)
2429 {
2430 const char *pszFile = papszIntermediateCerts[i];
2431 rc = RTCrStoreCertAddFromFile(hIntermediateCerts, 0 /*fFlags*/, pszFile, &pErrInfo->Core);
2432 if (RT_FAILURE(rc))
2433 {
2434 RTMsgError(Appliance::tr("RTCrStoreCertAddFromFile failed on '%s': %Rrc%#RTeim"), pszFile, rc, &pErrInfo->Core);
2435 break;
2436 }
2437 }
2438 }
2439 else
2440 RTMsgError(Appliance::tr("RTCrStoreCreateInMem failed: %Rrc"), rc);
2441 }
2442 if (RT_SUCCESS(rc))
2443 {
2444 /*
2445 * Do a dry run to determin the size of the signed data.
2446 */
2447 size_t cbResult = 0;
2448 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2449 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2450 hIntermediateCerts, NULL /*pAdditionalAuthenticatedAttribs*/,
2451 NULL /*pvResult*/, &cbResult, RTErrInfoInitStatic(pErrInfo));
2452 if (rc == VERR_BUFFER_OVERFLOW)
2453 {
2454 /*
2455 * Allocate a buffer of the right size and do the real run.
2456 */
2457 void *pvResult = RTMemAllocZ(cbResult);
2458 if (pvResult)
2459 {
2460 rc = RTCrPkcs7SimpleSignSignedData(RTCRPKCS7SIGN_SD_F_DEATCHED | RTCRPKCS7SIGN_SD_F_NO_SMIME_CAP,
2461 pCertificate, hPrivateKey, pvManifest, (size_t)cbManifest, enmDigestType,
2462 hIntermediateCerts, NULL /*pAdditionalAuthenticatedAttribs*/,
2463 pvResult, &cbResult, RTErrInfoInitStatic(pErrInfo));
2464 if (RT_SUCCESS(rc))
2465 {
2466 /*
2467 * Add it to the signature file in PEM format.
2468 */
2469 rc = (int)RTCrPemWriteBlobToVfsFile(hVfsFileSignature, pvResult, cbResult, "CMS");
2470 if (RT_SUCCESS(rc))
2471 {
2472 if (iVerbosity > 1)
2473 RTMsgInfo(Appliance::tr("Created PKCS#7/CMS signature: %zu bytes, %s.", "", cbResult),
2474 cbResult, RTCrDigestTypeToName(enmDigestType));
2475 if (enmDigestType == RTDIGESTTYPE_SHA1)
2476 RTMsgWarning(Appliance::tr("Using SHA-1 instead of SHA-3 for the PKCS#7/CMS signature."));
2477
2478 /*
2479 * Try decode and verify the signature.
2480 */
2481 rc = doCheckPkcs7Signature(pvResult, cbResult, pCertificate, hIntermediateCerts,
2482 pvManifest, (size_t)cbManifest, iVerbosity, pErrInfo);
2483 }
2484 else
2485 RTMsgError(Appliance::tr("RTCrPemWriteBlobToVfsFile failed: %Rrc"), rc);
2486 }
2487 RTMemFree(pvResult);
2488 }
2489 else
2490 rc = RTMsgErrorRc(VERR_NO_MEMORY, Appliance::tr("Out of memory!"));
2491 }
2492 else
2493 RTMsgError(Appliance::tr("RTCrPkcs7SimpleSignSignedData failed: %Rrc%#RTeim"), rc, &pErrInfo->Core);
2494 }
2495 }
2496 else
2497 RTMsgError(Appliance::tr("RTVfsFileReadAt failed: %Rrc"), rc);
2498 RTMemFree(pvManifest);
2499 return rc;
2500}
2501
2502
2503/**
2504 * Performs the OVA signing, producing an in-memory cert-file.
2505 */
2506static int doTheOvaSigning(PRTCRX509CERTIFICATE pCertificate, RTCRKEY hPrivateKey, RTDIGESTTYPE enmDigestType,
2507 const char *pszManifestName, RTVFSFILE hVfsFileManifest,
2508 bool fPkcs7, unsigned cIntermediateCerts, const char **papszIntermediateCerts, unsigned iVerbosity,
2509 PRTERRINFOSTATIC pErrInfo, PRTVFSFILE phVfsFileSignature)
2510{
2511 /*
2512 * Determine the digest types, preferring SHA-256 for the OVA signature
2513 * and SHA-512 for the PKCS#7/CMS one. Try use different hashes for the two.
2514 */
2515 if (enmDigestType == RTDIGESTTYPE_UNKNOWN)
2516 {
2517 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA256, NULL))
2518 enmDigestType = RTDIGESTTYPE_SHA256;
2519 else
2520 enmDigestType = RTDIGESTTYPE_SHA1;
2521 }
2522
2523 /* Try SHA-3 for better diversity, only fall back on SHA1 if the private
2524 key doesn't have enough bits (we skip SHA2 as it has the same variants
2525 and key size requirements as SHA-3). */
2526 RTDIGESTTYPE enmPkcs7DigestType;
2527 if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA3_512, NULL))
2528 enmPkcs7DigestType = RTDIGESTTYPE_SHA3_512;
2529 else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA3_384, NULL))
2530 enmPkcs7DigestType = RTDIGESTTYPE_SHA3_384;
2531 else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA3_256, NULL))
2532 enmPkcs7DigestType = RTDIGESTTYPE_SHA3_256;
2533 else if (RTCrPkixCanCertHandleDigestType(pCertificate, RTDIGESTTYPE_SHA3_224, NULL))
2534 enmPkcs7DigestType = RTDIGESTTYPE_SHA3_224;
2535 else
2536 enmPkcs7DigestType = RTDIGESTTYPE_SHA1;
2537
2538 /*
2539 * Figure the string name for the .cert file.
2540 */
2541 const char *pszDigestType;
2542 switch (enmDigestType)
2543 {
2544 case RTDIGESTTYPE_SHA1: pszDigestType = "SHA1"; break;
2545 case RTDIGESTTYPE_SHA256: pszDigestType = "SHA256"; break;
2546 case RTDIGESTTYPE_SHA224: pszDigestType = "SHA224"; break;
2547 case RTDIGESTTYPE_SHA512: pszDigestType = "SHA512"; break;
2548 default:
2549 return RTMsgErrorRc(VERR_INVALID_PARAMETER,
2550 Appliance::tr("Unsupported digest type: %s"), RTCrDigestTypeToName(enmDigestType));
2551 }
2552
2553 /*
2554 * Digest the manifest file.
2555 */
2556 RTCRDIGEST hDigest = NIL_RTCRDIGEST;
2557 int rc = RTCrDigestCreateByType(&hDigest, enmDigestType);
2558 if (RT_FAILURE(rc))
2559 return RTMsgErrorRc(rc, Appliance::tr("Failed to create digest for %s: %Rrc"), RTCrDigestTypeToName(enmDigestType), rc);
2560
2561 rc = RTCrDigestUpdateFromVfsFile(hDigest, hVfsFileManifest, true /*fRewindFile*/);
2562 if (RT_SUCCESS(rc))
2563 rc = RTCrDigestFinal(hDigest, NULL, 0);
2564 if (RT_SUCCESS(rc))
2565 {
2566 /*
2567 * Sign the digest. Two passes, first to figure the signature size, the
2568 * second to do the actual signing.
2569 */
2570 PCRTASN1OBJID const pAlgorithm = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Algorithm;
2571 PCRTASN1DYNTYPE const pAlgoParams = &pCertificate->TbsCertificate.SubjectPublicKeyInfo.Algorithm.Parameters;
2572 size_t cbSignature = 0;
2573 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0 /*fFlags*/,
2574 NULL /*pvSignature*/, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2575 if (rc == VERR_BUFFER_OVERFLOW)
2576 {
2577 void *pvSignature = RTMemAllocZ(cbSignature);
2578 if (pvSignature)
2579 {
2580 rc = RTCrPkixPubKeySignDigest(pAlgorithm, hPrivateKey, pAlgoParams, hDigest, 0,
2581 pvSignature, &cbSignature, RTErrInfoInitStatic(pErrInfo));
2582 if (RT_SUCCESS(rc))
2583 {
2584 if (iVerbosity > 1)
2585 RTMsgInfo(Appliance::tr("Created OVA signature: %zu bytes, %s", "", cbSignature), cbSignature,
2586 RTCrDigestTypeToName(enmDigestType));
2587
2588 /*
2589 * Verify the signature using the certificate to make sure we've
2590 * been given the right private key.
2591 */
2592 rc = RTCrPkixPubKeyVerifySignedDigestByCertPubKeyInfo(&pCertificate->TbsCertificate.SubjectPublicKeyInfo,
2593 pvSignature, cbSignature, hDigest,
2594 RTErrInfoInitStatic(pErrInfo));
2595 if (RT_SUCCESS(rc))
2596 {
2597 if (iVerbosity > 2)
2598 RTMsgInfo(Appliance::tr(" Successfully decoded and verified the OVA signature.\n"));
2599
2600 /*
2601 * Create the output file.
2602 */
2603 RTVFSFILE hVfsFileSignature;
2604 rc = RTVfsMemFileCreate(NIL_RTVFSIOSTREAM, _8K, &hVfsFileSignature);
2605 if (RT_SUCCESS(rc))
2606 {
2607 rc = (int)RTVfsFilePrintf(hVfsFileSignature, "%s(%s) = %#.*Rhxs\n\n",
2608 pszDigestType, pszManifestName, cbSignature, pvSignature);
2609 if (RT_SUCCESS(rc))
2610 {
2611 rc = (int)RTCrX509Certificate_WriteToVfsFile(hVfsFileSignature, pCertificate,
2612 RTErrInfoInitStatic(pErrInfo));
2613 if (RT_SUCCESS(rc))
2614 {
2615 if (fPkcs7)
2616 rc = doAddPkcs7Signature(pCertificate, hPrivateKey, enmPkcs7DigestType,
2617 cIntermediateCerts, papszIntermediateCerts, hVfsFileManifest,
2618 iVerbosity, pErrInfo, hVfsFileSignature);
2619 if (RT_SUCCESS(rc))
2620 {
2621 /*
2622 * Success.
2623 */
2624 *phVfsFileSignature = hVfsFileSignature;
2625 hVfsFileSignature = NIL_RTVFSFILE;
2626 }
2627 }
2628 else
2629 RTMsgError(Appliance::tr("Failed to write certificate to signature file: %Rrc%#RTeim"),
2630 rc, &pErrInfo->Core);
2631 }
2632 else
2633 RTMsgError(Appliance::tr("Failed to produce signature file: %Rrc"), rc);
2634 RTVfsFileRelease(hVfsFileSignature);
2635 }
2636 else
2637 RTMsgError(Appliance::tr("RTVfsMemFileCreate failed: %Rrc"), rc);
2638 }
2639 else
2640 RTMsgError(Appliance::tr("Encountered a problem when validating the signature we just created: %Rrc%#RTeim\n"
2641 "Please make sure the certificate and private key matches."),
2642 rc, &pErrInfo->Core);
2643 }
2644 else
2645 RTMsgError(Appliance::tr("2nd RTCrPkixPubKeySignDigest call failed: %Rrc%#RTeim"), rc, pErrInfo->Core);
2646 RTMemFree(pvSignature);
2647 }
2648 else
2649 rc = RTMsgErrorRc(VERR_NO_MEMORY, Appliance::tr("Out of memory!"));
2650 }
2651 else
2652 RTMsgError(Appliance::tr("RTCrPkixPubKeySignDigest failed: %Rrc%#RTeim"), rc, pErrInfo->Core);
2653 }
2654 else
2655 RTMsgError(Appliance::tr("Failed to create digest %s: %Rrc"), RTCrDigestTypeToName(enmDigestType), rc);
2656 RTCrDigestRelease(hDigest);
2657 return rc;
2658}
2659
2660
2661/**
2662 * Handles the 'ovasign' command.
2663 */
2664RTEXITCODE handleSignAppliance(HandlerArg *arg)
2665{
2666 /*
2667 * Parse arguments.
2668 */
2669 static const RTGETOPTDEF s_aOptions[] =
2670 {
2671 { "--certificate", 'c', RTGETOPT_REQ_STRING },
2672 { "--private-key", 'k', RTGETOPT_REQ_STRING },
2673 { "--private-key-password", 'p', RTGETOPT_REQ_STRING },
2674 { "--private-key-password-file",'P', RTGETOPT_REQ_STRING },
2675 { "--digest-type", 'd', RTGETOPT_REQ_STRING },
2676 { "--pkcs7", '7', RTGETOPT_REQ_NOTHING },
2677 { "--cms", '7', RTGETOPT_REQ_NOTHING },
2678 { "--no-pkcs7", 'n', RTGETOPT_REQ_NOTHING },
2679 { "--no-cms", 'n', RTGETOPT_REQ_NOTHING },
2680 { "--intermediate-cert-file", 'i', RTGETOPT_REQ_STRING },
2681 { "--force", 'f', RTGETOPT_REQ_NOTHING },
2682 { "--verbose", 'v', RTGETOPT_REQ_NOTHING },
2683 { "--quiet", 'q', RTGETOPT_REQ_NOTHING },
2684 { "--dry-run", 'D', RTGETOPT_REQ_NOTHING },
2685 };
2686
2687 RTGETOPTSTATE GetState;
2688 int rc = RTGetOptInit(&GetState, arg->argc, arg->argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0);
2689 AssertRCReturn(rc, RTEXITCODE_FAILURE);
2690
2691 const char *pszOva = NULL;
2692 const char *pszCertificate = NULL;
2693 const char *pszPrivateKey = NULL;
2694 Utf8Str strPrivateKeyPassword;
2695 RTDIGESTTYPE enmDigestType = RTDIGESTTYPE_UNKNOWN;
2696 bool fPkcs7 = true;
2697 unsigned cIntermediateCerts = 0;
2698 const char *apszIntermediateCerts[32];
2699 bool fReSign = false;
2700 unsigned iVerbosity = 1;
2701 bool fDryRun = false;
2702
2703 int c;
2704 RTGETOPTUNION ValueUnion;
2705 while ((c = RTGetOpt(&GetState, &ValueUnion)) != 0)
2706 {
2707 switch (c)
2708 {
2709 case 'c':
2710 pszCertificate = ValueUnion.psz;
2711 break;
2712
2713 case 'k':
2714 pszPrivateKey = ValueUnion.psz;
2715 break;
2716
2717 case 'p':
2718 if (strPrivateKeyPassword.isNotEmpty())
2719 RTMsgWarning(Appliance::tr("Password is given more than once."));
2720 strPrivateKeyPassword = ValueUnion.psz;
2721 break;
2722
2723 case 'P':
2724 {
2725 if (strPrivateKeyPassword.isNotEmpty())
2726 RTMsgWarning(Appliance::tr("Password is given more than once."));
2727 RTEXITCODE rcExit = readPasswordFile(ValueUnion.psz, &strPrivateKeyPassword);
2728 if (rcExit == RTEXITCODE_SUCCESS)
2729 break;
2730 return rcExit;
2731 }
2732
2733 case 'd':
2734 if ( RTStrICmp(ValueUnion.psz, "sha1") == 0
2735 || RTStrICmp(ValueUnion.psz, "sha-1") == 0)
2736 enmDigestType = RTDIGESTTYPE_SHA1;
2737 else if ( RTStrICmp(ValueUnion.psz, "sha256") == 0
2738 || RTStrICmp(ValueUnion.psz, "sha-256") == 0)
2739 enmDigestType = RTDIGESTTYPE_SHA256;
2740 else if ( RTStrICmp(ValueUnion.psz, "sha512") == 0
2741 || RTStrICmp(ValueUnion.psz, "sha-512") == 0)
2742 enmDigestType = RTDIGESTTYPE_SHA512;
2743 else
2744 return RTMsgErrorExitFailure(Appliance::tr("Unknown digest type: %s"), ValueUnion.psz);
2745 break;
2746
2747 case '7':
2748 fPkcs7 = true;
2749 break;
2750
2751 case 'n':
2752 fPkcs7 = false;
2753 break;
2754
2755 case 'i':
2756 if (cIntermediateCerts >= RT_ELEMENTS(apszIntermediateCerts))
2757 return RTMsgErrorExitFailure(Appliance::tr("Too many intermediate certificates: max %zu"),
2758 RT_ELEMENTS(apszIntermediateCerts));
2759 apszIntermediateCerts[cIntermediateCerts++] = ValueUnion.psz;
2760 fPkcs7 = true;
2761 break;
2762
2763 case 'f':
2764 fReSign = true;
2765 break;
2766
2767 case 'v':
2768 iVerbosity++;
2769 break;
2770
2771 case 'q':
2772 iVerbosity = 0;
2773 break;
2774
2775 case 'D':
2776 fDryRun = true;
2777 break;
2778
2779 case VINF_GETOPT_NOT_OPTION:
2780 if (!pszOva)
2781 {
2782 pszOva = ValueUnion.psz;
2783 break;
2784 }
2785 RT_FALL_THRU();
2786 default:
2787 return errorGetOpt(c, &ValueUnion);
2788 }
2789 }
2790
2791 /* Required paramaters: */
2792 if (!pszOva || !*pszOva)
2793 return RTMsgErrorExit(RTEXITCODE_SYNTAX, Appliance::tr("No OVA file was specified!"));
2794 if (!pszCertificate || !*pszCertificate)
2795 return RTMsgErrorExit(RTEXITCODE_SYNTAX, Appliance::tr("No signing certificate (--certificate=<file>) was specified!"));
2796 if (!pszPrivateKey || !*pszPrivateKey)
2797 return RTMsgErrorExit(RTEXITCODE_SYNTAX, Appliance::tr("No signing private key (--private-key=<file>) was specified!"));
2798
2799 /* Check that input files exists before we commence: */
2800 if (!RTFileExists(pszOva))
2801 return RTMsgErrorExitFailure(Appliance::tr("The specified OVA file was not found: %s"), pszOva);
2802 if (!RTFileExists(pszCertificate))
2803 return RTMsgErrorExitFailure(Appliance::tr("The specified certificate file was not found: %s"), pszCertificate);
2804 if (!RTFileExists(pszPrivateKey))
2805 return RTMsgErrorExitFailure(Appliance::tr("The specified private key file was not found: %s"), pszPrivateKey);
2806
2807 /*
2808 * Open the OVA, read the manifest and look for any existing signature.
2809 */
2810 RTVFSFSSTREAM hVfsFssOva = NIL_RTVFSFSSTREAM;
2811 RTVFSOBJ hVfsOldSignature = NIL_RTVFSOBJ;
2812 RTVFSFILE hVfsFileManifest = NIL_RTVFSFILE;
2813 Utf8Str strManifestName;
2814 rc = openOvaAndGetManifestAndOldSignature(pszOva, iVerbosity, fReSign,
2815 &hVfsFssOva, &strManifestName, &hVfsFileManifest, &hVfsOldSignature);
2816 if (RT_SUCCESS(rc))
2817 {
2818 /*
2819 * Read the certificate and private key.
2820 */
2821 RTERRINFOSTATIC ErrInfo;
2822 RTCRX509CERTIFICATE Certificate;
2823 rc = RTCrX509Certificate_ReadFromFile(&Certificate, pszCertificate, 0, &g_RTAsn1DefaultAllocator,
2824 RTErrInfoInitStatic(&ErrInfo));
2825 if (RT_FAILURE(rc))
2826 return RTMsgErrorExitFailure(Appliance::tr("Error reading certificate from '%s': %Rrc%#RTeim"),
2827 pszCertificate, rc, &ErrInfo.Core);
2828
2829 RTCRKEY hPrivateKey = NIL_RTCRKEY;
2830 rc = RTCrKeyCreateFromFile(&hPrivateKey, 0 /*fFlags*/, pszPrivateKey, strPrivateKeyPassword.c_str(),
2831 RTErrInfoInitStatic(&ErrInfo));
2832 if (RT_SUCCESS(rc))
2833 {
2834 if (iVerbosity > 1)
2835 RTMsgInfo(Appliance::tr("Successfully read the certificate and private key."));
2836
2837 /*
2838 * Do the signing and create the signature file.
2839 */
2840 RTVFSFILE hVfsFileSignature = NIL_RTVFSFILE;
2841 rc = doTheOvaSigning(&Certificate, hPrivateKey, enmDigestType, strManifestName.c_str(), hVfsFileManifest,
2842 fPkcs7, cIntermediateCerts, apszIntermediateCerts, iVerbosity, &ErrInfo, &hVfsFileSignature);
2843
2844 /*
2845 * Construct the signature filename:
2846 */
2847 if (RT_SUCCESS(rc))
2848 {
2849 Utf8Str strSignatureName;
2850 rc = strSignatureName.assignNoThrow(strManifestName);
2851 if (RT_SUCCESS(rc))
2852 rc = strSignatureName.stripSuffix().appendNoThrow(".cert");
2853 if (RT_SUCCESS(rc) && !fDryRun)
2854 {
2855 /*
2856 * Update the OVA.
2857 */
2858 rc = updateTheOvaSignature(hVfsFssOva, pszOva, strSignatureName.c_str(),
2859 hVfsFileSignature, hVfsOldSignature, iVerbosity);
2860 if (RT_SUCCESS(rc) && iVerbosity > 0)
2861 RTMsgInfo(Appliance::tr("Successfully signed '%s'."), pszOva);
2862 }
2863 }
2864 RTCrKeyRelease(hPrivateKey);
2865 }
2866 else
2867 RTPrintf(Appliance::tr("Error reading the private key from %s: %Rrc%#RTeim"), pszPrivateKey, rc, &ErrInfo.Core);
2868 RTCrX509Certificate_Delete(&Certificate);
2869 }
2870
2871 RTVfsObjRelease(hVfsOldSignature);
2872 RTVfsFileRelease(hVfsFileManifest);
2873 RTVfsFsStrmRelease(hVfsFssOva);
2874
2875 return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE;
2876}
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