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