From 3d7415f43f0fe6a821d7bc4a341ba371e8a30ef3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 22 Jan 2015 03:57:15 +0100 Subject: import: introduce new mini-daemon systemd-importd, and make machinectl a client to it The old "systemd-import" binary is now an internal tool. We still use it as asynchronous backend for systemd-importd. Since the import tool might require some IO and CPU resources (due to qcow2 explosion, and decompression), and because we might want to run it with more minimal priviliges we still keep it around as the worker binary to execute as child process of importd. machinectl now has verbs for pulling down images, cancelling them and listing them. --- src/import/.gitignore | 1 + src/import/import-common.c | 448 +++++++++++++ src/import/import-common.h | 39 ++ src/import/import-dkr.c | 34 +- src/import/import-dkr.h | 4 - src/import/import-raw.c | 3 +- src/import/import-tar.c | 3 +- src/import/import-util.c | 514 -------------- src/import/import-util.h | 54 -- src/import/import.c | 104 +-- src/import/importd.c | 958 +++++++++++++++++++++++++++ src/import/org.freedesktop.import1.conf | 62 ++ src/import/org.freedesktop.import1.policy.in | 29 + src/import/org.freedesktop.import1.service | 12 + src/journal/journald-syslog.c | 40 -- src/journal/journald-syslog.h | 1 - src/libsystemd/sd-bus/bus-common-errors.c | 3 + src/libsystemd/sd-bus/bus-common-errors.h | 3 + src/machine/machinectl.c | 553 +++++++++++++++- src/shared/import-util.c | 182 +++++ src/shared/import-util.h | 47 ++ src/shared/util.c | 55 ++ src/shared/util.h | 4 + 23 files changed, 2396 insertions(+), 757 deletions(-) create mode 100644 src/import/.gitignore create mode 100644 src/import/import-common.c create mode 100644 src/import/import-common.h delete mode 100644 src/import/import-util.c delete mode 100644 src/import/import-util.h create mode 100644 src/import/importd.c create mode 100644 src/import/org.freedesktop.import1.conf create mode 100644 src/import/org.freedesktop.import1.policy.in create mode 100644 src/import/org.freedesktop.import1.service create mode 100644 src/shared/import-util.c create mode 100644 src/shared/import-util.h (limited to 'src') diff --git a/src/import/.gitignore b/src/import/.gitignore new file mode 100644 index 0000000000..01106e2e68 --- /dev/null +++ b/src/import/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.import1.policy diff --git a/src/import/import-common.c b/src/import/import-common.c new file mode 100644 index 0000000000..395f998f16 --- /dev/null +++ b/src/import/import-common.c @@ -0,0 +1,448 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "util.h" +#include "strv.h" +#include "copy.h" +#include "btrfs-util.h" +#include "import-job.h" +#include "import-common.h" + +#define FILENAME_ESCAPE "/.#\"\'" + +int import_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) { + _cleanup_free_ char *escaped_url = NULL; + _cleanup_closedir_ DIR *d = NULL; + _cleanup_strv_free_ char **l = NULL; + struct dirent *de; + int r; + + assert(url); + assert(etags); + + if (!image_root) + image_root = "/var/lib/machines"; + + escaped_url = xescape(url, FILENAME_ESCAPE); + if (!escaped_url) + return -ENOMEM; + + d = opendir(image_root); + if (!d) { + if (errno == ENOENT) { + *etags = NULL; + return 0; + } + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + const char *a, *b; + char *u; + + if (de->d_type != DT_UNKNOWN && + de->d_type != dt) + continue; + + if (prefix) { + a = startswith(de->d_name, prefix); + if (!a) + continue; + } else + a = de->d_name; + + a = startswith(a, escaped_url); + if (!a) + continue; + + a = startswith(a, "."); + if (!a) + continue; + + if (suffix) { + b = endswith(de->d_name, suffix); + if (!b) + continue; + } else + b = strchr(de->d_name, 0); + + if (a >= b) + continue; + + u = cunescape_length(a, b - a); + if (!u) + return -ENOMEM; + + if (!http_etag_is_valid(u)) { + free(u); + continue; + } + + r = strv_consume(&l, u); + if (r < 0) + return r; + } + + *etags = l; + l = NULL; + + return 0; +} + +int import_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) { + const char *p; + int r; + + assert(final); + assert(local); + + if (!image_root) + image_root = "/var/lib/machines"; + + p = strappenda(image_root, "/", local); + + if (force_local) { + (void) btrfs_subvol_remove(p); + (void) rm_rf_dangerous(p, false, true, false); + } + + r = btrfs_subvol_snapshot(final, p, false, false); + if (r == -ENOTTY) { + r = copy_tree(final, p, false); + if (r < 0) + return log_error_errno(r, "Failed to copy image: %m"); + } else if (r < 0) + return log_error_errno(r, "Failed to create local image: %m"); + + log_info("Created new local image '%s'.", local); + + return 0; +} + +int import_make_read_only_fd(int fd) { + int r; + + assert(fd >= 0); + + /* First, let's make this a read-only subvolume if it refers + * to a subvolume */ + r = btrfs_subvol_set_read_only_fd(fd, true); + if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) { + struct stat st; + + /* This doesn't refer to a subvolume, or the file + * system isn't even btrfs. In that, case fall back to + * chmod()ing */ + + r = fstat(fd, &st); + if (r < 0) + return log_error_errno(errno, "Failed to stat temporary image: %m"); + + /* Drop "w" flag */ + if (fchmod(fd, st.st_mode & 07555) < 0) + return log_error_errno(errno, "Failed to chmod() final image: %m"); + + return 0; + + } else if (r < 0) + return log_error_errno(r, "Failed to make subvolume read-only: %m"); + + return 0; +} + +int import_make_read_only(const char *path) { + _cleanup_close_ int fd = 1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + return import_make_read_only_fd(fd); +} + +int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) { + _cleanup_free_ char *escaped_url = NULL; + char *path; + + assert(url); + assert(ret); + + if (!image_root) + image_root = "/var/lib/machines"; + + escaped_url = xescape(url, FILENAME_ESCAPE); + if (!escaped_url) + return -ENOMEM; + + if (etag) { + _cleanup_free_ char *escaped_etag = NULL; + + escaped_etag = xescape(etag, FILENAME_ESCAPE); + if (!escaped_etag) + return -ENOMEM; + + path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL); + } else + path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL); + if (!path) + return -ENOMEM; + + *ret = path; + return 0; +} + +int import_make_verification_jobs( + ImportJob **ret_checksum_job, + ImportJob **ret_signature_job, + ImportVerify verify, + const char *url, + CurlGlue *glue, + ImportJobFinished on_finished, + void *userdata) { + + _cleanup_(import_job_unrefp) ImportJob *checksum_job = NULL, *signature_job = NULL; + int r; + + assert(ret_checksum_job); + assert(ret_signature_job); + assert(verify >= 0); + assert(verify < _IMPORT_VERIFY_MAX); + assert(url); + assert(glue); + + if (verify != IMPORT_VERIFY_NO) { + _cleanup_free_ char *checksum_url = NULL; + + /* Queue job for the SHA256SUMS file for the image */ + r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url); + if (r < 0) + return r; + + r = import_job_new(&checksum_job, checksum_url, glue, userdata); + if (r < 0) + return r; + + checksum_job->on_finished = on_finished; + checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL; + } + + if (verify == IMPORT_VERIFY_SIGNATURE) { + _cleanup_free_ char *signature_url = NULL; + + /* Queue job for the SHA256SUMS.gpg file for the image. */ + r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url); + if (r < 0) + return r; + + r = import_job_new(&signature_job, signature_url, glue, userdata); + if (r < 0) + return r; + + signature_job->on_finished = on_finished; + signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; + } + + *ret_checksum_job = checksum_job; + *ret_signature_job = signature_job; + + checksum_job = signature_job = NULL; + + return 0; +} + +int import_verify( + ImportJob *main_job, + ImportJob *checksum_job, + ImportJob *signature_job) { + + _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; + _cleanup_free_ char *fn = NULL; + _cleanup_close_ int sig_file = -1; + const char *p, *line; + char sig_file_path[] = "/tmp/sigXXXXXX"; + _cleanup_sigkill_wait_ pid_t pid = 0; + int r; + + assert(main_job); + assert(main_job->state == IMPORT_JOB_DONE); + + if (!checksum_job) + return 0; + + assert(main_job->calc_checksum); + assert(main_job->checksum); + assert(checksum_job->state == IMPORT_JOB_DONE); + + if (!checksum_job->payload || checksum_job->payload_size <= 0) { + log_error("Checksum is empty, cannot verify."); + return -EBADMSG; + } + + r = import_url_last_component(main_job->url, &fn); + if (r < 0) + return log_oom(); + + if (!filename_is_valid(fn)) { + log_error("Cannot verify checksum, could not determine valid server-side file name."); + return -EBADMSG; + } + + line = strappenda(main_job->checksum, " *", fn, "\n"); + + p = memmem(checksum_job->payload, + checksum_job->payload_size, + line, + strlen(line)); + + if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { + log_error("Checksum did not check out, payload has been tempered with."); + return -EBADMSG; + } + + log_info("SHA256 checksum of %s is valid.", main_job->url); + + if (!signature_job) + return 0; + + assert(signature_job->state == IMPORT_JOB_DONE); + + if (!signature_job->payload || signature_job->payload_size <= 0) { + log_error("Signature is empty, cannot verify."); + return -EBADMSG; + } + + r = pipe2(gpg_pipe, O_CLOEXEC); + if (r < 0) + return log_error_errno(errno, "Failed to create pipe for gpg: %m"); + + sig_file = mkostemp(sig_file_path, O_RDWR); + if (sig_file < 0) + return log_error_errno(errno, "Failed to create temporary file: %m"); + + r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false); + if (r < 0) { + log_error_errno(r, "Failed to write to temporary file: %m"); + goto finish; + } + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off gpg: %m"); + if (pid == 0) { + const char *cmd[] = { + "gpg", + "--no-options", + "--no-default-keyring", + "--no-auto-key-locate", + "--no-auto-check-trustdb", + "--batch", + "--trust-model=always", + "--keyring=" VENDOR_KEYRING_PATH, + NULL, /* maybe user keyring */ + NULL, /* --verify */ + NULL, /* signature file */ + NULL, /* dash */ + NULL /* trailing NULL */ + }; + unsigned k = ELEMENTSOF(cmd) - 5; + int null_fd; + + /* Child */ + + reset_all_signal_handlers(); + reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + gpg_pipe[1] = safe_close(gpg_pipe[1]); + + if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (gpg_pipe[0] != STDIN_FILENO) + gpg_pipe[0] = safe_close(gpg_pipe[0]); + + null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDOUT_FILENO) + null_fd = safe_close(null_fd); + + /* We add the user keyring only to the command line + * arguments, if it's around since gpg fails + * otherwise. */ + if (access(USER_KEYRING_PATH, F_OK) >= 0) + cmd[k++] = "--keyring=" USER_KEYRING_PATH; + + cmd[k++] = "--verify"; + cmd[k++] = sig_file_path; + cmd[k++] = "-"; + cmd[k++] = NULL; + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + execvp("gpg", (char * const *) cmd); + log_error_errno(errno, "Failed to execute gpg: %m"); + _exit(EXIT_FAILURE); + } + + gpg_pipe[0] = safe_close(gpg_pipe[0]); + + r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false); + if (r < 0) { + log_error_errno(r, "Failed to write to pipe: %m"); + goto finish; + } + + gpg_pipe[1] = safe_close(gpg_pipe[1]); + + r = wait_for_terminate_and_warn("gpg", pid, true); + pid = 0; + if (r < 0) + goto finish; + if (r > 0) { + log_error("Signature verification failed."); + r = -EBADMSG; + } else { + log_info("Signature verification succeeded."); + r = 0; + } + +finish: + if (sig_file >= 0) + unlink(sig_file_path); + + return r; +} diff --git a/src/import/import-common.h b/src/import/import-common.h new file mode 100644 index 0000000000..39ac6acd08 --- /dev/null +++ b/src/import/import-common.h @@ -0,0 +1,39 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "import-job.h" +#include "import-util.h" + +int import_make_local_copy(const char *final, const char *root, const char *local, bool force_local); + +int import_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); + +int import_make_read_only_fd(int fd); +int import_make_read_only(const char *path); + +int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); + +int import_make_verification_jobs(ImportJob **ret_checksum_job, ImportJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, ImportJobFinished on_finished, void *userdata); +int import_verify(ImportJob *main_job, ImportJob *checksum_job, ImportJob *signature_job); diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c index 1a6cd4eaff..0e99275090 100644 --- a/src/import/import-dkr.c +++ b/src/import/import-dkr.c @@ -28,10 +28,11 @@ #include "btrfs-util.h" #include "utf8.h" #include "mkdir.h" +#include "import-util.h" #include "curl-util.h" #include "aufs-util.h" -#include "import-util.h" #include "import-job.h" +#include "import-common.h" #include "import-dkr.h" struct DkrImport { @@ -854,34 +855,3 @@ int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char return import_job_begin(i->images_job); } - -bool dkr_name_is_valid(const char *name) { - const char *slash, *p; - - if (isempty(name)) - return false; - - slash = strchr(name, '/'); - if (!slash) - return false; - - if (!filename_is_valid(slash + 1)) - return false; - - p = strndupa(name, slash - name); - if (!filename_is_valid(p)) - return false; - - return true; -} - -bool dkr_id_is_valid(const char *id) { - - if (!filename_is_valid(id)) - return false; - - if (!in_charset(id, "0123456789abcdef")) - return false; - - return true; -} diff --git a/src/import/import-dkr.h b/src/import/import-dkr.h index b56d83f38f..51aacbe90b 100644 --- a/src/import/import-dkr.h +++ b/src/import/import-dkr.h @@ -32,7 +32,3 @@ DkrImport* dkr_import_unref(DkrImport *import); DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImport*, dkr_import_unref); int dkr_import_pull(DkrImport *import, const char *name, const char *tag, const char *local, bool force_local); - -bool dkr_name_is_valid(const char *name); -bool dkr_id_is_valid(const char *id); -#define dkr_tag_is_valid(tag) filename_is_valid(tag) diff --git a/src/import/import-raw.c b/src/import/import-raw.c index edcd8864fa..21e2488d55 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -30,10 +30,11 @@ #include "util.h" #include "macro.h" #include "mkdir.h" +#include "import-util.h" #include "curl-util.h" #include "qcow2-util.h" #include "import-job.h" -#include "import-util.h" +#include "import-common.h" #include "import-raw.h" typedef struct RawImportFile RawImportFile; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 15482b48f0..e311ad277f 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -29,9 +29,10 @@ #include "util.h" #include "macro.h" #include "mkdir.h" +#include "import-util.h" #include "curl-util.h" #include "import-job.h" -#include "import-util.h" +#include "import-common.h" #include "import-tar.h" struct TarImport { diff --git a/src/import/import-util.c b/src/import/import-util.c deleted file mode 100644 index 0330bc1fb2..0000000000 --- a/src/import/import-util.c +++ /dev/null @@ -1,514 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2015 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include - -#include "util.h" -#include "strv.h" -#include "copy.h" -#include "btrfs-util.h" -#include "import-job.h" -#include "import-util.h" - -#define FILENAME_ESCAPE "/.#\"\'" - -bool http_etag_is_valid(const char *etag) { - if (!endswith(etag, "\"")) - return false; - - if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) - return false; - - return true; -} - -int import_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) { - _cleanup_free_ char *escaped_url = NULL; - _cleanup_closedir_ DIR *d = NULL; - _cleanup_strv_free_ char **l = NULL; - struct dirent *de; - int r; - - assert(url); - assert(etags); - - if (!image_root) - image_root = "/var/lib/machines"; - - escaped_url = xescape(url, FILENAME_ESCAPE); - if (!escaped_url) - return -ENOMEM; - - d = opendir(image_root); - if (!d) { - if (errno == ENOENT) { - *etags = NULL; - return 0; - } - - return -errno; - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - const char *a, *b; - char *u; - - if (de->d_type != DT_UNKNOWN && - de->d_type != dt) - continue; - - if (prefix) { - a = startswith(de->d_name, prefix); - if (!a) - continue; - } else - a = de->d_name; - - a = startswith(a, escaped_url); - if (!a) - continue; - - a = startswith(a, "."); - if (!a) - continue; - - if (suffix) { - b = endswith(de->d_name, suffix); - if (!b) - continue; - } else - b = strchr(de->d_name, 0); - - if (a >= b) - continue; - - u = cunescape_length(a, b - a); - if (!u) - return -ENOMEM; - - if (!http_etag_is_valid(u)) { - free(u); - continue; - } - - r = strv_consume(&l, u); - if (r < 0) - return r; - } - - *etags = l; - l = NULL; - - return 0; -} - -int import_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) { - const char *p; - int r; - - assert(final); - assert(local); - - if (!image_root) - image_root = "/var/lib/machines"; - - p = strappenda(image_root, "/", local); - - if (force_local) { - (void) btrfs_subvol_remove(p); - (void) rm_rf_dangerous(p, false, true, false); - } - - r = btrfs_subvol_snapshot(final, p, false, false); - if (r == -ENOTTY) { - r = copy_tree(final, p, false); - if (r < 0) - return log_error_errno(r, "Failed to copy image: %m"); - } else if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); - - log_info("Created new local image '%s'.", local); - - return 0; -} - -int import_make_read_only_fd(int fd) { - int r; - - assert(fd >= 0); - - /* First, let's make this a read-only subvolume if it refers - * to a subvolume */ - r = btrfs_subvol_set_read_only_fd(fd, true); - if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) { - struct stat st; - - /* This doesn't refer to a subvolume, or the file - * system isn't even btrfs. In that, case fall back to - * chmod()ing */ - - r = fstat(fd, &st); - if (r < 0) - return log_error_errno(errno, "Failed to stat temporary image: %m"); - - /* Drop "w" flag */ - if (fchmod(fd, st.st_mode & 07555) < 0) - return log_error_errno(errno, "Failed to chmod() final image: %m"); - - return 0; - - } else if (r < 0) - return log_error_errno(r, "Failed to make subvolume read-only: %m"); - - return 0; -} - -int import_make_read_only(const char *path) { - _cleanup_close_ int fd = 1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - - return import_make_read_only_fd(fd); -} - -int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) { - _cleanup_free_ char *escaped_url = NULL; - char *path; - - assert(url); - assert(ret); - - if (!image_root) - image_root = "/var/lib/machines"; - - escaped_url = xescape(url, FILENAME_ESCAPE); - if (!escaped_url) - return -ENOMEM; - - if (etag) { - _cleanup_free_ char *escaped_etag = NULL; - - escaped_etag = xescape(etag, FILENAME_ESCAPE); - if (!escaped_etag) - return -ENOMEM; - - path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL); - } else - path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL); - if (!path) - return -ENOMEM; - - *ret = path; - return 0; -} - -int import_url_last_component(const char *url, char **ret) { - const char *e, *p; - char *s; - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - p = e; - while (p > url && p[-1] != '/') - p--; - - if (e <= p) - return -EINVAL; - - s = strndup(p, e - p); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - - -int import_url_change_last_component(const char *url, const char *suffix, char **ret) { - const char *e; - char *s; - - assert(url); - assert(ret); - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - while (e > url && e[-1] != '/') - e--; - - if (e <= url) - return -EINVAL; - - s = new(char, (e - url) + strlen(suffix) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, url, e - url), suffix); - *ret = s; - return 0; -} - -static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { - [IMPORT_VERIFY_NO] = "no", - [IMPORT_VERIFY_SUM] = "sum", - [IMPORT_VERIFY_SIGNATURE] = "signature", -}; - -DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); - -int import_make_verification_jobs( - ImportJob **ret_checksum_job, - ImportJob **ret_signature_job, - ImportVerify verify, - const char *url, - CurlGlue *glue, - ImportJobFinished on_finished, - void *userdata) { - - _cleanup_(import_job_unrefp) ImportJob *checksum_job = NULL, *signature_job = NULL; - int r; - - assert(ret_checksum_job); - assert(ret_signature_job); - assert(verify >= 0); - assert(verify < _IMPORT_VERIFY_MAX); - assert(url); - assert(glue); - - if (verify != IMPORT_VERIFY_NO) { - _cleanup_free_ char *checksum_url = NULL; - - /* Queue job for the SHA256SUMS file for the image */ - r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url); - if (r < 0) - return r; - - r = import_job_new(&checksum_job, checksum_url, glue, userdata); - if (r < 0) - return r; - - checksum_job->on_finished = on_finished; - checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL; - } - - if (verify == IMPORT_VERIFY_SIGNATURE) { - _cleanup_free_ char *signature_url = NULL; - - /* Queue job for the SHA256SUMS.gpg file for the image. */ - r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url); - if (r < 0) - return r; - - r = import_job_new(&signature_job, signature_url, glue, userdata); - if (r < 0) - return r; - - signature_job->on_finished = on_finished; - signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL; - } - - *ret_checksum_job = checksum_job; - *ret_signature_job = signature_job; - - checksum_job = signature_job = NULL; - - return 0; -} - -int import_verify( - ImportJob *main_job, - ImportJob *checksum_job, - ImportJob *signature_job) { - - _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; - _cleanup_free_ char *fn = NULL; - _cleanup_close_ int sig_file = -1; - const char *p, *line; - char sig_file_path[] = "/tmp/sigXXXXXX"; - _cleanup_sigkill_wait_ pid_t pid = 0; - int r; - - assert(main_job); - assert(main_job->state == IMPORT_JOB_DONE); - - if (!checksum_job) - return 0; - - assert(main_job->calc_checksum); - assert(main_job->checksum); - assert(checksum_job->state == IMPORT_JOB_DONE); - - if (!checksum_job->payload || checksum_job->payload_size <= 0) { - log_error("Checksum is empty, cannot verify."); - return -EBADMSG; - } - - r = import_url_last_component(main_job->url, &fn); - if (r < 0) - return log_oom(); - - if (!filename_is_valid(fn)) { - log_error("Cannot verify checksum, could not determine valid server-side file name."); - return -EBADMSG; - } - - line = strappenda(main_job->checksum, " *", fn, "\n"); - - p = memmem(checksum_job->payload, - checksum_job->payload_size, - line, - strlen(line)); - - if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { - log_error("Checksum did not check out, payload has been tempered with."); - return -EBADMSG; - } - - log_info("SHA256 checksum of %s is valid.", main_job->url); - - if (!signature_job) - return 0; - - assert(signature_job->state == IMPORT_JOB_DONE); - - if (!signature_job->payload || signature_job->payload_size <= 0) { - log_error("Signature is empty, cannot verify."); - return -EBADMSG; - } - - r = pipe2(gpg_pipe, O_CLOEXEC); - if (r < 0) - return log_error_errno(errno, "Failed to create pipe for gpg: %m"); - - sig_file = mkostemp(sig_file_path, O_RDWR); - if (sig_file < 0) - return log_error_errno(errno, "Failed to create temporary file: %m"); - - r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false); - if (r < 0) { - log_error_errno(r, "Failed to write to temporary file: %m"); - goto finish; - } - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off gpg: %m"); - if (pid == 0) { - const char *cmd[] = { - "gpg", - "--no-options", - "--no-default-keyring", - "--no-auto-key-locate", - "--no-auto-check-trustdb", - "--batch", - "--trust-model=always", - "--keyring=" VENDOR_KEYRING_PATH, - NULL, /* maybe user keyring */ - NULL, /* --verify */ - NULL, /* signature file */ - NULL, /* dash */ - NULL /* trailing NULL */ - }; - unsigned k = ELEMENTSOF(cmd) - 5; - int null_fd; - - /* Child */ - - reset_all_signal_handlers(); - reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - gpg_pipe[1] = safe_close(gpg_pipe[1]); - - if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (gpg_pipe[0] != STDIN_FILENO) - gpg_pipe[0] = safe_close(gpg_pipe[0]); - - null_fd = open("/dev/null", O_WRONLY|O_NOCTTY); - if (null_fd < 0) { - log_error_errno(errno, "Failed to open /dev/null: %m"); - _exit(EXIT_FAILURE); - } - - if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) { - log_error_errno(errno, "Failed to dup2() fd: %m"); - _exit(EXIT_FAILURE); - } - - if (null_fd != STDOUT_FILENO) - null_fd = safe_close(null_fd); - - /* We add the user keyring only to the command line - * arguments, if it's around since gpg fails - * otherwise. */ - if (access(USER_KEYRING_PATH, F_OK) >= 0) - cmd[k++] = "--keyring=" USER_KEYRING_PATH; - - cmd[k++] = "--verify"; - cmd[k++] = sig_file_path; - cmd[k++] = "-"; - cmd[k++] = NULL; - - execvp("gpg", (char * const *) cmd); - log_error_errno(errno, "Failed to execute gpg: %m"); - _exit(EXIT_FAILURE); - } - - gpg_pipe[0] = safe_close(gpg_pipe[0]); - - r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false); - if (r < 0) { - log_error_errno(r, "Failed to write to pipe: %m"); - goto finish; - } - - gpg_pipe[1] = safe_close(gpg_pipe[1]); - - r = wait_for_terminate_and_warn("gpg", pid, true); - pid = 0; - if (r < 0) - goto finish; - if (r > 0) { - log_error("Signature verification failed."); - r = -EBADMSG; - } else { - log_info("Signature verification succeeded."); - r = 0; - } - -finish: - if (sig_file >= 0) - unlink(sig_file_path); - - return r; -} diff --git a/src/import/import-util.h b/src/import/import-util.h deleted file mode 100644 index 6c63fc32a4..0000000000 --- a/src/import/import-util.h +++ /dev/null @@ -1,54 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include - -#include "import-job.h" - -typedef enum ImportVerify { - IMPORT_VERIFY_NO, - IMPORT_VERIFY_SUM, - IMPORT_VERIFY_SIGNATURE, - _IMPORT_VERIFY_MAX, - _IMPORT_VERIFY_INVALID = -1, -} ImportVerify; - -bool http_etag_is_valid(const char *etag); - -int import_make_local_copy(const char *final, const char *root, const char *local, bool force_local); - -int import_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); - -int import_make_read_only_fd(int fd); -int import_make_read_only(const char *path); - -int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); - -int import_url_last_component(const char *url, char **ret); -int import_url_change_last_component(const char *url, const char *suffix, char **ret); - -const char* import_verify_to_string(ImportVerify v) _const_; -ImportVerify import_verify_from_string(const char *s) _pure_; - -int import_make_verification_jobs(ImportJob **ret_checksum_job, ImportJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, ImportJobFinished on_finished, void *userdata); -int import_verify(ImportJob *main_job, ImportJob *checksum_job, ImportJob *signature_job); diff --git a/src/import/import.c b/src/import/import.c index 4c36108c2e..5b1d0c1417 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -36,6 +36,12 @@ static const char *arg_image_root = "/var/lib/machines"; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static const char* arg_dkr_index_url = DEFAULT_DKR_INDEX_URL; +static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + log_notice("Transfer aborted."); + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + static void on_tar_finished(TarImport *import, int error, void *userdata) { sd_event *event = userdata; assert(import); @@ -43,34 +49,7 @@ static void on_tar_finished(TarImport *import, int error, void *userdata) { if (error == 0) log_info("Operation completed successfully."); - sd_event_exit(event, EXIT_FAILURE); -} - -static int strip_tar_suffixes(const char *name, char **ret) { - const char *e; - char *s; - - e = endswith(name, ".tar"); - if (!e) - e = endswith(name, ".tar.xz"); - if (!e) - e = endswith(name, ".tar.gz"); - if (!e) - e = endswith(name, ".tar.bz2"); - if (!e) - e = endswith(name, ".tgz"); - if (!e) - e = strchr(name, 0); - - if (e <= name) - return -EINVAL; - - s = strndup(name, e - name); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; + sd_event_exit(event, abs(error)); } static int pull_tar(int argc, char *argv[], void *userdata) { @@ -100,7 +79,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { local = NULL; if (local) { - r = strip_tar_suffixes(local, &ll); + r = tar_strip_suffixes(local, &ll); if (r < 0) return log_oom(); @@ -130,8 +109,8 @@ static int pull_tar(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate event loop: %m"); assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event); if (r < 0) @@ -146,8 +125,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to run event loop: %m"); log_info("Exiting."); - - return r; + return -r; } static void on_raw_finished(RawImport *import, int error, void *userdata) { @@ -157,47 +135,7 @@ static void on_raw_finished(RawImport *import, int error, void *userdata) { if (error == 0) log_info("Operation completed successfully."); - sd_event_exit(event, EXIT_FAILURE); -} - -static int strip_raw_suffixes(const char *p, char **ret) { - static const char suffixes[] = - ".xz\0" - ".gz\0" - ".bz2\0" - ".raw\0" - ".qcow2\0" - ".img\0" - ".bin\0"; - - _cleanup_free_ char *q = NULL; - - q = strdup(p); - if (!q) - return -ENOMEM; - - for (;;) { - const char *sfx; - bool changed = false; - - NULSTR_FOREACH(sfx, suffixes) { - char *e; - - e = endswith(q, sfx); - if (e) { - *e = 0; - changed = true; - } - } - - if (!changed) - break; - } - - *ret = q; - q = NULL; - - return 0; + sd_event_exit(event, abs(error)); } static int pull_raw(int argc, char *argv[], void *userdata) { @@ -227,7 +165,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { local = NULL; if (local) { - r = strip_raw_suffixes(local, &ll); + r = raw_strip_suffixes(local, &ll); if (r < 0) return log_oom(); @@ -257,8 +195,8 @@ static int pull_raw(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate event loop: %m"); assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event); if (r < 0) @@ -273,8 +211,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to run event loop: %m"); log_info("Exiting."); - - return r; + return -r; } static void on_dkr_finished(DkrImport *import, int error, void *userdata) { @@ -284,7 +221,7 @@ static void on_dkr_finished(DkrImport *import, int error, void *userdata) { if (error == 0) log_info("Operation completed successfully."); - sd_event_exit(event, EXIT_FAILURE); + sd_event_exit(event, abs(error)); } static int pull_dkr(int argc, char *argv[], void *userdata) { @@ -360,8 +297,8 @@ static int pull_dkr(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate event loop: %m"); assert_se(sigprocmask_many(SIG_BLOCK, SIGTERM, SIGINT, -1) == 0); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); + sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); r = dkr_import_new(&import, event, arg_dkr_index_url, arg_image_root, on_dkr_finished, event); if (r < 0) @@ -376,8 +313,7 @@ static int pull_dkr(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to run event loop: %m"); log_info("Exiting."); - - return 0; + return -r; } static int help(int argc, char *argv[], void *userdata) { diff --git a/src/import/importd.c b/src/import/importd.c new file mode 100644 index 0000000000..92de43038c --- /dev/null +++ b/src/import/importd.c @@ -0,0 +1,958 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "sd-bus.h" +#include "util.h" +#include "strv.h" +#include "bus-util.h" +#include "bus-common-errors.h" +#include "def.h" +#include "import-util.h" + +typedef struct Transfer Transfer; +typedef struct Manager Manager; + +typedef enum TransferType { + TRANSFER_TAR, + TRANSFER_RAW, + TRANSFER_DKR, + _TRANSFER_TYPE_MAX, + _TRANSFER_TYPE_INVALID = -1, +} TransferType; + +struct Transfer { + Manager *manager; + + uint32_t id; + char *object_path; + + TransferType type; + ImportVerify verify; + + char *remote; + char *local; + bool force_local; + + char *dkr_index_url; + + pid_t pid; + + int log_fd; + + char log_message[LINE_MAX]; + size_t log_message_size; + + sd_event_source *pid_event_source; + sd_event_source *log_event_source; + + unsigned n_canceled; +}; + +struct Manager { + sd_event *event; + sd_bus *bus; + + uint32_t current_transfer_id; + Hashmap *transfers; + + Hashmap *polkit_registry; +}; + +#define TRANSFERS_MAX 64 + +static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { + [TRANSFER_TAR] = "tar", + [TRANSFER_RAW] = "raw", + [TRANSFER_DKR] = "dkr", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(transfer_type, TransferType); + +static Transfer *transfer_unref(Transfer *t) { + if (!t) + return NULL; + + if (t->manager) + hashmap_remove(t->manager->transfers, UINT32_TO_PTR(t->id)); + + sd_event_source_unref(t->pid_event_source); + sd_event_source_unref(t->log_event_source); + + free(t->remote); + free(t->local); + free(t->dkr_index_url); + free(t->object_path); + + if (t->pid > 0) { + (void) kill_and_sigcont(t->pid, SIGKILL); + (void) wait_for_terminate(t->pid, NULL); + } + + safe_close(t->log_fd); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Transfer*, transfer_unref); + +static int transfer_new(Manager *m, Transfer **ret) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + uint32_t id; + int r; + + assert(m); + assert(ret); + + if (hashmap_size(m->transfers) >= TRANSFERS_MAX) + return -E2BIG; + + r = hashmap_ensure_allocated(&m->transfers, &trivial_hash_ops); + if (r < 0) + return r; + + t = new0(Transfer, 1); + if (!t) + return -ENOMEM; + + t->type = _TRANSFER_TYPE_INVALID; + t->log_fd = -1; + + id = m->current_transfer_id + 1; + + if (asprintf(&t->object_path, "/org/freedesktop/import1/transfer/_%" PRIu32, id) < 0) + return -ENOMEM; + + r = hashmap_put(m->transfers, UINT32_TO_PTR(id), t); + if (r < 0) + return r; + + m->current_transfer_id = id; + + t->manager = m; + t->id = id; + + *ret = t; + t = NULL; + + return 0; +} + +static void transfer_send_log_line(Transfer *t, const char *line) { + int r, priority = LOG_INFO; + + assert(t); + assert(line); + + syslog_parse_priority(&line, &priority, true); + + log_full(priority, "(transfer%" PRIu32 "): %s", t->id, line); + + r = sd_bus_emit_signal( + t->manager->bus, + t->object_path, + "org.freedesktop.import1.Transfer", + "LogMessage", + "us", + priority, + line); + if (r < 0) + log_error_errno(r, "Cannot emit message: %m"); + } + +static void transfer_send_logs(Transfer *t, bool flush) { + assert(t); + + /* Try to send out all log messages, if we can. But if we + * can't we remove the messages from the buffer, but don't + * fail */ + + while (t->log_message_size > 0) { + _cleanup_free_ char *n = NULL; + char *e; + + if (t->log_message_size >= sizeof(t->log_message)) + e = t->log_message + sizeof(t->log_message); + else { + char *a, *b; + + a = memchr(t->log_message, 0, t->log_message_size); + b = memchr(t->log_message, '\n', t->log_message_size); + + if (a && b) + e = a < b ? a : b; + else if (a) + e = a; + else + e = b; + } + + if (!e) { + if (!flush) + return; + + e = t->log_message + t->log_message_size; + } + + n = strndup(t->log_message, e - t->log_message); + + /* Skip over NUL and newlines */ + while (e < t->log_message + t->log_message_size && (*e == 0 || *e == '\n')) + e++; + + memmove(t->log_message, e, t->log_message + sizeof(t->log_message) - e); + t->log_message_size -= e - t->log_message; + + if (!n) { + log_oom(); + continue; + } + + if (isempty(n)) + continue; + + transfer_send_log_line(t, n); + } +} + +static int transfer_finalize(Transfer *t, bool success) { + int r; + + assert(t); + + transfer_send_logs(t, true); + + r = sd_bus_emit_signal( + t->manager->bus, + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "TransferRemoved", + "uos", + t->id, + t->object_path, + success ? "done" : + t->n_canceled > 0 ? "canceled" : "failed"); + + if (r < 0) + log_error_errno(r, "Cannot emit message: %m"); + + transfer_unref(t); + return 0; +} + +static int transfer_cancel(Transfer *t) { + int r; + + assert(t); + + r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL); + if (r < 0) + return r; + + t->n_canceled++; + return 0; +} + +static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userdata) { + Transfer *t = userdata; + bool success = false; + + assert(s); + assert(t); + + if (si->si_code == CLD_EXITED) { + if (si->si_status != 0) + log_error("Import process failed with exit code %i.", si->si_status); + else { + log_debug("Import process succeeded."); + success = true; + } + + } else if (si->si_code == CLD_KILLED || + si->si_code == CLD_DUMPED) + + log_error("Import process terminated by signal %s.", signal_to_string(si->si_status)); + else + log_error("Import process failed due to unknown reason."); + + t->pid = 0; + + return transfer_finalize(t, success); +} + +static int transfer_on_log(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Transfer *t = userdata; + ssize_t l; + + assert(s); + assert(t); + + l = read(fd, t->log_message + t->log_message_size, sizeof(t->log_message) - t->log_message_size); + if (l <= 0) { + /* EOF/read error. We just close the pipe here, and + * close the watch, waiting for the SIGCHLD to arrive, + * before we do anything else. */ + + if (l < 0) + log_error_errno(errno, "Failed to read log message: %m"); + + t->log_event_source = sd_event_source_unref(t->log_event_source); + return 0; + } + + t->log_message_size += l; + + transfer_send_logs(t, false); + + return 0; +} + +static int transfer_start(Transfer *t) { + _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; + int r; + + assert(t); + assert(t->pid <= 0); + + if (pipe2(pipefd, O_CLOEXEC) < 0) + return -errno; + + t->pid = fork(); + if (t->pid < 0) + return -errno; + if (t->pid == 0) { + const char *cmd[] = { + "systemd-import", + t->type == TRANSFER_TAR ? "pull-tar" : + t->type == TRANSFER_RAW ? "pull-raw" : + "pull-dkr", + "--verify", + NULL, /* verify argument */ + NULL, /* maybe --force */ + NULL, /* maybe --dkr-index-url */ + NULL, /* the actual URL */ + NULL, /* remote */ + NULL, /* local */ + NULL + }; + int null_fd; + unsigned k = 3; + + /* Child */ + + reset_all_signal_handlers(); + reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pipefd[0] = safe_close(pipefd[0]); + + if (dup2(pipefd[1], STDOUT_FILENO) != STDOUT_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(pipefd[1], STDERR_FILENO) != STDERR_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (pipefd[1] != STDOUT_FILENO && pipefd[1] != STDERR_FILENO) + pipefd[1] = safe_close(pipefd[1]); + + null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); + if (null_fd < 0) { + log_error_errno(errno, "Failed to open /dev/null: %m"); + _exit(EXIT_FAILURE); + } + + if (dup2(null_fd, STDIN_FILENO) != STDIN_FILENO) { + log_error_errno(errno, "Failed to dup2() fd: %m"); + _exit(EXIT_FAILURE); + } + + if (null_fd != STDIN_FILENO) + safe_close(null_fd); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_cloexec(STDERR_FILENO, false); + + putenv((char*) "SYSTEMD_LOG_TARGET=console-prefixed"); + + cmd[k++] = import_verify_to_string(t->verify); + if (t->force_local) + cmd[k++] = "--force"; + + if (t->dkr_index_url) { + cmd[k++] = "--dkr-index-url"; + cmd[k++] = t->dkr_index_url; + } + + cmd[k++] = t->remote; + if (t->local) + cmd[k++] = t->local; + cmd[k] = NULL; + + execv(SYSTEMD_IMPORT_PATH, (char * const *) cmd); + log_error_errno(errno, "Failed to execute import tool: %m"); + _exit(EXIT_FAILURE); + } + + pipefd[1] = safe_close(pipefd[1]); + t->log_fd = pipefd[0]; + pipefd[0] = -1; + + r = sd_event_add_child(t->manager->event, &t->pid_event_source, t->pid, WEXITED, transfer_on_pid, t); + if (r < 0) + return r; + + r = sd_event_add_io(t->manager->event, &t->log_event_source, t->log_fd, EPOLLIN, transfer_on_log, t); + if (r < 0) + return r; + + /* Make sure always process logging before SIGCHLD */ + r = sd_event_source_set_priority(t->log_event_source, SD_EVENT_PRIORITY_NORMAL -5); + if (r < 0) + return r; + + r = sd_bus_emit_signal( + t->manager->bus, + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "TransferNew", + "uo", + t->id, + t->object_path); + if (r < 0) + return r; + + return 0; +} + +static Manager *manager_unref(Manager *m) { + Transfer *t; + + if (!m) + return NULL; + + while ((t = hashmap_first(m->transfers))) + transfer_unref(t); + + hashmap_free(m->transfers); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + sd_bus_close(m->bus); + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref); + +static int manager_new(Manager **ret) { + _cleanup_(manager_unrefp) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_set_watchdog(m->event, true); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return r; + + *ret = m; + m = NULL; + + return 0; +} + +static Transfer *manager_find(Manager *m, TransferType type, const char *dkr_index_url, const char *remote) { + Transfer *t; + Iterator i; + + assert(m); + assert(type >= 0); + assert(type < _TRANSFER_TYPE_MAX); + + HASHMAP_FOREACH(t, m->transfers, i) { + + if (t->type == type && + streq_ptr(t->remote, remote) && + streq_ptr(t->dkr_index_url, dkr_index_url)) + return t; + } + + return NULL; +} + +static int method_pull_tar_or_raw(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + const char *remote, *local, *verify, *object; + Manager *m = userdata; + ImportVerify v; + TransferType type; + int force, r; + uint32_t id; + + assert(bus); + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + false, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); + if (r < 0) + return r; + + if (!http_url_is_valid(remote)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "URL %s is invalid", remote); + + if (isempty(local)) + local = NULL; + else if (!machine_name_is_valid(local)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); + + if (isempty(verify)) + v = IMPORT_VERIFY_SIGNATURE; + else + v = import_verify_from_string(verify); + if (v < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); + + type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_TAR : TRANSFER_RAW; + + if (manager_find(m, type, NULL, remote)) + return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote); + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = type; + t->verify = v; + t->force_local = force; + + t->remote = strdup(remote); + if (!t->remote) + return -ENOMEM; + + t->local = strdup(local); + if (!t->local) + return -ENOMEM; + + r = transfer_start(t); + if (r < 0) + return r; + + object = t->object_path; + id = t->id; + t = NULL; + + return sd_bus_reply_method_return(msg, "uo", id, object); +} + +static int method_pull_dkr(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(transfer_unrefp) Transfer *t = NULL; + const char *index_url, *remote, *tag, *local, *verify, *object; + Manager *m = userdata; + ImportVerify v; + int force, r; + uint32_t id; + + assert(bus); + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + false, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "sssssb", &index_url, &remote, &tag, &local, &verify, &force); + if (r < 0) + return r; + + if (isempty(index_url)) + index_url = DEFAULT_DKR_INDEX_URL; + if (!index_url) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL must be specified."); + if (!http_url_is_valid(index_url)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Index URL %s is invalid", index_url); + + if (!dkr_name_is_valid(remote)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Remote name %s is not valid", remote); + + if (isempty(tag)) + tag = "latest"; + else if (!dkr_tag_is_valid(tag)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Tag %s is not valid", tag); + + if (isempty(local)) + local = NULL; + else if (!machine_name_is_valid(local)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Local name %s is invalid", local); + + if (isempty(verify)) + v = IMPORT_VERIFY_SIGNATURE; + else + v = import_verify_from_string(verify); + if (v < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); + + if (v != IMPORT_VERIFY_NO) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "DKR does not support verification."); + + if (manager_find(m, TRANSFER_DKR, index_url, remote)) + return sd_bus_error_setf(error, BUS_ERROR_TRANSFER_IN_PROGRESS, "Transfer for %s already in progress.", remote); + + r = transfer_new(m, &t); + if (r < 0) + return r; + + t->type = TRANSFER_DKR; + t->verify = v; + t->force_local = force; + + t->dkr_index_url = strdup(index_url); + if (!t->dkr_index_url) + return -ENOMEM; + + t->remote = strjoin(remote, ":", tag, NULL); + if (!t->remote) + return -ENOMEM; + + t->local = strdup(local); + if (!t->local) + return -ENOMEM; + + r = transfer_start(t); + if (r < 0) + return r; + + object = t->object_path; + id = t->id; + t = NULL; + + return sd_bus_reply_method_return(msg, "uo", id, object); +} + +static int method_list_transfers(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + Manager *m = userdata; + Transfer *t; + Iterator i; + int r; + + assert(bus); + assert(msg); + assert(m); + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ussso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(t, m->transfers, i) { + + r = sd_bus_message_append( + reply, + "(ussso)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + t->object_path); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(bus, reply, NULL); +} + +static int method_cancel(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) { + Transfer *t = userdata; + int r; + + assert(bus); + assert(msg); + assert(t); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + false, + &t->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = transfer_cancel(t); + if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, NULL); +} + +static int method_cancel_transfer(sd_bus *bus, sd_bus_message *msg, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Transfer *t; + uint32_t id; + int r; + + assert(bus); + assert(msg); + assert(m); + + r = bus_verify_polkit_async( + msg, + CAP_SYS_ADMIN, + "org.freedesktop.import1.pull", + false, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = sd_bus_message_read(msg, "u", &id); + if (r < 0) + return r; + if (id <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid transfer id"); + + t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); + if (!t) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_TRANSFER, "Not transfer by id %" PRIu32, id); + + r = transfer_cancel(t); + if (r < 0) + return r; + + return sd_bus_reply_method_return(msg, NULL); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, transfer_type, TransferType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_verify, import_verify, ImportVerify); + +static const sd_bus_vtable transfer_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Transfer, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Local", "s", NULL, offsetof(Transfer, local), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Remote", "s", NULL, offsetof(Transfer, remote), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Transfer, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Verify", "s", property_get_verify, offsetof(Transfer, verify), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_METHOD("Cancel", NULL, NULL, method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("LogMessage", "us", 0), + SD_BUS_VTABLE_END, +}; + +static const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("PullTar", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PullRaw", "sssb", "uo", method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PullDkr", "sssssb", "uo", method_pull_dkr, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListTransfers", NULL, "a(ussso)", method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CancelTransfer", "u", NULL, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("TransferNew", "uo", 0), + SD_BUS_SIGNAL("TransferRemoved", "uos", 0), + SD_BUS_VTABLE_END, +}; + +static int transfer_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Transfer *t; + const char *p; + uint32_t id; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + p = startswith(path, "/org/freedesktop/import1/transfer/_"); + if (!p) + return 0; + + r = safe_atou32(p, &id); + if (r < 0 || id == 0) + return 0; + + t = hashmap_get(m->transfers, UINT32_TO_PTR(id)); + if (!t) + return 0; + + *found = t; + return 1; +} + +static int transfer_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Transfer *t; + unsigned k = 0; + Iterator i; + + l = new0(char*, hashmap_size(m->transfers) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(t, m->transfers, i) { + + l[k] = strdup(t->object_path); + if (!l[k]) + return -ENOMEM; + + k++; + } + + *nodes = l; + l = NULL; + + return 1; +} + +static int manager_add_bus_objects(Manager *m) { + int r; + + assert(m); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/import1/transfer", "org.freedesktop.import1.Transfer", transfer_vtable, transfer_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/import1/transfer", transfer_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add transfer enumerator: %m"); + + r = sd_bus_request_name(m->bus, "org.freedesktop.import1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + +static bool manager_check_idle(void *userdata) { + Manager *m = userdata; + + return hashmap_isempty(m->transfers); +} + +static int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.import1", + DEFAULT_EXIT_USEC, + manager_check_idle, + m); +} + +int main(int argc, char *argv[]) { + _cleanup_(manager_unrefp) Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + assert_se(sigprocmask_many(SIG_BLOCK, SIGCHLD, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Failed to allocate manager object: %m"); + goto finish; + } + + r = manager_add_bus_objects(m); + if (r < 0) + goto finish; + + r = manager_run(m); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/import/org.freedesktop.import1.conf b/src/import/org.freedesktop.import1.conf new file mode 100644 index 0000000000..9a769c3f02 --- /dev/null +++ b/src/import/org.freedesktop.import1.conf @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/import/org.freedesktop.import1.policy.in b/src/import/org.freedesktop.import1.policy.in new file mode 100644 index 0000000000..1003f46447 --- /dev/null +++ b/src/import/org.freedesktop.import1.policy.in @@ -0,0 +1,29 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Download a VM or container image + <_message>Authentication is required to download a VM or container image + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/import/org.freedesktop.import1.service b/src/import/org.freedesktop.import1.service new file mode 100644 index 0000000000..8fc4c47881 --- /dev/null +++ b/src/import/org.freedesktop.import1.service @@ -0,0 +1,12 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.import1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.import1.service diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 5d211323ad..21bc9679c2 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -238,46 +238,6 @@ size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid) return e; } -void syslog_parse_priority(const char **p, int *priority, bool with_facility) { - int a = 0, b = 0, c = 0; - int k; - - assert(p); - assert(*p); - assert(priority); - - if ((*p)[0] != '<') - return; - - if (!strchr(*p, '>')) - return; - - if ((*p)[2] == '>') { - c = undecchar((*p)[1]); - k = 3; - } else if ((*p)[3] == '>') { - b = undecchar((*p)[1]); - c = undecchar((*p)[2]); - k = 4; - } else if ((*p)[4] == '>') { - a = undecchar((*p)[1]); - b = undecchar((*p)[2]); - c = undecchar((*p)[3]); - k = 5; - } else - return; - - if (a < 0 || b < 0 || c < 0 || - (!with_facility && (a || b || c > 7))) - return; - - if (with_facility) - *priority = a*100 + b*10 + c; - else - *priority = (*priority & LOG_FACMASK) | c; - *p += k; -} - static void syslog_skip_date(char **buf) { enum { LETTER, diff --git a/src/journal/journald-syslog.h b/src/journal/journald-syslog.h index 25f89883f4..3774ebdf05 100644 --- a/src/journal/journald-syslog.h +++ b/src/journal/journald-syslog.h @@ -25,7 +25,6 @@ int syslog_fixup_facility(int priority) _const_; -void syslog_parse_priority(const char **p, int *priority, bool with_facility); size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid); void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index 635363cf98..eb60ca3628 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -73,5 +73,8 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLOCK), SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), + SD_BUS_ERROR_MAP_END }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 9007b85c19..e935833bc3 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -72,4 +72,7 @@ #define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" #define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." +#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" +#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" + BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index bae2abe188..11b2c004b8 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -53,6 +53,7 @@ #include "mkdir.h" #include "copy.h" #include "verbs.h" +#include "import-util.h" static char **arg_property = NULL; static bool arg_all = false; @@ -69,6 +70,9 @@ static bool arg_quiet = false; static bool arg_ask_password = true; static unsigned arg_lines = 10; static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_force = false; +static const char* arg_verify = NULL; +static const char* arg_dkr_index_url = NULL; static void pager_open_if_enabled(void) { @@ -135,13 +139,13 @@ static int list_machines(int argc, char *argv[], void *userdata) { "ListMachines", &error, &reply, - ""); + NULL); if (r < 0) { log_error("Could not get machines: %s", bus_error_message(&error, -r)); return r; } - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssso)"); + r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); if (r < 0) return bus_log_parse_error(r); @@ -1732,6 +1736,464 @@ static int enable_machine(int argc, char *argv[], void *userdata) { return 0; } +typedef struct PullContext { + const char *path; + int result; +} PullContext; + +static int match_log_message(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + PullContext *c = userdata; + const char *line; + unsigned priority; + int r; + + assert(bus); + assert(m); + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(c->path, sd_bus_message_get_path(m))) + return 0; + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + log_full(priority, "%s", line); + return 0; +} + +static int match_transfer_removed(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + PullContext *c = userdata; + const char *path, *result; + uint32_t id; + int r; + + assert(bus); + assert(m); + assert(c); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(c->path, path)) + return 0; + + c->result = streq_ptr(result, "done"); + return 0; +} + +static int pull_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_bus_slot_unref_ sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + PullContext c = { + .result = -1, + }; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_add_match( + bus, + &slot_job_removed, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Manager'," + "member='TransferRemoved'," + "path='/org/freedesktop/import1'", + match_transfer_removed, &c); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_add_match( + bus, + &slot_log_message, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Transfer'," + "member='LogMessage'", + match_log_message, &c); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed pull image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "uo", &id, &c.path); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + + /* The match sets this to NULL when we are done */ + if (c.result >= 0) + break; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } + + return c.result ? 0 : -EINVAL; +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_error_errno(r, "Failed to strip tar suffixes: %m"); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + arg_verify, + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return pull_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_error_errno(r, "Failed to strip tar suffixes: %m"); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + arg_verify, + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return pull_image_common(bus, m); +} + +static int pull_dkr(int argc, char *argv[], void *userdata) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote, *tag; + sd_bus *bus = userdata; + int r; + + if (!streq_ptr(arg_dkr_index_url, "no")) { + log_error("Imports from DKR do not support image verification, please pass --verify=no."); + return -EINVAL; + } + + remote = argv[1]; + tag = strchr(remote, ':'); + if (tag) { + remote = strndupa(remote, tag - remote); + tag++; + } + + if (!dkr_name_is_valid(remote)) { + log_error("DKR name '%s' is invalid.", remote); + return -EINVAL; + } + if (tag && !dkr_tag_is_valid(tag)) { + log_error("DKR tag '%s' is invalid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + local = strchr(remote, '/'); + if (local) + local++; + else + local = remote; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullDkr"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssssb", + arg_dkr_index_url, + remote, + tag, + local, + arg_verify, + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return pull_image_common(bus, m); +} + +typedef struct TransferInfo { + uint32_t id; + const char *type; + const char *remote; + const char *local; +} TransferInfo; + +static int compare_transfer_info(const void *a, const void *b) { + const TransferInfo *x = a, *y = b; + + return strcmp(x->local, y->local); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ TransferInfo *transfers = NULL; + size_t n_transfers = 0, n_allocated = 0, j; + const char *type, *remote, *local, *object; + sd_bus *bus = userdata; + uint32_t id, max_id = 0; + int r; + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ListTransfers", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get transfers: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(ussso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ussso)", &id, &type, &remote, &local, &object)) > 0) { + size_t l; + + if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) + return log_oom(); + + transfers[n_transfers].id = id; + transfers[n_transfers].type = type; + transfers[n_transfers].remote = remote; + transfers[n_transfers].local = local; + + l = strlen(type); + if (l > max_type) + max_type = l; + + l = strlen(remote); + if (l > max_remote) + max_remote = l; + + l = strlen(local); + if (l > max_local) + max_local = l; + + if (id > max_id) + max_id = id; + + n_transfers ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", + (int) max_type, "TYPE", + (int) max_local, "LOCAL", + (int) max_remote, "REMOTE"); + + for (j = 0; j < n_transfers; j++) + printf("%*" PRIu32 " %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, + (int) max_type, transfers[j].type, + (int) max_local, transfers[j].local, + (int) max_remote, transfers[j].remote); + + if (arg_legend) + printf("\n%zu transfers listed.\n", n_transfers); + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "CancelTransfer", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + static int help(int argc, char *argv[], void *userdata) { printf("%s [OPTIONS...] {COMMAND} ...\n\n" @@ -1755,7 +2217,12 @@ static int help(int argc, char *argv[], void *userdata) { " -n --lines=INTEGER Number of journal entries to show\n" " -o --output=STRING Change journal output mode (short,\n" " short-monotonic, verbose, export, json,\n" - " json-pretty, json-sse, cat)\n\n" + " json-pretty, json-sse, cat)\n" + " --verify=MODE Verification mode for downloaded images (no, sum,\n" + " signature)\n" + " --force Download image even if already exists\n" + " --dkr-index-url=URL Specify the index URL to use for DKR image\n" + " downloads\n\n" "Machine Commands:\n" " list List running VMs and containers\n" " status NAME... Show VM/container details\n" @@ -1778,7 +2245,13 @@ static int help(int argc, char *argv[], void *userdata) { " clone NAME NAME Clone an image\n" " rename NAME NAME Rename an image\n" " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" + " remove NAME... Remove an image\n\n" + "Transfer Commands:\n" + " pull-tar URL [NAME] Download a TAR image\n" + " pull-raw URL [NAME] Download a RAW image\n" + " pull-dkr REMOTE [NAME] Download a DKR image\n" + " list-transfers Show list of current downloads\n" + " cancel-transfer Cancel a download\n" , program_invocation_short_name); return 0; @@ -1794,6 +2267,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_READ_ONLY, ARG_MKDIR, ARG_NO_ASK_PASSWORD, + ARG_VERIFY, + ARG_FORCE, + ARG_DKR_INDEX_URL, }; static const struct option options[] = { @@ -1814,6 +2290,9 @@ static int parse_argv(int argc, char *argv[]) { { "lines", required_argument, NULL, 'n' }, { "output", required_argument, NULL, 'o' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "dkr-index-url", required_argument, NULL, ARG_DKR_INDEX_URL }, {} }; @@ -1914,6 +2393,23 @@ static int parse_argv(int argc, char *argv[]) { arg_quiet = true; break; + case ARG_VERIFY: + arg_verify = optarg; + break; + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_DKR_INDEX_URL: + if (!http_url_is_valid(optarg)) { + log_error("Index URL is invalid: %s", optarg); + return -EINVAL; + } + + arg_dkr_index_url = optarg; + break; + case '?': return -EINVAL; @@ -1927,28 +2423,33 @@ static int parse_argv(int argc, char *argv[]) { static int machinectl_main(int argc, char *argv[], sd_bus *bus) { static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status",2, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", 2, 2, 0, login_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, + { "list-images", VERB_ANY, 1, 0, list_images }, + { "status", 2, VERB_ANY, 0, show_machine }, + { "image-status", 2, VERB_ANY, 0, show_image }, + { "show", VERB_ANY, VERB_ANY, 0, show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, + { "terminate", 2, VERB_ANY, 0, terminate_machine }, + { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, + { "kill", 2, VERB_ANY, 0, kill_machine }, + { "login", 2, 2, 0, login_machine }, + { "bind", 3, 4, 0, bind_mount }, + { "copy-to", 3, 4, 0, copy_files }, + { "copy-from", 3, 4, 0, copy_files }, + { "remove", 2, VERB_ANY, 0, remove_image }, + { "rename", 3, 3, 0, rename_image }, + { "clone", 3, 3, 0, clone_image }, + { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, + { "enable", 2, VERB_ANY, 0, enable_machine }, + { "disable", 2, VERB_ANY, 0, enable_machine }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "pull-dkr", 2, 3, 0, pull_dkr }, + { "list-transfers", VERB_ANY, 1, 0, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, {} }; diff --git a/src/shared/import-util.c b/src/shared/import-util.c new file mode 100644 index 0000000000..46671e8c99 --- /dev/null +++ b/src/shared/import-util.c @@ -0,0 +1,182 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "util.h" +#include "import-util.h" + +int import_url_last_component(const char *url, char **ret) { + const char *e, *p; + char *s; + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + p = e; + while (p > url && p[-1] != '/') + p--; + + if (e <= p) + return -EINVAL; + + s = strndup(p, e - p); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + + +int import_url_change_last_component(const char *url, const char *suffix, char **ret) { + const char *e; + char *s; + + assert(url); + assert(ret); + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + while (e > url && e[-1] != '/') + e--; + + if (e <= url) + return -EINVAL; + + s = new(char, (e - url) + strlen(suffix) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, url, e - url), suffix); + *ret = s; + return 0; +} + +static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { + [IMPORT_VERIFY_NO] = "no", + [IMPORT_VERIFY_SUM] = "sum", + [IMPORT_VERIFY_SIGNATURE] = "signature", +}; + +DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); + +int tar_strip_suffixes(const char *name, char **ret) { + const char *e; + char *s; + + e = endswith(name, ".tar"); + if (!e) + e = endswith(name, ".tar.xz"); + if (!e) + e = endswith(name, ".tar.gz"); + if (!e) + e = endswith(name, ".tar.bz2"); + if (!e) + e = endswith(name, ".tgz"); + if (!e) + e = strchr(name, 0); + + if (e <= name) + return -EINVAL; + + s = strndup(name, e - name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int raw_strip_suffixes(const char *p, char **ret) { + + static const char suffixes[] = + ".xz\0" + ".gz\0" + ".bz2\0" + ".raw\0" + ".qcow2\0" + ".img\0" + ".bin\0"; + + _cleanup_free_ char *q = NULL; + + q = strdup(p); + if (!q) + return -ENOMEM; + + for (;;) { + const char *sfx; + bool changed = false; + + NULSTR_FOREACH(sfx, suffixes) { + char *e; + + e = endswith(q, sfx); + if (e) { + *e = 0; + changed = true; + } + } + + if (!changed) + break; + } + + *ret = q; + q = NULL; + + return 0; +} + +bool dkr_name_is_valid(const char *name) { + const char *slash, *p; + + if (isempty(name)) + return false; + + slash = strchr(name, '/'); + if (!slash) + return false; + + if (!filename_is_valid(slash + 1)) + return false; + + p = strndupa(name, slash - name); + if (!filename_is_valid(p)) + return false; + + return true; +} + +bool dkr_id_is_valid(const char *id) { + + if (!filename_is_valid(id)) + return false; + + if (!in_charset(id, "0123456789abcdef")) + return false; + + return true; +} diff --git a/src/shared/import-util.h b/src/shared/import-util.h new file mode 100644 index 0000000000..da87a4012b --- /dev/null +++ b/src/shared/import-util.h @@ -0,0 +1,47 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include "macro.h" + +typedef enum ImportVerify { + IMPORT_VERIFY_NO, + IMPORT_VERIFY_SUM, + IMPORT_VERIFY_SIGNATURE, + _IMPORT_VERIFY_MAX, + _IMPORT_VERIFY_INVALID = -1, +} ImportVerify; + +int import_url_last_component(const char *url, char **ret); +int import_url_change_last_component(const char *url, const char *suffix, char **ret); + +const char* import_verify_to_string(ImportVerify v) _const_; +ImportVerify import_verify_from_string(const char *s) _pure_; + +int tar_strip_suffixes(const char *name, char **ret); +int raw_strip_suffixes(const char *name, char **ret); + +bool dkr_name_is_valid(const char *name); +bool dkr_id_is_valid(const char *id); +#define dkr_tag_is_valid(tag) filename_is_valid(tag) diff --git a/src/shared/util.c b/src/shared/util.c index 3aa952fd77..891182a35b 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -5462,6 +5462,19 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { return r; } +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) + return false; + + return true; +} + bool http_url_is_valid(const char *url) { const char *p; @@ -8025,3 +8038,45 @@ void sigkill_wait(pid_t *pid) { if (kill(*pid, SIGKILL) > 0) (void) wait_for_terminate(*pid, NULL); } + +int syslog_parse_priority(const char **p, int *priority, bool with_facility) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return 0; + + if (!strchr(*p, '>')) + return 0; + + if ((*p)[2] == '>') { + c = undecchar((*p)[1]); + k = 3; + } else if ((*p)[3] == '>') { + b = undecchar((*p)[1]); + c = undecchar((*p)[2]); + k = 4; + } else if ((*p)[4] == '>') { + a = undecchar((*p)[1]); + b = undecchar((*p)[2]); + c = undecchar((*p)[3]); + k = 5; + } else + return 0; + + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) + return 0; + + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; + + *p += k; + return 1; +} diff --git a/src/shared/util.h b/src/shared/util.h index f59a2bbb99..bb2c101569 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -654,6 +654,8 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value); bool http_url_is_valid(const char *url) _pure_; bool documentation_url_is_valid(const char *url) _pure_; +bool http_etag_is_valid(const char *etag); + bool in_initrd(void); void warn_melody(void); @@ -1068,3 +1070,5 @@ ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); void sigkill_wait(pid_t *pid); #define _cleanup_sigkill_wait_ _cleanup_(sigkill_wait) + +int syslog_parse_priority(const char **p, int *priority, bool with_facility); -- cgit v1.2.3-54-g00ecf