VirtualBox

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

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

Main: bugref:1909: Added translation into VBoxManage excluding built-in docbook help. Fixed translation marks in the VBoxManage

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