VirtualBox

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

Last change on this file since 84311 was 84311, checked in by vboxsync, 5 years ago

VBoxManage/signova: Fixed a PKCS7/CMS signing bug. Validate more of the PKCS7/CMS signature after we're done as it's easier to deal with here than in VBoxSVC. bugref:9699

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