VirtualBox

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

Last change on this file since 89777 was 89777, checked in by vboxsync, 3 years ago

bugref:10036. Added API functions for getting Vnic attachments list and Vnic information from OCI.

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