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