VirtualBox

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

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

Main: bugref:1909: Added translation marks around messages of VBoxManage output

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