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