1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2015 Lennart Poettering
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.
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.
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/>.
22 #include <sys/prctl.h>
27 #include "btrfs-util.h"
28 #include "import-job.h"
29 #include "import-util.h"
31 #define FILENAME_ESCAPE "/.#\"\'"
33 bool http_etag_is_valid(const char *etag) {
34 if (!endswith(etag, "\""))
37 if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
43 int import_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) {
44 _cleanup_free_ char *escaped_url = NULL;
45 _cleanup_closedir_ DIR *d = NULL;
46 _cleanup_strv_free_ char **l = NULL;
54 image_root = "/var/lib/machines";
56 escaped_url = xescape(url, FILENAME_ESCAPE);
60 d = opendir(image_root);
62 if (errno == ENOENT) {
70 FOREACH_DIRENT_ALL(de, d, return -errno) {
74 if (de->d_type != DT_UNKNOWN &&
79 a = startswith(de->d_name, prefix);
85 a = startswith(a, escaped_url);
89 a = startswith(a, ".");
94 b = endswith(de->d_name, suffix);
98 b = strchr(de->d_name, 0);
103 u = cunescape_length(a, b - a);
107 if (!http_etag_is_valid(u)) {
112 r = strv_consume(&l, u);
123 int import_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
131 image_root = "/var/lib/machines";
133 p = strappenda(image_root, "/", local);
136 (void) btrfs_subvol_remove(p);
137 (void) rm_rf_dangerous(p, false, true, false);
140 r = btrfs_subvol_snapshot(final, p, false, false);
142 r = copy_tree(final, p, false);
144 return log_error_errno(r, "Failed to copy image: %m");
146 return log_error_errno(r, "Failed to create local image: %m");
148 log_info("Created new local image '%s'.", local);
153 int import_make_read_only_fd(int fd) {
158 /* First, let's make this a read-only subvolume if it refers
160 r = btrfs_subvol_set_read_only_fd(fd, true);
161 if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) {
164 /* This doesn't refer to a subvolume, or the file
165 * system isn't even btrfs. In that, case fall back to
170 return log_error_errno(errno, "Failed to stat temporary image: %m");
173 if (fchmod(fd, st.st_mode & 07555) < 0)
174 return log_error_errno(errno, "Failed to chmod() final image: %m");
179 return log_error_errno(r, "Failed to make subvolume read-only: %m");
184 int import_make_read_only(const char *path) {
185 _cleanup_close_ int fd = 1;
187 fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
189 return log_error_errno(errno, "Failed to open %s: %m", path);
191 return import_make_read_only_fd(fd);
194 int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
195 _cleanup_free_ char *escaped_url = NULL;
202 image_root = "/var/lib/machines";
204 escaped_url = xescape(url, FILENAME_ESCAPE);
209 _cleanup_free_ char *escaped_etag = NULL;
211 escaped_etag = xescape(etag, FILENAME_ESCAPE);
215 path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL);
217 path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL);
225 int import_url_last_component(const char *url, char **ret) {
229 e = strchrnul(url, '?');
231 while (e > url && e[-1] == '/')
235 while (p > url && p[-1] != '/')
241 s = strndup(p, e - p);
250 int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
257 e = strchrnul(url, '?');
259 while (e > url && e[-1] == '/')
262 while (e > url && e[-1] != '/')
268 s = new(char, (e - url) + strlen(suffix) + 1);
272 strcpy(mempcpy(s, url, e - url), suffix);
277 static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = {
278 [IMPORT_VERIFY_NO] = "no",
279 [IMPORT_VERIFY_SUM] = "sum",
280 [IMPORT_VERIFY_SIGNATURE] = "signature",
283 DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify);
285 int import_make_verification_jobs(
286 ImportJob **ret_checksum_job,
287 ImportJob **ret_signature_job,
291 ImportJobFinished on_finished,
294 _cleanup_(import_job_unrefp) ImportJob *checksum_job = NULL, *signature_job = NULL;
297 assert(ret_checksum_job);
298 assert(ret_signature_job);
300 assert(verify < _IMPORT_VERIFY_MAX);
304 if (verify != IMPORT_VERIFY_NO) {
305 _cleanup_free_ char *checksum_url = NULL;
307 /* Queue job for the SHA256SUMS file for the image */
308 r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url);
312 r = import_job_new(&checksum_job, checksum_url, glue, userdata);
316 checksum_job->on_finished = on_finished;
317 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
320 if (verify == IMPORT_VERIFY_SIGNATURE) {
321 _cleanup_free_ char *signature_url = NULL;
323 /* Queue job for the SHA256SUMS.gpg file for the image. */
324 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
328 r = import_job_new(&signature_job, signature_url, glue, userdata);
332 signature_job->on_finished = on_finished;
333 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
336 *ret_checksum_job = checksum_job;
337 *ret_signature_job = signature_job;
339 checksum_job = signature_job = NULL;
346 ImportJob *checksum_job,
347 ImportJob *signature_job) {
349 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
350 _cleanup_free_ char *fn = NULL;
351 _cleanup_close_ int sig_file = -1;
352 const char *p, *line;
353 char sig_file_path[] = "/tmp/sigXXXXXX";
354 _cleanup_sigkill_wait_ pid_t pid = 0;
358 assert(main_job->state == IMPORT_JOB_DONE);
363 assert(main_job->calc_checksum);
364 assert(main_job->checksum);
365 assert(checksum_job->state == IMPORT_JOB_DONE);
367 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
368 log_error("Checksum is empty, cannot verify.");
372 r = import_url_last_component(main_job->url, &fn);
376 if (!filename_is_valid(fn)) {
377 log_error("Cannot verify checksum, could not determine valid server-side file name.");
381 line = strappenda(main_job->checksum, " *", fn, "\n");
383 p = memmem(checksum_job->payload,
384 checksum_job->payload_size,
388 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
389 log_error("Checksum did not check out, payload has been tempered with.");
393 log_info("SHA256 checksum of %s is valid.", main_job->url);
398 assert(signature_job->state == IMPORT_JOB_DONE);
400 if (!signature_job->payload || signature_job->payload_size <= 0) {
401 log_error("Signature is empty, cannot verify.");
405 r = pipe2(gpg_pipe, O_CLOEXEC);
407 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
409 sig_file = mkostemp(sig_file_path, O_RDWR);
411 return log_error_errno(errno, "Failed to create temporary file: %m");
413 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
415 log_error_errno(r, "Failed to write to temporary file: %m");
421 return log_error_errno(errno, "Failed to fork off gpg: %m");
423 const char *cmd[] = {
426 "--no-default-keyring",
427 "--no-auto-key-locate",
428 "--no-auto-check-trustdb",
430 "--trust-model=always",
431 "--keyring=" VENDOR_KEYRING_PATH,
432 NULL, /* maybe user keyring */
434 NULL, /* signature file */
436 NULL /* trailing NULL */
438 unsigned k = ELEMENTSOF(cmd) - 5;
443 reset_all_signal_handlers();
445 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
447 gpg_pipe[1] = safe_close(gpg_pipe[1]);
449 if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
450 log_error_errno(errno, "Failed to dup2() fd: %m");
454 if (gpg_pipe[0] != STDIN_FILENO)
455 gpg_pipe[0] = safe_close(gpg_pipe[0]);
457 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
459 log_error_errno(errno, "Failed to open /dev/null: %m");
463 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
464 log_error_errno(errno, "Failed to dup2() fd: %m");
468 if (null_fd != STDOUT_FILENO)
469 null_fd = safe_close(null_fd);
471 /* We add the user keyring only to the command line
472 * arguments, if it's around since gpg fails
474 if (access(USER_KEYRING_PATH, F_OK) >= 0)
475 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
477 cmd[k++] = "--verify";
478 cmd[k++] = sig_file_path;
482 execvp("gpg", (char * const *) cmd);
483 log_error_errno(errno, "Failed to execute gpg: %m");
487 gpg_pipe[0] = safe_close(gpg_pipe[0]);
489 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
491 log_error_errno(r, "Failed to write to pipe: %m");
495 gpg_pipe[1] = safe_close(gpg_pipe[1]);
497 r = wait_for_terminate_and_warn("gpg", pid, true);
502 log_error("Signature verification failed.");
505 log_info("Signature verification succeeded.");
511 unlink(sig_file_path);