]> git.ipfire.org Git - thirdparty/systemd.git/blame_incremental - src/import/pull-common.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[thirdparty/systemd.git] / src / import / pull-common.c
... / ...
CommitLineData
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
46int 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 = TAKE_PTR(l);
128
129 return 0;
130}
131
132int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
133 const char *p;
134 int r;
135
136 assert(final);
137 assert(local);
138
139 if (!image_root)
140 image_root = "/var/lib/machines";
141
142 p = strjoina(image_root, "/", local);
143
144 if (force_local)
145 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
146
147 r = btrfs_subvol_snapshot(final, p,
148 BTRFS_SNAPSHOT_QUOTA|
149 BTRFS_SNAPSHOT_FALLBACK_COPY|
150 BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
151 BTRFS_SNAPSHOT_RECURSIVE);
152 if (r < 0)
153 return log_error_errno(r, "Failed to create local image: %m");
154
155 log_info("Created new local image '%s'.", local);
156
157 return 0;
158}
159
160static int hash_url(const char *url, char **ret) {
161 uint64_t h;
162 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);
163
164 assert(url);
165
166 h = siphash24(url, strlen(url), k.bytes);
167 if (asprintf(ret, "%"PRIx64, h) < 0)
168 return -ENOMEM;
169
170 return 0;
171}
172
173int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
174 _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
175 char *path;
176
177 assert(url);
178 assert(ret);
179
180 if (!image_root)
181 image_root = "/var/lib/machines";
182
183 escaped_url = xescape(url, FILENAME_ESCAPE);
184 if (!escaped_url)
185 return -ENOMEM;
186
187 if (etag) {
188 escaped_etag = xescape(etag, FILENAME_ESCAPE);
189 if (!escaped_etag)
190 return -ENOMEM;
191 }
192
193 path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
194 strempty(escaped_etag), strempty(suffix));
195 if (!path)
196 return -ENOMEM;
197
198 /* URLs might make the path longer than the maximum allowed length for a file name.
199 * When that happens, a URL hash is used instead. Paths returned by this function
200 * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
201 if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
202 _cleanup_free_ char *hash = NULL;
203 int r;
204
205 free(path);
206
207 r = hash_url(url, &hash);
208 if (r < 0)
209 return r;
210
211 path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
212 strempty(escaped_etag), strempty(suffix));
213 if (!path)
214 return -ENOMEM;
215 }
216
217 *ret = path;
218 return 0;
219}
220
221int pull_make_auxiliary_job(
222 PullJob **ret,
223 const char *url,
224 int (*strip_suffixes)(const char *name, char **ret),
225 const char *suffix,
226 CurlGlue *glue,
227 PullJobFinished on_finished,
228 void *userdata) {
229
230 _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
231 _cleanup_(pull_job_unrefp) PullJob *job = NULL;
232 const char *q;
233 int r;
234
235 assert(ret);
236 assert(url);
237 assert(strip_suffixes);
238 assert(glue);
239
240 r = import_url_last_component(url, &last_component);
241 if (r < 0)
242 return r;
243
244 r = strip_suffixes(last_component, &ll);
245 if (r < 0)
246 return r;
247
248 q = strjoina(ll, suffix);
249
250 r = import_url_change_last_component(url, q, &auxiliary_url);
251 if (r < 0)
252 return r;
253
254 r = pull_job_new(&job, auxiliary_url, glue, userdata);
255 if (r < 0)
256 return r;
257
258 job->on_finished = on_finished;
259 job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
260
261 *ret = TAKE_PTR(job);
262
263 return 0;
264}
265
266int pull_make_verification_jobs(
267 PullJob **ret_checksum_job,
268 PullJob **ret_signature_job,
269 ImportVerify verify,
270 const char *url,
271 CurlGlue *glue,
272 PullJobFinished on_finished,
273 void *userdata) {
274
275 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
276 int r;
277 const char *chksums = NULL;
278
279 assert(ret_checksum_job);
280 assert(ret_signature_job);
281 assert(verify >= 0);
282 assert(verify < _IMPORT_VERIFY_MAX);
283 assert(url);
284 assert(glue);
285
286 if (verify != IMPORT_VERIFY_NO) {
287 _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
288
289 /* Queue jobs for the checksum file for the image. */
290 r = import_url_last_component(url, &fn);
291 if (r < 0)
292 return r;
293
294 chksums = strjoina(fn, ".sha256");
295
296 r = import_url_change_last_component(url, chksums, &checksum_url);
297 if (r < 0)
298 return r;
299
300 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
301 if (r < 0)
302 return r;
303
304 checksum_job->on_finished = on_finished;
305 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
306 }
307
308 if (verify == IMPORT_VERIFY_SIGNATURE) {
309 _cleanup_free_ char *signature_url = NULL;
310
311 /* Queue job for the SHA256SUMS.gpg file for the image. */
312 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
313 if (r < 0)
314 return r;
315
316 r = pull_job_new(&signature_job, signature_url, glue, userdata);
317 if (r < 0)
318 return r;
319
320 signature_job->on_finished = on_finished;
321 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
322 }
323
324 *ret_checksum_job = checksum_job;
325 *ret_signature_job = signature_job;
326
327 checksum_job = signature_job = NULL;
328
329 return 0;
330}
331
332static int verify_one(PullJob *checksum_job, PullJob *job) {
333 _cleanup_free_ char *fn = NULL;
334 const char *line, *p;
335 int r;
336
337 assert(checksum_job);
338
339 if (!job)
340 return 0;
341
342 assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
343
344 /* Don't verify the checksum if we didn't actually successfully download something new */
345 if (job->state != PULL_JOB_DONE)
346 return 0;
347 if (job->error != 0)
348 return 0;
349 if (job->etag_exists)
350 return 0;
351
352 assert(job->calc_checksum);
353 assert(job->checksum);
354
355 r = import_url_last_component(job->url, &fn);
356 if (r < 0)
357 return log_oom();
358
359 if (!filename_is_valid(fn)) {
360 log_error("Cannot verify checksum, could not determine server-side file name.");
361 return -EBADMSG;
362 }
363
364 line = strjoina(job->checksum, " *", fn, "\n");
365
366 p = memmem(checksum_job->payload,
367 checksum_job->payload_size,
368 line,
369 strlen(line));
370
371 if (!p) {
372 line = strjoina(job->checksum, " ", fn, "\n");
373
374 p = memmem(checksum_job->payload,
375 checksum_job->payload_size,
376 line,
377 strlen(line));
378 }
379
380 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
381 log_error("DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn);
382 return -EBADMSG;
383 }
384
385 log_info("SHA256 checksum of %s is valid.", job->url);
386 return 1;
387}
388
389int pull_verify(PullJob *main_job,
390 PullJob *roothash_job,
391 PullJob *settings_job,
392 PullJob *checksum_job,
393 PullJob *signature_job) {
394
395 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
396 _cleanup_close_ int sig_file = -1;
397 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
398 _cleanup_(sigkill_waitp) pid_t pid = 0;
399 bool gpg_home_created = false;
400 int r;
401
402 assert(main_job);
403 assert(main_job->state == PULL_JOB_DONE);
404
405 if (!checksum_job)
406 return 0;
407
408 assert(main_job->calc_checksum);
409 assert(main_job->checksum);
410
411 assert(checksum_job->state == PULL_JOB_DONE);
412
413 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
414 log_error("Checksum is empty, cannot verify.");
415 return -EBADMSG;
416 }
417
418 r = verify_one(checksum_job, main_job);
419 if (r < 0)
420 return r;
421
422 r = verify_one(checksum_job, roothash_job);
423 if (r < 0)
424 return r;
425
426 r = verify_one(checksum_job, settings_job);
427 if (r < 0)
428 return r;
429
430 if (!signature_job)
431 return 0;
432
433 if (checksum_job->style == VERIFICATION_PER_FILE)
434 signature_job = checksum_job;
435
436 assert(signature_job->state == PULL_JOB_DONE);
437
438 if (!signature_job->payload || signature_job->payload_size <= 0) {
439 log_error("Signature is empty, cannot verify.");
440 return -EBADMSG;
441 }
442
443 r = pipe2(gpg_pipe, O_CLOEXEC);
444 if (r < 0)
445 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
446
447 sig_file = mkostemp(sig_file_path, O_RDWR);
448 if (sig_file < 0)
449 return log_error_errno(errno, "Failed to create temporary file: %m");
450
451 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
452 if (r < 0) {
453 log_error_errno(r, "Failed to write to temporary file: %m");
454 goto finish;
455 }
456
457 if (!mkdtemp(gpg_home)) {
458 r = log_error_errno(errno, "Failed to create tempory home for gpg: %m");
459 goto finish;
460 }
461
462 gpg_home_created = true;
463
464 r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
465 if (r < 0)
466 return r;
467 if (r == 0) {
468 const char *cmd[] = {
469 "gpg",
470 "--no-options",
471 "--no-default-keyring",
472 "--no-auto-key-locate",
473 "--no-auto-check-trustdb",
474 "--batch",
475 "--trust-model=always",
476 NULL, /* --homedir= */
477 NULL, /* --keyring= */
478 NULL, /* --verify */
479 NULL, /* signature file */
480 NULL, /* dash */
481 NULL /* trailing NULL */
482 };
483 unsigned k = ELEMENTSOF(cmd) - 6;
484
485 /* Child */
486
487 gpg_pipe[1] = safe_close(gpg_pipe[1]);
488
489 r = rearrange_stdio(gpg_pipe[0], -1, STDERR_FILENO);
490 if (r < 0) {
491 log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
492 _exit(EXIT_FAILURE);
493 }
494
495 cmd[k++] = strjoina("--homedir=", gpg_home);
496
497 /* We add the user keyring only to the command line
498 * arguments, if it's around since gpg fails
499 * otherwise. */
500 if (access(USER_KEYRING_PATH, F_OK) >= 0)
501 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
502 else
503 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
504
505 cmd[k++] = "--verify";
506 if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
507 cmd[k++] = sig_file_path;
508 cmd[k++] = "-";
509 cmd[k++] = NULL;
510 }
511
512 execvp("gpg2", (char * const *) cmd);
513 execvp("gpg", (char * const *) cmd);
514 log_error_errno(errno, "Failed to execute gpg: %m");
515 _exit(EXIT_FAILURE);
516 }
517
518 gpg_pipe[0] = safe_close(gpg_pipe[0]);
519
520 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
521 if (r < 0) {
522 log_error_errno(r, "Failed to write to pipe: %m");
523 goto finish;
524 }
525
526 gpg_pipe[1] = safe_close(gpg_pipe[1]);
527
528 r = wait_for_terminate_and_check("gpg", pid, WAIT_LOG_ABNORMAL);
529 pid = 0;
530 if (r < 0)
531 goto finish;
532 if (r != EXIT_SUCCESS) {
533 log_error("DOWNLOAD INVALID: Signature verification failed.");
534 r = -EBADMSG;
535 } else {
536 log_info("Signature verification succeeded.");
537 r = 0;
538 }
539
540finish:
541 if (sig_file >= 0)
542 (void) unlink(sig_file_path);
543
544 if (gpg_home_created)
545 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
546
547 return r;
548}