VirtualBox

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

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

VBoxManage: VC++ 19.2 build adjustment. bugref:8489

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette