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