VirtualBox

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

Last change on this file since 86186 was 85631, checked in by vboxsync, 4 years ago

VBoxManage/signova: Use SHA-3 for the PKCS#7/CMS signature. Fixed newline/space separator confusion. bugref:9699

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