1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <getopt.h>
4 #include <locale.h>
5 
6 #include "sd-event.h"
7 #include "sd-id128.h"
8 
9 #include "alloc-util.h"
10 #include "discover-image.h"
11 #include "env-util.h"
12 #include "hexdecoct.h"
13 #include "hostname-util.h"
14 #include "import-common.h"
15 #include "import-util.h"
16 #include "io-util.h"
17 #include "main-func.h"
18 #include "parse-argument.h"
19 #include "parse-util.h"
20 #include "pull-raw.h"
21 #include "pull-tar.h"
22 #include "signal-util.h"
23 #include "string-util.h"
24 #include "terminal-util.h"
25 #include "verbs.h"
26 #include "web-util.h"
27 
28 static const char *arg_image_root = "/var/lib/machines";
29 static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE;
30 static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY | PULL_BTRFS_SUBVOL | PULL_BTRFS_QUOTA | PULL_CONVERT_QCOW2 | PULL_SYNC;
31 static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX;
32 static char *arg_checksum = NULL;
33 
34 STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep);
35 
normalize_local(const char * local,const char * url,char ** ret)36 static int normalize_local(const char *local, const char *url, char **ret) {
37         _cleanup_free_ char *ll = NULL;
38         int r;
39 
40         if (arg_pull_flags & PULL_DIRECT) {
41 
42                 if (!local)
43                         log_debug("Writing downloaded data to STDOUT.");
44                 else {
45                         if (!path_is_absolute(local)) {
46                                 ll = path_join(arg_image_root, local);
47                                 if (!ll)
48                                         return log_oom();
49 
50                                 local = ll;
51                         }
52 
53                         if (!path_is_valid(local))
54                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
55                                                "Local path name '%s' is not valid.", local);
56                 }
57 
58         } else if (local) {
59 
60                 if (!hostname_is_valid(local, 0))
61                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
62                                                "Local image name '%s' is not valid.",
63                                                local);
64 
65                 if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) {
66                         r = image_find(IMAGE_MACHINE, local, NULL, NULL);
67                         if (r < 0) {
68                                 if (r != -ENOENT)
69                                         return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local);
70                         } else
71                                 return log_error_errno(SYNTHETIC_ERRNO(EEXIST),
72                                                        "Image '%s' already exists.",
73                                                        local);
74                 }
75         }
76 
77         if (!ll && local) {
78                 ll = strdup(local);
79                 if (!ll)
80                         return log_oom();
81         }
82 
83         if (ll) {
84                 if (arg_offset != UINT64_MAX)
85                         log_info("Pulling '%s', saving at offset %" PRIu64 " in '%s'.", url, arg_offset, ll);
86                 else
87                         log_info("Pulling '%s', saving as '%s'.", url, ll);
88         } else
89                 log_info("Pulling '%s'.", url);
90 
91         *ret = TAKE_PTR(ll);
92         return 0;
93 }
94 
on_tar_finished(TarPull * pull,int error,void * userdata)95 static void on_tar_finished(TarPull *pull, int error, void *userdata) {
96         sd_event *event = userdata;
97         assert(pull);
98 
99         if (error == 0)
100                 log_info("Operation completed successfully.");
101 
102         sd_event_exit(event, abs(error));
103 }
104 
pull_tar(int argc,char * argv[],void * userdata)105 static int pull_tar(int argc, char *argv[], void *userdata) {
106         _cleanup_free_ char *ll = NULL, *normalized = NULL;
107         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
108         _cleanup_(tar_pull_unrefp) TarPull *pull = NULL;
109         const char *url, *local;
110         int r;
111 
112         url = argv[1];
113         if (!http_url_is_valid(url) && !file_url_is_valid(url))
114                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", url);
115 
116         if (argc >= 3)
117                 local = empty_or_dash_to_null(argv[2]);
118         else {
119                 _cleanup_free_ char *l = NULL;
120 
121                 r = import_url_last_component(url, &l);
122                 if (r < 0)
123                         return log_error_errno(r, "Failed to get final component of URL: %m");
124 
125                 r = tar_strip_suffixes(l, &ll);
126                 if (r < 0)
127                         return log_oom();
128 
129                 local = ll;
130         }
131 
132         if (!local && FLAGS_SET(arg_pull_flags, PULL_DIRECT))
133                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pulling tar images to STDOUT is not supported.");
134 
135         r = normalize_local(local, url, &normalized);
136         if (r < 0)
137                 return r;
138 
139         r = import_allocate_event_with_signals(&event);
140         if (r < 0)
141                 return r;
142 
143         if (!FLAGS_SET(arg_pull_flags, PULL_SYNC))
144                 log_info("File system synchronization on completion is off.");
145 
146         r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event);
147         if (r < 0)
148                 return log_error_errno(r, "Failed to allocate puller: %m");
149 
150         r = tar_pull_start(
151                         pull,
152                         url,
153                         normalized,
154                         arg_pull_flags & PULL_FLAGS_MASK_TAR,
155                         arg_verify,
156                         arg_checksum);
157         if (r < 0)
158                 return log_error_errno(r, "Failed to pull image: %m");
159 
160         r = sd_event_loop(event);
161         if (r < 0)
162                 return log_error_errno(r, "Failed to run event loop: %m");
163 
164         log_info("Exiting.");
165         return -r;
166 }
167 
on_raw_finished(RawPull * pull,int error,void * userdata)168 static void on_raw_finished(RawPull *pull, int error, void *userdata) {
169         sd_event *event = userdata;
170         assert(pull);
171 
172         if (error == 0)
173                 log_info("Operation completed successfully.");
174 
175         sd_event_exit(event, abs(error));
176 }
177 
pull_raw(int argc,char * argv[],void * userdata)178 static int pull_raw(int argc, char *argv[], void *userdata) {
179         _cleanup_free_ char *ll = NULL, *normalized = NULL;
180         _cleanup_(sd_event_unrefp) sd_event *event = NULL;
181         _cleanup_(raw_pull_unrefp) RawPull *pull = NULL;
182         const char *url, *local;
183         int r;
184 
185         url = argv[1];
186         if (!http_url_is_valid(url) && !file_url_is_valid(url))
187                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "URL '%s' is not valid.", url);
188 
189         if (argc >= 3)
190                 local = empty_or_dash_to_null(argv[2]);
191         else {
192                 _cleanup_free_ char *l = NULL;
193 
194                 r = import_url_last_component(url, &l);
195                 if (r < 0)
196                         return log_error_errno(r, "Failed to get final component of URL: %m");
197 
198                 r = raw_strip_suffixes(l, &ll);
199                 if (r < 0)
200                         return log_oom();
201 
202                 local = ll;
203         }
204 
205         r = normalize_local(local, url, &normalized);
206         if (r < 0)
207                 return r;
208 
209         r = import_allocate_event_with_signals(&event);
210         if (r < 0)
211                 return r;
212 
213         if (!FLAGS_SET(arg_pull_flags, PULL_SYNC))
214                 log_info("File system synchronization on completion is off.");
215          r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event);
216         if (r < 0)
217                 return log_error_errno(r, "Failed to allocate puller: %m");
218 
219         r = raw_pull_start(
220                         pull,
221                         url,
222                         normalized,
223                         arg_offset,
224                         arg_size_max,
225                         arg_pull_flags & PULL_FLAGS_MASK_RAW,
226                         arg_verify,
227                         arg_checksum);
228         if (r < 0)
229                 return log_error_errno(r, "Failed to pull image: %m");
230 
231         r = sd_event_loop(event);
232         if (r < 0)
233                 return log_error_errno(r, "Failed to run event loop: %m");
234 
235         log_info("Exiting.");
236         return -r;
237 }
238 
help(int argc,char * argv[],void * userdata)239 static int help(int argc, char *argv[], void *userdata) {
240 
241         printf("%1$s [OPTIONS...] {COMMAND} ...\n"
242                "\n%4$sDownload container or virtual machine images.%5$s\n"
243                "\n%2$sCommands:%3$s\n"
244                "  tar URL [NAME]              Download a TAR image\n"
245                "  raw URL [NAME]              Download a RAW image\n"
246                "\n%2$sOptions:%3$s\n"
247                "  -h --help                   Show this help\n"
248                "     --version                Show package version\n"
249                "     --force                  Force creation of image\n"
250                "     --verify=MODE            Verify downloaded image, one of: 'no',\n"
251                "                              'checksum', 'signature' or literal SHA256 hash\n"
252                "     --settings=BOOL          Download settings file with image\n"
253                "     --roothash=BOOL          Download root hash file with image\n"
254                "     --roothash-signature=BOOL\n"
255                "                              Download root hash signature file with image\n"
256                "     --verity=BOOL            Download verity file with image\n"
257                "     --image-root=PATH        Image root directory\n\n"
258                "     --read-only              Create a read-only image\n"
259                "     --direct                 Download directly to specified file\n"
260                "     --btrfs-subvol=BOOL      Controls whether to create a btrfs subvolume\n"
261                "                              instead of a directory\n"
262                "     --btrfs-quota=BOOL       Controls whether to set up quota for btrfs\n"
263                "                              subvolume\n"
264                "     --convert-qcow2=BOOL     Controls whether to convert QCOW2 images to\n"
265                "                              regular disk images\n"
266                "     --sync=BOOL              Controls whether to sync() before completing\n"
267                "     --offset=BYTES           Offset to seek to in destination\n"
268                "     --size-max=BYTES         Maximum number of bytes to write to destination\n",
269                program_invocation_short_name,
270                ansi_underline(),
271                ansi_normal(),
272                ansi_highlight(),
273                ansi_normal());
274 
275         return 0;
276 }
277 
parse_argv(int argc,char * argv[])278 static int parse_argv(int argc, char *argv[]) {
279 
280         enum {
281                 ARG_VERSION = 0x100,
282                 ARG_FORCE,
283                 ARG_IMAGE_ROOT,
284                 ARG_VERIFY,
285                 ARG_SETTINGS,
286                 ARG_ROOTHASH,
287                 ARG_ROOTHASH_SIGNATURE,
288                 ARG_VERITY,
289                 ARG_READ_ONLY,
290                 ARG_DIRECT,
291                 ARG_BTRFS_SUBVOL,
292                 ARG_BTRFS_QUOTA,
293                 ARG_CONVERT_QCOW2,
294                 ARG_SYNC,
295                 ARG_OFFSET,
296                 ARG_SIZE_MAX,
297         };
298 
299         static const struct option options[] = {
300                 { "help",               no_argument,       NULL, 'h'                    },
301                 { "version",            no_argument,       NULL, ARG_VERSION            },
302                 { "force",              no_argument,       NULL, ARG_FORCE              },
303                 { "image-root",         required_argument, NULL, ARG_IMAGE_ROOT         },
304                 { "verify",             required_argument, NULL, ARG_VERIFY             },
305                 { "settings",           required_argument, NULL, ARG_SETTINGS           },
306                 { "roothash",           required_argument, NULL, ARG_ROOTHASH           },
307                 { "roothash-signature", required_argument, NULL, ARG_ROOTHASH_SIGNATURE },
308                 { "verity",             required_argument, NULL, ARG_VERITY             },
309                 { "read-only",          no_argument,       NULL, ARG_READ_ONLY          },
310                 { "direct",             no_argument,       NULL, ARG_DIRECT             },
311                 { "btrfs-subvol",       required_argument, NULL, ARG_BTRFS_SUBVOL       },
312                 { "btrfs-quota",        required_argument, NULL, ARG_BTRFS_QUOTA        },
313                 { "convert-qcow2",      required_argument, NULL, ARG_CONVERT_QCOW2      },
314                 { "sync",               required_argument, NULL, ARG_SYNC               },
315                 { "offset",             required_argument, NULL, ARG_OFFSET             },
316                 { "size-max",           required_argument, NULL, ARG_SIZE_MAX           },
317                 {}
318         };
319 
320         int c, r;
321 
322         assert(argc >= 0);
323         assert(argv);
324 
325         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
326 
327                 switch (c) {
328 
329                 case 'h':
330                         return help(0, NULL, NULL);
331 
332                 case ARG_VERSION:
333                         return version();
334 
335                 case ARG_FORCE:
336                         arg_pull_flags |= PULL_FORCE;
337                         break;
338 
339                 case ARG_IMAGE_ROOT:
340                         arg_image_root = optarg;
341                         break;
342 
343                 case ARG_VERIFY: {
344                         ImportVerify v;
345 
346                         v = import_verify_from_string(optarg);
347                         if (v < 0) {
348                                 _cleanup_free_ void *h = NULL;
349                                 char *hh;
350                                 size_t n;
351 
352                                 /* If this is not a valid verification mode, maybe it's a literally specified
353                                  * SHA256 hash? We can handle that too... */
354 
355                                 r = unhexmem(optarg, (size_t) -1, &h, &n);
356                                 if (r < 0 || n == 0)
357                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
358                                                                "Invalid verification setting: %s", optarg);
359                                 if (n != 32)
360                                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
361                                                                "64 hex character SHA256 hash required when specifying explicit checksum, %zu specified", n * 2);
362 
363                                 hh = hexmem(h, n); /* bring into canonical (lowercase) form */
364                                 if (!hh)
365                                         return log_oom();
366 
367                                 free_and_replace(arg_checksum, hh);
368                                 arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY);
369                                 arg_verify = _IMPORT_VERIFY_INVALID;
370                         } else
371                                 arg_verify = v;
372 
373                         break;
374                 }
375 
376                 case ARG_SETTINGS:
377                         r = parse_boolean_argument("--settings=", optarg, NULL);
378                         if (r < 0)
379                                 return r;
380 
381                         SET_FLAG(arg_pull_flags, PULL_SETTINGS, r);
382                         break;
383 
384                 case ARG_ROOTHASH:
385                         r = parse_boolean_argument("--roothash=", optarg, NULL);
386                         if (r < 0)
387                                 return r;
388 
389                         SET_FLAG(arg_pull_flags, PULL_ROOTHASH, r);
390 
391                         /* If we were asked to turn off the root hash, implicitly also turn off the root hash signature */
392                         if (!r)
393                                 SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, false);
394                         break;
395 
396                 case ARG_ROOTHASH_SIGNATURE:
397                         r = parse_boolean_argument("--roothash-signature=", optarg, NULL);
398                         if (r < 0)
399                                 return r;
400 
401                         SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, r);
402                         break;
403 
404                 case ARG_VERITY:
405                         r = parse_boolean_argument("--verity=", optarg, NULL);
406                         if (r < 0)
407                                 return r;
408 
409                         SET_FLAG(arg_pull_flags, PULL_VERITY, r);
410                         break;
411 
412                 case ARG_READ_ONLY:
413                         arg_pull_flags |= PULL_READ_ONLY;
414                         break;
415 
416                 case ARG_DIRECT:
417                         arg_pull_flags |= PULL_DIRECT;
418                         arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY);
419                         break;
420 
421                 case ARG_BTRFS_SUBVOL:
422                         r = parse_boolean_argument("--btrfs-subvol=", optarg, NULL);
423                         if (r < 0)
424                                 return r;
425 
426                         SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r);
427                         break;
428 
429                 case ARG_BTRFS_QUOTA:
430                         r = parse_boolean_argument("--btrfs-quota=", optarg, NULL);
431                         if (r < 0)
432                                 return r;
433 
434                         SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r);
435                         break;
436 
437                 case ARG_CONVERT_QCOW2:
438                         r = parse_boolean_argument("--convert-qcow2=", optarg, NULL);
439                         if (r < 0)
440                                 return r;
441 
442                         SET_FLAG(arg_pull_flags, PULL_CONVERT_QCOW2, r);
443                         break;
444 
445                 case ARG_SYNC:
446                         r = parse_boolean_argument("--sync=", optarg, NULL);
447                         if (r < 0)
448                                 return r;
449 
450                         SET_FLAG(arg_pull_flags, PULL_SYNC, r);
451                         break;
452 
453                 case ARG_OFFSET: {
454                         uint64_t u;
455 
456                         r = safe_atou64(optarg, &u);
457                         if (r < 0)
458                                 return log_error_errno(r, "Failed to parse --offset= argument: %s", optarg);
459                         if (!FILE_SIZE_VALID(u))
460                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --offset= switch too large: %s", optarg);
461 
462                         arg_offset = u;
463                         break;
464                 }
465 
466                 case ARG_SIZE_MAX: {
467                         uint64_t u;
468 
469                         r = parse_size(optarg, 1024, &u);
470                         if (r < 0)
471                                 return log_error_errno(r, "Failed to parse --size-max= argument: %s", optarg);
472                         if (!FILE_SIZE_VALID(u))
473                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Argument to --size-max= switch too large: %s", optarg);
474 
475                         arg_size_max = u;
476                         break;
477                 }
478 
479                 case '?':
480                         return -EINVAL;
481 
482                 default:
483                         assert_not_reached();
484                 }
485 
486         /* Make sure offset+size is still in the valid range if both set */
487         if (arg_offset != UINT64_MAX && arg_size_max != UINT64_MAX &&
488             ((arg_size_max > (UINT64_MAX - arg_offset)) ||
489              !FILE_SIZE_VALID(arg_offset + arg_size_max)))
490                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset und maximum size out of range.");
491 
492         if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_pull_flags, PULL_DIRECT))
493                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode.");
494 
495         if (arg_checksum && (arg_pull_flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) != 0)
496                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded.");
497 
498         return 1;
499 }
500 
parse_env(void)501 static void parse_env(void) {
502         int r;
503 
504         /* Let's make these relatively low-level settings also controllable via env vars. User can then set
505          * them for systemd-importd.service if they like to tweak behaviour */
506 
507         r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL");
508         if (r >= 0)
509                 SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r);
510         else if (r != -ENXIO)
511                 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_SUBVOL: %m");
512 
513         r = getenv_bool("SYSTEMD_IMPORT_BTRFS_QUOTA");
514         if (r >= 0)
515                 SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r);
516         else if (r != -ENXIO)
517                 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m");
518 
519         r = getenv_bool("SYSTEMD_IMPORT_SYNC");
520         if (r >= 0)
521                 SET_FLAG(arg_pull_flags, PULL_SYNC, r);
522         else if (r != -ENXIO)
523                 log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m");
524 }
525 
pull_main(int argc,char * argv[])526 static int pull_main(int argc, char *argv[]) {
527         static const Verb verbs[] = {
528                 { "help", VERB_ANY, VERB_ANY, 0, help     },
529                 { "tar",  2,        3,        0, pull_tar },
530                 { "raw",  2,        3,        0, pull_raw },
531                 {}
532         };
533 
534         return dispatch_verb(argc, argv, verbs, NULL);
535 }
536 
run(int argc,char * argv[])537 static int run(int argc, char *argv[]) {
538         int r;
539 
540         setlocale(LC_ALL, "");
541         log_parse_environment();
542         log_open();
543 
544         parse_env();
545 
546         r = parse_argv(argc, argv);
547         if (r <= 0)
548                 return r;
549 
550         (void) ignore_signals(SIGPIPE);
551 
552         return pull_main(argc, argv);
553 }
554 
555 DEFINE_MAIN_FUNCTION(run);
556