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