]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/pull-common.c
ecdcbd2dc22e9de9715e7e0e9ac479a4018e3715
[thirdparty/systemd.git] / src / import / pull-common.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2015 Lennart Poettering
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <sys/prctl.h>
22
23 #include "alloc-util.h"
24 #include "btrfs-util.h"
25 #include "capability-util.h"
26 #include "copy.h"
27 #include "dirent-util.h"
28 #include "escape.h"
29 #include "fd-util.h"
30 #include "io-util.h"
31 #include "path-util.h"
32 #include "process-util.h"
33 #include "pull-common.h"
34 #include "pull-job.h"
35 #include "rm-rf.h"
36 #include "signal-util.h"
37 #include "siphash24.h"
38 #include "string-util.h"
39 #include "strv.h"
40 #include "util.h"
41 #include "web-util.h"
42
43 #define FILENAME_ESCAPE "/.#\"\'"
44 #define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16)
45
46 int pull_find_old_etags(
47 const char *url,
48 const char *image_root,
49 int dt,
50 const char *prefix,
51 const char *suffix,
52 char ***etags) {
53
54 _cleanup_free_ char *escaped_url = NULL;
55 _cleanup_closedir_ DIR *d = NULL;
56 _cleanup_strv_free_ char **l = NULL;
57 struct dirent *de;
58 int r;
59
60 assert(url);
61 assert(etags);
62
63 if (!image_root)
64 image_root = "/var/lib/machines";
65
66 escaped_url = xescape(url, FILENAME_ESCAPE);
67 if (!escaped_url)
68 return -ENOMEM;
69
70 d = opendir(image_root);
71 if (!d) {
72 if (errno == ENOENT) {
73 *etags = NULL;
74 return 0;
75 }
76
77 return -errno;
78 }
79
80 FOREACH_DIRENT_ALL(de, d, return -errno) {
81 const char *a, *b;
82 char *u;
83
84 if (de->d_type != DT_UNKNOWN &&
85 de->d_type != dt)
86 continue;
87
88 if (prefix) {
89 a = startswith(de->d_name, prefix);
90 if (!a)
91 continue;
92 } else
93 a = de->d_name;
94
95 a = startswith(a, escaped_url);
96 if (!a)
97 continue;
98
99 a = startswith(a, ".");
100 if (!a)
101 continue;
102
103 if (suffix) {
104 b = endswith(de->d_name, suffix);
105 if (!b)
106 continue;
107 } else
108 b = strchr(de->d_name, 0);
109
110 if (a >= b)
111 continue;
112
113 r = cunescape_length(a, b - a, 0, &u);
114 if (r < 0)
115 return r;
116
117 if (!http_etag_is_valid(u)) {
118 free(u);
119 continue;
120 }
121
122 r = strv_consume(&l, u);
123 if (r < 0)
124 return r;
125 }
126
127 *etags = l;
128 l = NULL;
129
130 return 0;
131 }
132
133 int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
134 const char *p;
135 int r;
136
137 assert(final);
138 assert(local);
139
140 if (!image_root)
141 image_root = "/var/lib/machines";
142
143 p = strjoina(image_root, "/", local);
144
145 if (force_local)
146 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
147
148 r = btrfs_subvol_snapshot(final, p,
149 BTRFS_SNAPSHOT_QUOTA|
150 BTRFS_SNAPSHOT_FALLBACK_COPY|
151 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
152 BTRFS_SNAPSHOT_RECURSIVE);
153 if (r < 0)
154 return log_error_errno(r, "Failed to create local image: %m");
155
156 log_info("Created new local image '%s'.", local);
157
158 return 0;
159 }
160
161 static int hash_url(const char *url, char **ret) {
162 uint64_t h;
163 static const sd_id128_t k = SD_ID128_ARRAY(df,89,16,87,01,cc,42,30,98,ab,4a,19,a6,a5,63,4f);
164
165 assert(url);
166
167 h = siphash24(url, strlen(url), k.bytes);
168 if (asprintf(ret, "%"PRIx64, h) < 0)
169 return -ENOMEM;
170
171 return 0;
172 }
173
174 int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
175 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
176 char *path;
177
178 assert(url);
179 assert(ret);
180
181 if (!image_root)
182 image_root = "/var/lib/machines";
183
184 escaped_url = xescape(url, FILENAME_ESCAPE);
185 if (!escaped_url)
186 return -ENOMEM;
187
188 if (etag) {
189 escaped_etag = xescape(etag, FILENAME_ESCAPE);
190 if (!escaped_etag)
191 return -ENOMEM;
192 }
193
194 path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
195 strempty(escaped_etag), strempty(suffix));
196 if (!path)
197 return -ENOMEM;
198
199 /* URLs might make the path longer than the maximum allowed length for a file name.
200 * When that happens, a URL hash is used instead. Paths returned by this function
201 * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
202 if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
203 _cleanup_free_ char *hash = NULL;
204 int r;
205
206 free(path);
207
208 r = hash_url(url, &hash);
209 if (r < 0)
210 return r;
211
212 path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
213 strempty(escaped_etag), strempty(suffix));
214 if (!path)
215 return -ENOMEM;
216 }
217
218 *ret = path;
219 return 0;
220 }
221
222 int pull_make_auxiliary_job(
223 PullJob **ret,
224 const char *url,
225 int (*strip_suffixes)(const char *name, char **ret),
226 const char *suffix,
227 CurlGlue *glue,
228 PullJobFinished on_finished,
229 void *userdata) {
230
231 _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
232 _cleanup_(pull_job_unrefp) PullJob *job = NULL;
233 const char *q;
234 int r;
235
236 assert(ret);
237 assert(url);
238 assert(strip_suffixes);
239 assert(glue);
240
241 r = import_url_last_component(url, &last_component);
242 if (r < 0)
243 return r;
244
245 r = strip_suffixes(last_component, &ll);
246 if (r < 0)
247 return r;
248
249 q = strjoina(ll, suffix);
250
251 r = import_url_change_last_component(url, q, &auxiliary_url);
252 if (r < 0)
253 return r;
254
255 r = pull_job_new(&job, auxiliary_url, glue, userdata);
256 if (r < 0)
257 return r;
258
259 job->on_finished = on_finished;
260 job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
261
262 *ret = job;
263 job = NULL;
264
265 return 0;
266 }
267
268 int pull_make_verification_jobs(
269 PullJob **ret_checksum_job,
270 PullJob **ret_signature_job,
271 ImportVerify verify,
272 const char *url,
273 CurlGlue *glue,
274 PullJobFinished on_finished,
275 void *userdata) {
276
277 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
278 int r;
279 const char *chksums = NULL;
280
281 assert(ret_checksum_job);
282 assert(ret_signature_job);
283 assert(verify >= 0);
284 assert(verify < _IMPORT_VERIFY_MAX);
285 assert(url);
286 assert(glue);
287
288 if (verify != IMPORT_VERIFY_NO) {
289 _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
290
291 /* Queue jobs for the checksum file for the image. */
292 r = import_url_last_component(url, &fn);
293 if (r < 0)
294 return r;
295
296 chksums = strjoina(fn, ".sha256");
297
298 r = import_url_change_last_component(url, chksums, &checksum_url);
299 if (r < 0)
300 return r;
301
302 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
303 if (r < 0)
304 return r;
305
306 checksum_job->on_finished = on_finished;
307 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
308 }
309
310 if (verify == IMPORT_VERIFY_SIGNATURE) {
311 _cleanup_free_ char *signature_url = NULL;
312
313 /* Queue job for the SHA256SUMS.gpg file for the image. */
314 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
315 if (r < 0)
316 return r;
317
318 r = pull_job_new(&signature_job, signature_url, glue, userdata);
319 if (r < 0)
320 return r;
321
322 signature_job->on_finished = on_finished;
323 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
324 }
325
326 *ret_checksum_job = checksum_job;
327 *ret_signature_job = signature_job;
328
329 checksum_job = signature_job = NULL;
330
331 return 0;
332 }
333
334 static int verify_one(PullJob *checksum_job, PullJob *job) {
335 _cleanup_free_ char *fn = NULL;
336 const char *line, *p;
337 int r;
338
339 assert(checksum_job);
340
341 if (!job)
342 return 0;
343
344 assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
345
346 /* Don't verify the checksum if we didn't actually successfully download something new */
347 if (job->state != PULL_JOB_DONE)
348 return 0;
349 if (job->error != 0)
350 return 0;
351 if (job->etag_exists)
352 return 0;
353
354 assert(job->calc_checksum);
355 assert(job->checksum);
356
357 r = import_url_last_component(job->url, &fn);
358 if (r < 0)
359 return log_oom();
360
361 if (!filename_is_valid(fn)) {
362 log_error("Cannot verify checksum, could not determine server-side file name.");
363 return -EBADMSG;
364 }
365
366 line = strjoina(job->checksum, " *", fn, "\n");
367
368 p = memmem(checksum_job->payload,
369 checksum_job->payload_size,
370 line,
371 strlen(line));
372
373 if (!p) {
374 line = strjoina(job->checksum, " ", fn, "\n");
375
376 p = memmem(checksum_job->payload,
377 checksum_job->payload_size,
378 line,
379 strlen(line));
380 }
381
382 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
383 log_error("DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn);
384 return -EBADMSG;
385 }
386
387 log_info("SHA256 checksum of %s is valid.", job->url);
388 return 1;
389 }
390
391 int pull_verify(PullJob *main_job,
392 PullJob *roothash_job,
393 PullJob *settings_job,
394 PullJob *checksum_job,
395 PullJob *signature_job) {
396
397 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
398 _cleanup_close_ int sig_file = -1;
399 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
400 _cleanup_(sigkill_waitp) pid_t pid = 0;
401 bool gpg_home_created = false;
402 int r;
403
404 assert(main_job);
405 assert(main_job->state == PULL_JOB_DONE);
406
407 if (!checksum_job)
408 return 0;
409
410 assert(main_job->calc_checksum);
411 assert(main_job->checksum);
412
413 assert(checksum_job->state == PULL_JOB_DONE);
414
415 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
416 log_error("Checksum is empty, cannot verify.");
417 return -EBADMSG;
418 }
419
420 r = verify_one(checksum_job, main_job);
421 if (r < 0)
422 return r;
423
424 r = verify_one(checksum_job, roothash_job);
425 if (r < 0)
426 return r;
427
428 r = verify_one(checksum_job, settings_job);
429 if (r < 0)
430 return r;
431
432 if (!signature_job)
433 return 0;
434
435 if (checksum_job->style == VERIFICATION_PER_FILE)
436 signature_job = checksum_job;
437
438 assert(signature_job->state == PULL_JOB_DONE);
439
440 if (!signature_job->payload || signature_job->payload_size <= 0) {
441 log_error("Signature is empty, cannot verify.");
442 return -EBADMSG;
443 }
444
445 r = pipe2(gpg_pipe, O_CLOEXEC);
446 if (r < 0)
447 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
448
449 sig_file = mkostemp(sig_file_path, O_RDWR);
450 if (sig_file < 0)
451 return log_error_errno(errno, "Failed to create temporary file: %m");
452
453 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
454 if (r < 0) {
455 log_error_errno(r, "Failed to write to temporary file: %m");
456 goto finish;
457 }
458
459 if (!mkdtemp(gpg_home)) {
460 r = log_error_errno(errno, "Failed to create tempory home for gpg: %m");
461 goto finish;
462 }
463
464 gpg_home_created = true;
465
466 r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
467 if (r < 0)
468 return r;
469 if (r == 0) {
470 const char *cmd[] = {
471 "gpg",
472 "--no-options",
473 "--no-default-keyring",
474 "--no-auto-key-locate",
475 "--no-auto-check-trustdb",
476 "--batch",
477 "--trust-model=always",
478 NULL, /* --homedir= */
479 NULL, /* --keyring= */
480 NULL, /* --verify */
481 NULL, /* signature file */
482 NULL, /* dash */
483 NULL /* trailing NULL */
484 };
485 unsigned k = ELEMENTSOF(cmd) - 6;
486 int null_fd;
487
488 /* Child */
489
490 gpg_pipe[1] = safe_close(gpg_pipe[1]);
491
492 r = move_fd(gpg_pipe[0], STDIN_FILENO, false);
493 if (r < 0) {
494 log_error_errno(errno, "Failed to move fd: %m");
495 _exit(EXIT_FAILURE);
496 }
497
498 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
499 if (null_fd < 0) {
500 log_error_errno(errno, "Failed to open /dev/null: %m");
501 _exit(EXIT_FAILURE);
502 }
503
504 r = move_fd(null_fd, STDOUT_FILENO, false);
505 if (r < 0) {
506 log_error_errno(errno, "Failed to move fd: %m");
507 _exit(EXIT_FAILURE);
508 }
509
510 cmd[k++] = strjoina("--homedir=", gpg_home);
511
512 /* We add the user keyring only to the command line
513 * arguments, if it's around since gpg fails
514 * otherwise. */
515 if (access(USER_KEYRING_PATH, F_OK) >= 0)
516 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
517 else
518 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
519
520 cmd[k++] = "--verify";
521 if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
522 cmd[k++] = sig_file_path;
523 cmd[k++] = "-";
524 cmd[k++] = NULL;
525 }
526
527 stdio_unset_cloexec();
528
529 execvp("gpg2", (char * const *) cmd);
530 execvp("gpg", (char * const *) cmd);
531 log_error_errno(errno, "Failed to execute gpg: %m");
532 _exit(EXIT_FAILURE);
533 }
534
535 gpg_pipe[0] = safe_close(gpg_pipe[0]);
536
537 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
538 if (r < 0) {
539 log_error_errno(r, "Failed to write to pipe: %m");
540 goto finish;
541 }
542
543 gpg_pipe[1] = safe_close(gpg_pipe[1]);
544
545 r = wait_for_terminate_and_check("gpg", pid, WAIT_LOG_ABNORMAL);
546 pid = 0;
547 if (r < 0)
548 goto finish;
549 if (r != EXIT_SUCCESS) {
550 log_error("DOWNLOAD INVALID: Signature verification failed.");
551 r = -EBADMSG;
552 } else {
553 log_info("Signature verification succeeded.");
554 r = 0;
555 }
556
557 finish:
558 if (sig_file >= 0)
559 (void) unlink(sig_file_path);
560
561 if (gpg_home_created)
562 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
563
564 return r;
565 }