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