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