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