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