VirtualBox

source: vbox/trunk/src/VBox/Main/src-server/UnattendedScript.cpp

Last change on this file was 107906, checked in by vboxsync, 5 weeks ago

Main/Unattended: Add AVOID_UPDATES_OVER_NETWORK variable for expressions and disable any fetching of packages from remote repositories in the OL post install script if indicated to avoid network updates (hopefully fixes unattened testcases timing out during the post install phase because the host proxies are not set up correctly)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 39.4 KB
Line 
1/* $Id: UnattendedScript.cpp 107906 2025-01-23 12:30:36Z vboxsync $ */
2/** @file
3 * Classes for reading/parsing/saving scripts for unattended installation.
4 */
5
6/*
7 * Copyright (C) 2006-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_MAIN_UNATTENDED
33#include "LoggingNew.h"
34#include "VirtualBoxBase.h"
35#include "AutoCaller.h"
36#include <VBox/com/ErrorInfo.h>
37
38#include "UnattendedScript.h"
39#include "UnattendedImpl.h"
40
41#include <iprt/err.h>
42#include <iprt/md5.h>
43#include <iprt/sha.h>
44
45#include <iprt/ctype.h>
46#include <iprt/file.h>
47#include <iprt/rand.h> /* For RTCrShaCrypt salt generation. */
48#include <iprt/vfs.h>
49#include <iprt/getopt.h>
50#include <iprt/path.h>
51
52#include <iprt/crypto/shacrypt.h>
53
54using namespace std;
55
56#ifdef VBOX_WITH_UNATTENDED
57
58
59/*********************************************************************************************************************************
60* Defined Constants And Macros *
61*********************************************************************************************************************************/
62static const char g_szPrefix[] = "@@VBOX_";
63static const char g_szPrefixInsert[] = "@@VBOX_INSERT";
64static const char g_szPrefixInsertXxx[] = "@@VBOX_INSERT_";
65static const char g_szPrefixInsertExpr[] = "@@VBOX_INSERT[";
66static const char g_szPrefixCond[] = "@@VBOX_COND";
67static const char g_szPrefixCondXxx[] = "@@VBOX_COND_";
68static const char g_szPrefixCondExpr[] = "@@VBOX_COND[";
69static const char g_szPrefixCondElse[] = "@@VBOX_COND_ELSE@@";
70static const char g_szPrefixCondEnd[] = "@@VBOX_COND_END@@";
71static const char g_szPrefixSplitter[] = "@@VBOX_SPLITTER";
72
73
74/*********************************************************************************************************************************
75* UnattendedScriptTemplate Implementation *
76*********************************************************************************************************************************/
77
78UnattendedScriptTemplate::UnattendedScriptTemplate(Unattended *pUnattended, const char *pszDefaultTemplateFilename,
79 const char *pszDefaultFilename)
80 : BaseTextScript(pUnattended, pszDefaultTemplateFilename, pszDefaultFilename), mpUnattended(pUnattended)
81{
82}
83
84HRESULT UnattendedScriptTemplate::saveToString(Utf8Str &rStrDst)
85{
86 RTEXPREVAL hEvaluator = NIL_RTEXPREVAL;
87 int vrc = RTExprEvalCreate(&hEvaluator, 0, "unattended", this, UnattendedScriptTemplate::queryVariableForExpr);
88 AssertRCReturn(vrc, mpSetError->setErrorVrc(vrc));
89
90 struct
91 {
92 bool fSavedOutputting;
93 } aConds[8];
94 unsigned cConds = 0;
95 bool fOutputting = true;
96 HRESULT hrc = E_FAIL;
97 size_t offTemplate = 0;
98 size_t cchTemplate = mStrScriptFullContent.length();
99 rStrDst.setNull();
100 for (;;)
101 {
102 /*
103 * Find the next placeholder and add any text before it to the output.
104 */
105 size_t offPlaceholder = mStrScriptFullContent.find(g_szPrefix, offTemplate);
106 size_t cchToCopy = offPlaceholder != RTCString::npos ? offPlaceholder - offTemplate : cchTemplate - offTemplate;
107 if (cchToCopy > 0)
108 {
109 if (fOutputting)
110 {
111 try
112 {
113 rStrDst.append(mStrScriptFullContent, offTemplate , cchToCopy);
114 }
115 catch (std::bad_alloc &)
116 {
117 hrc = E_OUTOFMEMORY;
118 break;
119 }
120 }
121 offTemplate += cchToCopy;
122 }
123
124 /*
125 * Process placeholder.
126 */
127 if (offPlaceholder != RTCString::npos)
128 {
129 /*
130 * First we must find the end of the placeholder string.
131 */
132 size_t const cchMaxPlaceholder = RT_MIN(cchTemplate - offPlaceholder, _1K);
133 const char *pszPlaceholder = mStrScriptFullContent.c_str() + offPlaceholder;
134 size_t cchPlaceholder = sizeof(g_szPrefix) - 1;
135 char ch;
136 while ( cchPlaceholder < cchMaxPlaceholder
137 && (ch = pszPlaceholder[cchPlaceholder]) != '\0'
138 && (RT_C_IS_PRINT(ch) || RT_C_IS_SPACE(ch))
139 && ch != '@')
140 cchPlaceholder++;
141
142 if ( offPlaceholder + cchPlaceholder < cchTemplate
143 && pszPlaceholder[cchPlaceholder] == '@')
144 {
145 cchPlaceholder++;
146 if ( offPlaceholder + cchPlaceholder < cchTemplate
147 && pszPlaceholder[cchPlaceholder] == '@')
148 cchPlaceholder++;
149 }
150
151 if ( pszPlaceholder[cchPlaceholder - 1] != '@'
152 || pszPlaceholder[cchPlaceholder - 2] != '@'
153 || ( strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsert)) != 0
154 && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCond)) != 0
155 && strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) != 0 ) )
156 {
157 hrc = mpSetError->setError(E_FAIL, tr("Malformed or too long template placeholder '%.*s'"),
158 cchPlaceholder, pszPlaceholder);
159 break;
160 }
161
162 offTemplate += cchPlaceholder;
163
164 /*
165 * @@VBOX_INSERT_XXX@@:
166 */
167 if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertXxx)) == 0)
168 {
169 /*
170 * Get the placeholder value and add it to the output.
171 */
172 RTCString strValue;
173 hrc = getReplacement(pszPlaceholder, cchPlaceholder, fOutputting, strValue);
174 if (SUCCEEDED(hrc))
175 {
176 if (fOutputting)
177 {
178 try
179 {
180 rStrDst.append(strValue);
181 }
182 catch (std::bad_alloc &)
183 {
184 hrc = E_OUTOFMEMORY;
185 break;
186 }
187 }
188 }
189 else
190 break;
191 }
192 /*
193 * @@VBOX_INSERT[expr]@@:
194 * @@VBOX_INSERT[expr]SH@@:
195 * @@VBOX_INSERT[expr]ELEMENT@@:
196 * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
197 */
198 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixInsertExpr)) == 0)
199 {
200 /*
201 * Get the placeholder value and add it to the output.
202 */
203 char *pszValue = NULL;
204 hrc = getReplacementForExpr(hEvaluator, pszPlaceholder, cchPlaceholder, fOutputting, &pszValue);
205 if (SUCCEEDED(hrc))
206 {
207 if (fOutputting && pszValue)
208 {
209 try
210 {
211 rStrDst.append(pszValue);
212 }
213 catch (std::bad_alloc &)
214 {
215 hrc = E_OUTOFMEMORY;
216 break;
217 }
218 }
219 RTStrFree(pszValue);
220 }
221 else
222 break;
223 }
224 /*
225 * @@VBOX_COND_END@@: Pop one item of the conditional stack.
226 */
227 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondEnd)) == 0)
228 {
229 if (cConds > 0)
230 {
231 cConds--;
232 fOutputting = aConds[cConds].fSavedOutputting;
233 }
234 else
235 {
236 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
237 tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
238 g_szPrefixCondEnd, offPlaceholder, offPlaceholder);
239 break;
240 }
241 }
242 /*
243 * @@VBOX_COND_ELSE@@: Flip the output setting of the current condition.
244 */
245 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondElse)) == 0)
246 {
247 if (cConds > 0)
248 fOutputting = !fOutputting;
249 else
250 {
251 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
252 tr("%s without @@VBOX_COND_XXX@@ at offset %zu (%#zx)"),
253 g_szPrefixCondElse, offPlaceholder, offPlaceholder);
254 break;
255 }
256 }
257 /*
258 * @@VBOX_COND_XXX@@: Push the previous outputting state and combine it with the
259 * one from the condition.
260 */
261 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondXxx)) == 0)
262 {
263 if (cConds + 1 < RT_ELEMENTS(aConds))
264 {
265 aConds[cConds].fSavedOutputting = fOutputting;
266 bool fNewOutputting = fOutputting;
267 hrc = getConditional(pszPlaceholder, cchPlaceholder, &fNewOutputting);
268 if (SUCCEEDED(hrc))
269 fOutputting = fOutputting && fNewOutputting;
270 else
271 break;
272 cConds++;
273 }
274 else
275 {
276 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
277 tr("Too deep conditional nesting at offset %zu (%#zx)"),
278 offPlaceholder, offPlaceholder);
279 break;
280 }
281 }
282 /*
283 * @@VBOX_COND[expr]@@: Push the previous outputting state and combine it with the
284 * one from the condition.
285 */
286 else if (strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixCondExpr)) == 0)
287 {
288 if (cConds + 1 < RT_ELEMENTS(aConds))
289 {
290 aConds[cConds].fSavedOutputting = fOutputting;
291 bool fNewOutputting = fOutputting;
292 hrc = resolveConditionalExpr(hEvaluator, pszPlaceholder, cchPlaceholder, &fNewOutputting);
293 if (SUCCEEDED(hrc))
294 fOutputting = fOutputting && fNewOutputting;
295 else
296 break;
297 cConds++;
298 }
299 else
300 {
301 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR,
302 tr("Too deep conditional nesting at offset %zu (%#zx)"),
303 offPlaceholder, offPlaceholder);
304 break;
305 }
306 }
307 /*
308 * @@VBOX_SPLITTER_START/END[filename]@@: Ignored in this pass.
309 */
310 else
311 {
312 Assert(strncmp(pszPlaceholder, RT_STR_TUPLE(g_szPrefixSplitter)) == 0);
313 if (fOutputting)
314 {
315 try
316 {
317 rStrDst.append(pszPlaceholder, cchPlaceholder);
318 }
319 catch (std::bad_alloc &)
320 {
321 hrc = E_OUTOFMEMORY;
322 break;
323 }
324 }
325 }
326 }
327
328 /*
329 * Done?
330 */
331 if (offTemplate >= cchTemplate)
332 {
333 if (cConds == 0)
334 {
335 RTExprEvalRelease(hEvaluator);
336 return S_OK;
337 }
338 if (cConds == 1)
339 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing @@VBOX_COND_END@@"));
340 else
341 hrc = mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Missing %u @@VBOX_COND_END@@"), cConds);
342 break;
343 }
344 }
345
346 /* failed */
347 rStrDst.setNull();
348 RTExprEvalRelease(hEvaluator);
349 return hrc;
350}
351
352HRESULT UnattendedScriptTemplate::getReplacement(const char *pachPlaceholder, size_t cchPlaceholder,
353 bool fOutputting, RTCString &rValue)
354{
355 /*
356 * Check for an escaping suffix. Drop the '@@'.
357 */
358 kEvalEscaping_T enmEscaping;
359#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
360 ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
361 && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
362 if (PLACEHOLDER_ENDS_WITH("_SH@@"))
363 {
364 cchPlaceholder -= 3 + 2;
365 enmEscaping = kValueEscaping_Bourne;
366 }
367 else if (PLACEHOLDER_ENDS_WITH("_ELEMENT@@"))
368 {
369 cchPlaceholder -= 8 + 2;
370 enmEscaping = kValueEscaping_XML_Element;
371 }
372 else if (PLACEHOLDER_ENDS_WITH("_ATTRIB_DQ@@"))
373 {
374 cchPlaceholder -= 10 + 2;
375 enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
376 }
377 else
378 {
379 Assert(PLACEHOLDER_ENDS_WITH("@@"));
380 cchPlaceholder -= 2;
381 enmEscaping = kValueEscaping_None;
382 }
383#undef PLACEHOLDER_ENDS_WITH
384
385 /*
386 * Resolve and escape the value.
387 */
388 HRESULT hrc;
389 try
390 {
391 Utf8Str strTmp;
392 const char *pszReadOnlyValue = NULL;
393 int vrc = queryVariable(pachPlaceholder + sizeof(g_szPrefixInsertXxx) - 1,
394 cchPlaceholder - sizeof(g_szPrefixInsertXxx) + 1,
395 strTmp, fOutputting ? &pszReadOnlyValue : NULL);
396 if (RT_SUCCESS(vrc))
397 {
398 if (fOutputting)
399 {
400 Assert(pszReadOnlyValue != NULL);
401 switch (enmEscaping)
402 {
403 case kValueEscaping_None:
404 rValue = pszReadOnlyValue;
405 return S_OK;
406
407 case kValueEscaping_Bourne:
408 case kValueEscaping_XML_Element:
409 case kValueEscaping_XML_Attribute_Double_Quotes:
410 {
411 switch (enmEscaping)
412 {
413 case kValueEscaping_Bourne:
414 {
415 const char * const papszArgs[2] = { pszReadOnlyValue, NULL };
416 char *pszEscaped = NULL;
417 vrc = RTGetOptArgvToString(&pszEscaped, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
418 if (RT_SUCCESS(vrc))
419 {
420 try
421 {
422 rValue = pszEscaped;
423 RTStrFree(pszEscaped);
424 return S_OK;
425 }
426 catch (std::bad_alloc &)
427 {
428 hrc = E_OUTOFMEMORY;
429 }
430 RTStrFree(pszEscaped);
431 }
432 else
433 hrc = mpSetError->setErrorVrc(vrc);
434 break;
435 }
436
437 case kValueEscaping_XML_Element:
438 rValue.printf("%RMes", pszReadOnlyValue);
439 return S_OK;
440
441 case kValueEscaping_XML_Attribute_Double_Quotes:
442 {
443 RTCString strTmp2;
444 strTmp2.printf("%RMas", pszReadOnlyValue);
445 rValue = RTCString(strTmp2, 1, strTmp2.length() - 2);
446 return S_OK;
447 }
448
449 default:
450 hrc = E_FAIL;
451 break;
452 }
453 break;
454 }
455
456 default:
457 AssertFailedStmt(hrc = E_FAIL);
458 break;
459 }
460 }
461 else
462 hrc = S_OK;
463 }
464 else
465 hrc = E_FAIL;
466 }
467 catch (std::bad_alloc &)
468 {
469 hrc = E_OUTOFMEMORY;
470 }
471 rValue.setNull();
472 return hrc;
473}
474
475HRESULT UnattendedScriptTemplate::getReplacementForExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder, size_t cchPlaceholder,
476 bool fOutputting, char **ppszValue) RT_NOEXCEPT
477{
478 /*
479 * Process the tail of the placeholder to figure out the escaping rules.
480 *
481 * @@VBOX_INSERT[expr]@@:
482 * @@VBOX_INSERT[expr]SH@@:
483 * @@VBOX_INSERT[expr]ELEMENT@@:
484 * @@VBOX_INSERT[expr]ATTRIB_DQ@@:
485 */
486 kEvalEscaping_T enmEscaping;
487#define PLACEHOLDER_ENDS_WITH(a_szSuffix) \
488 ( cchPlaceholder > sizeof(a_szSuffix) - 1U \
489 && memcmp(&pachPlaceholder[cchPlaceholder - sizeof(a_szSuffix) + 1U], a_szSuffix, sizeof(a_szSuffix) - 1U) == 0)
490 if (PLACEHOLDER_ENDS_WITH("]SH@@"))
491 {
492 cchPlaceholder -= sizeof("]SH@@") - 1;
493 enmEscaping = kValueEscaping_Bourne;
494 }
495 else if (PLACEHOLDER_ENDS_WITH("]ELEMENT@@"))
496 {
497 cchPlaceholder -= sizeof("]ELEMENT@@") - 1;
498 enmEscaping = kValueEscaping_XML_Element;
499 }
500 else if (PLACEHOLDER_ENDS_WITH("]ATTRIB_DQ@@"))
501 {
502 cchPlaceholder -= sizeof("]ATTRIB_DQ@@") - 1;
503 enmEscaping = kValueEscaping_XML_Attribute_Double_Quotes;
504 }
505 else if (PLACEHOLDER_ENDS_WITH("]@@"))
506 {
507 cchPlaceholder -= sizeof("]@@") - 1;
508 enmEscaping = kValueEscaping_None;
509 }
510 else
511 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_INSERT[expr]@@: Missing ']' (%.*s)"),
512 cchPlaceholder, pachPlaceholder);
513#undef PLACEHOLDER_ENDS_WITH
514
515 /* The placeholder prefix length. The expression is from cchPrefix to cchPlaceholder. */
516 size_t const cchPrefix = sizeof(g_szPrefixInsertExpr) - 1;
517 Assert(pachPlaceholder[cchPrefix - 1] == '[');
518
519 /*
520 * Evaluate the expression. We do this regardless of fOutput for now.
521 */
522 RTERRINFOSTATIC ErrInfo;
523 char *pszValue = NULL;
524 int vrc = RTExprEvalToString(hEvaluator, &pachPlaceholder[cchPrefix], cchPlaceholder - cchPrefix, &pszValue,
525 RTErrInfoInitStatic(&ErrInfo));
526 LogFlowFunc(("RTExprEvalToString(%.*s) -> %Rrc pszValue=%s\n",
527 cchPlaceholder - cchPrefix, &pachPlaceholder[cchPrefix], vrc, pszValue));
528 if (RT_SUCCESS(vrc))
529 {
530 if (fOutputting)
531 {
532 switch (enmEscaping)
533 {
534 case kValueEscaping_None:
535 *ppszValue = pszValue;
536 pszValue = NULL;
537 break;
538
539 case kValueEscaping_Bourne:
540 {
541 const char * const papszArgs[2] = { pszValue, NULL };
542 vrc = RTGetOptArgvToString(ppszValue, papszArgs, RTGETOPTARGV_CNV_QUOTE_BOURNE_SH);
543 break;
544 }
545
546 case kValueEscaping_XML_Element:
547 vrc = RTStrAPrintf(ppszValue, "%RMes", pszValue);
548 break;
549
550 case kValueEscaping_XML_Attribute_Double_Quotes:
551 vrc = RTStrAPrintf(ppszValue, "%RMas", pszValue);
552 if (RT_SUCCESS(vrc))
553 {
554 /* drop the quotes */
555 char *pszRet = *ppszValue;
556 size_t const cchRet = strlen(pszRet) - 2;
557 memmove(pszRet, &pszRet[1], cchRet);
558 pszRet[cchRet] = '\0';
559 }
560 break;
561
562 default:
563 AssertFailedStmt(vrc = VERR_IPE_NOT_REACHED_DEFAULT_CASE);
564 break;
565 }
566 RTStrFree(pszValue);
567 if (RT_FAILURE(vrc))
568 return mpSetError->setErrorVrc(vrc);
569 }
570 else
571 {
572 *ppszValue = NULL;
573 RTStrFree(pszValue);
574 }
575 }
576 else
577 return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
578 cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
579 return S_OK;
580}
581
582HRESULT UnattendedScriptTemplate::resolveConditionalExpr(RTEXPREVAL hEvaluator, const char *pachPlaceholder,
583 size_t cchPlaceholder, bool *pfOutputting) RT_NOEXCEPT
584{
585 /*
586 * Check the placeholder tail: @@VBOX_COND[expr]@@
587 */
588 static const char s_szTail[] = "]@@";
589 if (memcmp(&pachPlaceholder[cchPlaceholder - sizeof(s_szTail) + 1], RT_STR_TUPLE(s_szTail)) != 0)
590 return mpSetError->setErrorBoth(E_FAIL, VERR_PARSE_ERROR, tr("Malformed @@VBOX_COND[expr]@@: Missing ']' (%.*s)"),
591 cchPlaceholder, pachPlaceholder);
592 Assert(pachPlaceholder[sizeof(g_szPrefixCondExpr) - 2 ] == '[');
593
594 /*
595 * Evaluate the expression.
596 */
597 RTERRINFOSTATIC ErrInfo;
598 const char * const pchExpr = &pachPlaceholder[sizeof(g_szPrefixCondExpr) - 1];
599 size_t const cchExpr = cchPlaceholder - sizeof(g_szPrefixCondExpr) + 1 - sizeof(s_szTail) + 1;
600 int vrc = RTExprEvalToBool(hEvaluator, pchExpr, cchExpr, pfOutputting, RTErrInfoInitStatic(&ErrInfo));
601 LogFlowFunc(("RTExprEvalToBool(%.*s) -> %Rrc *pfOutputting=%s\n", cchExpr, pchExpr, vrc, *pfOutputting));
602 if (RT_SUCCESS(vrc))
603 return S_OK;
604 return mpSetError->setErrorBoth(E_FAIL, vrc, tr("Expression evaluation error for '%.*s': %#RTeic"),
605 cchPlaceholder, pachPlaceholder, &ErrInfo.Core);
606}
607
608/*static */ DECLCALLBACK(int)
609UnattendedScriptTemplate::queryVariableForExpr(const char *pchName, size_t cchName, void *pvUser, char **ppszValue) RT_NOEXCEPT
610{
611 UnattendedScriptTemplate *pThis = (UnattendedScriptTemplate *)pvUser;
612 int vrc;
613 try
614 {
615 const char *pszReadOnlyValue = NULL;
616 Utf8Str strTmp;
617 vrc = pThis->queryVariable(pchName, cchName, strTmp, ppszValue ? &pszReadOnlyValue : NULL);
618 if (ppszValue)
619 {
620 if (RT_SUCCESS(vrc))
621 vrc = RTStrDupEx(ppszValue, pszReadOnlyValue);
622 else
623 *ppszValue = NULL;
624 }
625 }
626 catch (std::bad_alloc &)
627 {
628 vrc = VERR_NO_MEMORY;
629 *ppszValue = NULL;
630 }
631 return vrc;
632}
633
634/* static */
635int UnattendedScriptTemplate::shaCryptGenerateSalt(char *pszSalt, size_t cchSalt)
636{
637#ifdef IN_TST_UNATTENDED_SCRIPT
638 /* Use a fixed salt to predict the hashing result with the testcases. */
639 return RTStrCopy(pszSalt, cchSalt + 1, "testcase123");
640#else
641 return RTCrShaCryptGenerateSalt(pszSalt, cchSalt);
642#endif
643}
644
645int UnattendedScriptTemplate::queryVariable(const char *pchName, size_t cchName, Utf8Str &rstrTmp, const char **ppszValue)
646{
647#define IS_MATCH(a_szMatch) \
648 (cchName == sizeof(a_szMatch) - 1U && memcmp(pchName, a_szMatch, sizeof(a_szMatch) - 1U) == 0)
649
650/** Uses the RTCrShaCrypt APIs to hash and crypt data. Uses a randomized salt + (recommended) default rounds.
651 * @todo r=bird: Iff a template needs the salted password more than once,
652 * this approach will not deliver the same value.
653 */
654#define SHACRYPT_AND_ASSIGN(a_szKey, a_fnCrypt, a_cchMaxResult) \
655 do { \
656 if (ppszValue) \
657 { \
658 char szSalt[RT_SHACRYPT_SALT_MAX_LEN + 1]; \
659 int vrc = UnattendedScriptTemplate::shaCryptGenerateSalt(szSalt, sizeof(szSalt) - 1); \
660 AssertRCReturnStmt(vrc, mpSetError->setErrorBoth(E_FAIL, vrc, tr("RTCrShaCryptGenerateSalt failed: %Rrc"), vrc), \
661 vrc); \
662 rstrTmp.reserve(a_cchMaxResult); \
663 vrc = a_fnCrypt(a_szKey, szSalt, RT_SHACRYPT_ROUNDS_DEFAULT, rstrTmp.mutableRaw(), a_cchMaxResult); \
664 AssertRCReturnStmt(vrc, mpSetError->setErrorBoth(E_FAIL, vrc, tr(#a_fnCrypt " failed: %Rrc"), vrc), vrc); \
665 rstrTmp.jolt(); \
666 pszValue = rstrTmp.c_str(); \
667 } \
668 } while (0)
669
670 const char *pszValue = NULL;
671
672 /*
673 * Variables
674 */
675 if (IS_MATCH("USER_LOGIN"))
676 pszValue = mpUnattended->i_getUser().c_str();
677 else if (IS_MATCH("USER_PASSWORD"))
678 pszValue = mpUnattended->i_getUserPassword().c_str();
679 else if (IS_MATCH("USER_PASSWORD_SHACRYPT512"))
680 SHACRYPT_AND_ASSIGN(mpUnattended->i_getUserPassword().c_str(), RTCrShaCrypt512, RT_SHACRYPT_512_MAX_SIZE);
681 else if (IS_MATCH("ROOT_PASSWORD"))
682 pszValue = mpUnattended->i_getAdminPassword().c_str();
683 else if (IS_MATCH("ROOT_PASSWORD_SHACRYPT512"))
684 SHACRYPT_AND_ASSIGN(mpUnattended->i_getAdminPassword().c_str(), RTCrShaCrypt512, RT_SHACRYPT_512_MAX_SIZE);
685 else if (IS_MATCH("USER_FULL_NAME"))
686 pszValue = mpUnattended->i_getFullUserName().c_str();
687 else if (IS_MATCH("PRODUCT_KEY"))
688 pszValue = mpUnattended->i_getProductKey().c_str();
689 else if (IS_MATCH("POST_INSTALL_COMMAND"))
690 pszValue = mpUnattended->i_getPostInstallCommand().c_str();
691 else if (IS_MATCH("AUXILIARY_INSTALL_DIR"))
692 pszValue = mpUnattended->i_getAuxiliaryInstallDir().c_str();
693 else if (IS_MATCH("IMAGE_INDEX"))
694 pszValue = rstrTmp.printf("%u", mpUnattended->i_getImageIndex()).c_str();
695 else if (IS_MATCH("OS_ARCH"))
696 pszValue = mpUnattended->i_isGuestOs64Bit() ? "amd64" : "x86";
697 else if (IS_MATCH("OS_ARCH2"))
698 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "x86";
699 else if (IS_MATCH("OS_ARCH3"))
700 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i386";
701 else if (IS_MATCH("OS_ARCH4"))
702 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i486";
703 else if (IS_MATCH("OS_ARCH6"))
704 pszValue = mpUnattended->i_isGuestOs64Bit() ? "x86_64" : "i686";
705 else if (IS_MATCH("GUEST_OS_VERSION"))
706 pszValue = mpUnattended->i_getDetectedOSVersion().c_str();
707 else if (IS_MATCH("GUEST_OS_MAJOR_VERSION"))
708 {
709 Utf8Str const &rstrOsVer = mpUnattended->i_getDetectedOSVersion();
710 size_t offDot = rstrOsVer.find('.');
711 if (offDot > 0 && offDot != Utf8Str::npos)
712 pszValue = rstrTmp.assign(rstrOsVer, 0, offDot).c_str(); /* caller catches std::bad_alloc */
713 else if (!ppszValue)
714 return VERR_NOT_FOUND;
715 else
716 {
717 mpSetError->setErrorBoth(E_FAIL, VERR_NO_DATA, tr("Unknown guest OS major version '%s'"), rstrOsVer.c_str());
718 return VERR_NO_DATA;
719 }
720 }
721 else if (IS_MATCH("TIME_ZONE_UX"))
722 pszValue = mpUnattended->i_getTimeZoneInfo()
723 ? mpUnattended->i_getTimeZoneInfo()->pszUnixName : mpUnattended->i_getTimeZone().c_str();
724 else if (IS_MATCH("TIME_ZONE_WIN_NAME"))
725 {
726 PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
727 if (pInfo)
728 pszValue = pInfo->pszWindowsName ? pInfo->pszWindowsName : "GMT";
729 else
730 pszValue = mpUnattended->i_getTimeZone().c_str();
731 }
732 else if (IS_MATCH("TIME_ZONE_WIN_INDEX"))
733 {
734 PCRTTIMEZONEINFO pInfo = mpUnattended->i_getTimeZoneInfo();
735 if (pInfo)
736 pszValue = rstrTmp.printf("%u", pInfo->idxWindows ? pInfo->idxWindows : 85 /*GMT*/).c_str();
737 else
738 pszValue = mpUnattended->i_getTimeZone().c_str();
739 }
740 else if (IS_MATCH("LOCALE"))
741 pszValue = mpUnattended->i_getLocale().c_str();
742 else if (IS_MATCH("DASH_LOCALE"))
743 {
744 Assert(mpUnattended->i_getLocale()[2] == '_');
745 pszValue = rstrTmp.assign(mpUnattended->i_getLocale()).replace(2, 1, "-").c_str();
746 }
747 else if (IS_MATCH("LANGUAGE"))
748 pszValue = mpUnattended->i_getLanguage().c_str();
749 else if (IS_MATCH("COUNTRY"))
750 pszValue = mpUnattended->i_getCountry().c_str();
751 else if (IS_MATCH("HOSTNAME_FQDN"))
752 pszValue = mpUnattended->i_getHostname().c_str();
753 else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN"))
754 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, mpUnattended->i_getHostname().find(".")).c_str();
755 else if (IS_MATCH("HOSTNAME_WITHOUT_DOMAIN_MAX_15"))
756 {
757 size_t const idxDot = mpUnattended->i_getHostname().find(".");
758 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), 0, RT_MIN(idxDot, 15)).c_str();
759 }
760 else if (IS_MATCH("HOSTNAME_DOMAIN"))
761 pszValue = rstrTmp.assign(mpUnattended->i_getHostname(), mpUnattended->i_getHostname().find(".") + 1).c_str();
762 else if (IS_MATCH("PROXY"))
763 pszValue = mpUnattended->i_getProxy().c_str();
764 else if (IS_MATCH("ADDITIONS_INSTALL_PACKAGE_NAME"))
765 pszValue = mpUnattended->i_getAdditionsInstallPackage().c_str();
766 /*
767 * Indicator variables.
768 */
769 else if (IS_MATCH("IS_INSTALLING_ADDITIONS"))
770 pszValue = mpUnattended->i_getInstallGuestAdditions() ? "1" : "0";
771 else if (IS_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
772 pszValue = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0 ? "1" : "0";
773 else if (IS_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
774 pszValue = mpUnattended->i_getInstallTestExecService() ? "1" : "0";
775 else if (IS_MATCH("HAS_POST_INSTALL_COMMAND"))
776 pszValue = mpUnattended->i_getPostInstallCommand().isNotEmpty() ? "1" : "0";
777 else if (IS_MATCH("HAS_PRODUCT_KEY"))
778 pszValue = mpUnattended->i_getProductKey().isNotEmpty() ? "1" : "0";
779 else if (IS_MATCH("IS_MINIMAL_INSTALLATION"))
780 pszValue = mpUnattended->i_isMinimalInstallation() ? "1" : "0";
781 else if (IS_MATCH("IS_FIRMWARE_UEFI"))
782 pszValue = mpUnattended->i_isFirmwareEFI() ? "1" : "0";
783 else if (IS_MATCH("IS_RTC_USING_UTC"))
784 pszValue = mpUnattended->i_isRtcUsingUtc() ? "1" : "0";
785 else if (IS_MATCH("HAS_PROXY"))
786 pszValue = mpUnattended->i_getProxy().isNotEmpty() ? "1" : "0";
787 else if (IS_MATCH("AVOID_UPDATES_OVER_NETWORK"))
788 pszValue = mpUnattended->i_getAvoidUpdatesOverNetwork() ? "1" : "0";
789 /*
790 * Unknown variable.
791 */
792 else if (!ppszValue)
793 return VERR_NOT_FOUND;
794 else
795 {
796 mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown variable '%.*s'"), cchName, pchName);
797 return VERR_NO_DATA;
798 }
799 if (ppszValue)
800 *ppszValue = pszValue;
801 return VINF_SUCCESS;
802}
803
804HRESULT UnattendedScriptTemplate::getConditional(const char *pachPlaceholder, size_t cchPlaceholder, bool *pfOutputting)
805{
806#define IS_PLACEHOLDER_MATCH(a_szMatch) \
807 ( cchPlaceholder == sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U \
808 && memcmp(pachPlaceholder, "@@VBOX_COND_" a_szMatch "@@", sizeof("@@VBOX_COND_" a_szMatch "@@") - 1U) == 0)
809
810 /* Install Guest Additions: */
811 if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_ADDITIONS"))
812 *pfOutputting = mpUnattended->i_getInstallGuestAdditions();
813 else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_ADDITIONS"))
814 *pfOutputting = !mpUnattended->i_getInstallGuestAdditions();
815 /* User == Administrator: */
816 else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_ADMINISTRATOR"))
817 *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) == 0;
818 else if (IS_PLACEHOLDER_MATCH("IS_USER_LOGIN_NOT_ADMINISTRATOR"))
819 *pfOutputting = mpUnattended->i_getUser().compare("Administrator", RTCString::CaseInsensitive) != 0;
820 /* Install TXS: */
821 else if (IS_PLACEHOLDER_MATCH("IS_INSTALLING_TEST_EXEC_SERVICE"))
822 *pfOutputting = mpUnattended->i_getInstallTestExecService();
823 else if (IS_PLACEHOLDER_MATCH("IS_NOT_INSTALLING_TEST_EXEC_SERVICE"))
824 *pfOutputting = !mpUnattended->i_getInstallTestExecService();
825 /* Post install command: */
826 else if (IS_PLACEHOLDER_MATCH("HAS_POST_INSTALL_COMMAND"))
827 *pfOutputting = mpUnattended->i_getPostInstallCommand().isNotEmpty();
828 else if (IS_PLACEHOLDER_MATCH("HAS_NO_POST_INSTALL_COMMAND"))
829 *pfOutputting = mpUnattended->i_getPostInstallCommand().isEmpty();
830 /* Product key: */
831 else if (IS_PLACEHOLDER_MATCH("HAS_PRODUCT_KEY"))
832 *pfOutputting = mpUnattended->i_getProductKey().isNotEmpty();
833 else if (IS_PLACEHOLDER_MATCH("HAS_NO_PRODUCT_KEY"))
834 *pfOutputting = mpUnattended->i_getProductKey().isEmpty();
835 /* Minimal installation: */
836 else if (IS_PLACEHOLDER_MATCH("IS_MINIMAL_INSTALLATION"))
837 *pfOutputting = mpUnattended->i_isMinimalInstallation();
838 else if (IS_PLACEHOLDER_MATCH("IS_NOT_MINIMAL_INSTALLATION"))
839 *pfOutputting = !mpUnattended->i_isMinimalInstallation();
840 /* Is firmware UEFI: */
841 else if (IS_PLACEHOLDER_MATCH("IS_FIRMWARE_UEFI"))
842 *pfOutputting = mpUnattended->i_isFirmwareEFI();
843 else if (IS_PLACEHOLDER_MATCH("IS_NOT_FIRMWARE_UEFI"))
844 *pfOutputting = !mpUnattended->i_isFirmwareEFI();
845 /* Is RTC using UTC (i.e. set to UTC time on startup): */
846 else if (IS_PLACEHOLDER_MATCH("IS_RTC_USING_UTC"))
847 *pfOutputting = mpUnattended->i_isRtcUsingUtc();
848 else if (IS_PLACEHOLDER_MATCH("IS_NOT_RTC_USING_UTC"))
849 *pfOutputting = !mpUnattended->i_isRtcUsingUtc();
850 else if (IS_PLACEHOLDER_MATCH("HAS_PROXY"))
851 *pfOutputting = mpUnattended->i_getProxy().isNotEmpty();
852 else if (IS_PLACEHOLDER_MATCH("AVOID_UPDATES_OVER_NETWORK"))
853 *pfOutputting = mpUnattended->i_getAvoidUpdatesOverNetwork();
854 else
855 return mpSetError->setErrorBoth(E_FAIL, VERR_NOT_FOUND, tr("Unknown conditional placeholder '%.*s'"),
856 cchPlaceholder, pachPlaceholder);
857 return S_OK;
858#undef IS_PLACEHOLDER_MATCH
859}
860
861#endif /* VBOX_WITH_UNATTENDED */
862#if 0 /* Keeping this a reference */
863
864
865/*********************************************************************************************************************************
866* UnattendedSUSEXMLScript Implementation *
867*********************************************************************************************************************************/
868
869HRESULT UnattendedSUSEXMLScript::parse()
870{
871 HRESULT hrc = UnattendedXMLScript::parse();
872 if (SUCCEEDED(hrc))
873 {
874 /*
875 * Check that we've got the right root element type.
876 */
877 const xml::ElementNode *pelmRoot = mDoc.getRootElement();
878 if ( pelmRoot
879 && strcmp(pelmRoot->getName(), "profile") == 0)
880 {
881 /*
882 * Work thought the sections.
883 */
884 try
885 {
886 LoopThruSections(pelmRoot);
887 hrc = S_OK;
888 }
889 catch (std::bad_alloc &)
890 {
891 hrc = E_OUTOFMEMORY;
892 }
893 }
894 else if (pelmRoot)
895 hrc = mpSetError->setError(E_FAIL, tr("XML document root element is '%s' instead of 'profile'"),
896 pelmRoot->getName());
897 else
898 hrc = mpSetError->setError(E_FAIL, tr("Missing XML root element"));
899 }
900 return hrc;
901}
902
903HRESULT UnattendedSUSEXMLScript::setFieldInElement(xml::ElementNode *pElement, const DataId enmDataId, const Utf8Str &rStrValue)
904{
905 /*
906 * Don't set empty values.
907 */
908 if (rStrValue.isEmpty())
909 {
910 Utf8Str strProbableValue;
911 try
912 {
913 strProbableValue = createProbableValue(enmDataId, pElement);
914 }
915 catch (std::bad_alloc &)
916 {
917 return E_OUTOFMEMORY;
918 }
919 return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, strProbableValue);
920 }
921 return UnattendedXMLScript::setFieldInElement(pElement, enmDataId, rStrValue);
922}
923
924HRESULT UnattendedSUSEXMLScript::LoopThruSections(const xml::ElementNode *pelmRoot)
925{
926 xml::NodesLoop loopChildren(*pelmRoot);
927 const xml::ElementNode *pelmOuterLoop;
928 while ((pelmOuterLoop = loopChildren.forAllNodes()) != NULL)
929 {
930 const char *pcszElemName = pelmOuterLoop->getName();
931 if (!strcmp(pcszElemName, "users"))
932 {
933 xml::NodesLoop loopUsers(*pelmOuterLoop);
934 const xml::ElementNode *pelmUser;
935 while ((pelmUser = loopUsers.forAllNodes()) != NULL)
936 {
937 HRESULT hrc = HandleUserAccountsSection(pelmUser);
938 if (FAILED(hrc))
939 return hrc;
940 }
941 }
942 }
943 return S_OK;
944}
945
946HRESULT UnattendedSUSEXMLScript::HandleUserAccountsSection(const xml::ElementNode *pelmSection)
947{
948 xml::NodesLoop loopUser(*pelmSection);
949
950 const xml::ElementNode *pelmCur;
951 while ((pelmCur = loopUser.forAllNodes()) != NULL)
952 {
953 const char *pszValue = pelmCur->getValue();
954#ifdef LOG_ENABLED
955 if (!RTStrCmp(pelmCur->getName(), "uid"))
956 LogRelFunc(("UnattendedSUSEXMLScript::HandleUserAccountsSection profile/users/%s/%s = %s\n",
957 pelmSection->getName(), pelmCur->getName(), pszValue));
958#endif
959
960 if (!RTStrCmp(pszValue, "$homedir"))
961 mNodesForCorrectionMap.insert(make_pair(USERHOMEDIR_ID, pelmCur));
962
963 if (!RTStrCmp(pszValue, "$user"))
964 mNodesForCorrectionMap.insert(make_pair(USERNAME_ID, pelmCur));
965
966 if (!RTStrCmp(pszValue, "$password"))
967 mNodesForCorrectionMap.insert(make_pair(USERPASSWORD_ID, pelmCur));
968 }
969 return S_OK;
970}
971
972Utf8Str UnattendedSUSEXMLScript::createProbableValue(const DataId enmDataId, const xml::ElementNode *pCurElem)
973{
974 const xml::ElementNode *pElem = pCurElem;
975
976 switch (enmDataId)
977 {
978 case USERHOMEDIR_ID:
979// if ((pElem = pElem->findChildElement("home")))
980// {
981 return createProbableUserHomeDir(pElem);
982// }
983 break;
984 default:
985 break;
986 }
987
988 return Utf8Str::Empty;
989}
990
991Utf8Str UnattendedSUSEXMLScript::createProbableUserHomeDir(const xml::ElementNode *pCurElem)
992{
993 Utf8Str strCalcValue;
994 const xml::ElementNode *pElem = pCurElem->findNextSibilingElement("username");
995 if (pElem)
996 {
997 const char *pszValue = pElem->getValue();
998 strCalcValue = "/home/";
999 strCalcValue.append(pszValue);
1000 }
1001
1002 return strCalcValue;
1003}
1004#endif /* just for reference */
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