VirtualBox

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

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

bugref:9781. Added the placeholder @@VBOX_COND_GUEST_VERSION[>(required version)]@@. Updated the templates. Removed the obsolete function getGuestOSConditional().

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