chiark / gitweb /
import: introduce new mini-daemon systemd-importd, and make machinectl a client to it
[elogind.git] / src / import / import-common.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2015 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/prctl.h>
23
24 #include "util.h"
25 #include "strv.h"
26 #include "copy.h"
27 #include "btrfs-util.h"
28 #include "import-job.h"
29 #include "import-common.h"
30
31 #define FILENAME_ESCAPE "/.#\"\'"
32
33 int import_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) {
34         _cleanup_free_ char *escaped_url = NULL;
35         _cleanup_closedir_ DIR *d = NULL;
36         _cleanup_strv_free_ char **l = NULL;
37         struct dirent *de;
38         int r;
39
40         assert(url);
41         assert(etags);
42
43         if (!image_root)
44                 image_root = "/var/lib/machines";
45
46         escaped_url = xescape(url, FILENAME_ESCAPE);
47         if (!escaped_url)
48                 return -ENOMEM;
49
50         d = opendir(image_root);
51         if (!d) {
52                 if (errno == ENOENT) {
53                         *etags = NULL;
54                         return 0;
55                 }
56
57                 return -errno;
58         }
59
60         FOREACH_DIRENT_ALL(de, d, return -errno) {
61                 const char *a, *b;
62                 char *u;
63
64                 if (de->d_type != DT_UNKNOWN &&
65                     de->d_type != dt)
66                         continue;
67
68                 if (prefix) {
69                         a = startswith(de->d_name, prefix);
70                         if (!a)
71                                 continue;
72                 } else
73                         a = de->d_name;
74
75                 a = startswith(a, escaped_url);
76                 if (!a)
77                         continue;
78
79                 a = startswith(a, ".");
80                 if (!a)
81                         continue;
82
83                 if (suffix) {
84                         b = endswith(de->d_name, suffix);
85                         if (!b)
86                                 continue;
87                 } else
88                         b = strchr(de->d_name, 0);
89
90                 if (a >= b)
91                         continue;
92
93                 u = cunescape_length(a, b - a);
94                 if (!u)
95                         return -ENOMEM;
96
97                 if (!http_etag_is_valid(u)) {
98                         free(u);
99                         continue;
100                 }
101
102                 r = strv_consume(&l, u);
103                 if (r < 0)
104                         return r;
105         }
106
107         *etags = l;
108         l = NULL;
109
110         return 0;
111 }
112
113 int import_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
114         const char *p;
115         int r;
116
117         assert(final);
118         assert(local);
119
120         if (!image_root)
121                 image_root = "/var/lib/machines";
122
123         p = strappenda(image_root, "/", local);
124
125         if (force_local) {
126                 (void) btrfs_subvol_remove(p);
127                 (void) rm_rf_dangerous(p, false, true, false);
128         }
129
130         r = btrfs_subvol_snapshot(final, p, false, false);
131         if (r == -ENOTTY) {
132                 r = copy_tree(final, p, false);
133                 if (r < 0)
134                         return log_error_errno(r, "Failed to copy image: %m");
135         } else if (r < 0)
136                 return log_error_errno(r, "Failed to create local image: %m");
137
138         log_info("Created new local image '%s'.", local);
139
140         return 0;
141 }
142
143 int import_make_read_only_fd(int fd) {
144         int r;
145
146         assert(fd >= 0);
147
148         /* First, let's make this a read-only subvolume if it refers
149          * to a subvolume */
150         r = btrfs_subvol_set_read_only_fd(fd, true);
151         if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) {
152                 struct stat st;
153
154                 /* This doesn't refer to a subvolume, or the file
155                  * system isn't even btrfs. In that, case fall back to
156                  * chmod()ing */
157
158                 r = fstat(fd, &st);
159                 if (r < 0)
160                         return log_error_errno(errno, "Failed to stat temporary image: %m");
161
162                 /* Drop "w" flag */
163                 if (fchmod(fd, st.st_mode & 07555) < 0)
164                         return log_error_errno(errno, "Failed to chmod() final image: %m");
165
166                 return 0;
167
168         } else if (r < 0)
169                 return log_error_errno(r, "Failed to make subvolume read-only: %m");
170
171         return 0;
172 }
173
174 int import_make_read_only(const char *path) {
175         _cleanup_close_ int fd = 1;
176
177         fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
178         if (fd < 0)
179                 return log_error_errno(errno, "Failed to open %s: %m", path);
180
181         return import_make_read_only_fd(fd);
182 }
183
184 int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
185         _cleanup_free_ char *escaped_url = NULL;
186         char *path;
187
188         assert(url);
189         assert(ret);
190
191         if (!image_root)
192                 image_root = "/var/lib/machines";
193
194         escaped_url = xescape(url, FILENAME_ESCAPE);
195         if (!escaped_url)
196                 return -ENOMEM;
197
198         if (etag) {
199                 _cleanup_free_ char *escaped_etag = NULL;
200
201                 escaped_etag = xescape(etag, FILENAME_ESCAPE);
202                 if (!escaped_etag)
203                         return -ENOMEM;
204
205                 path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL);
206         } else
207                 path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL);
208         if (!path)
209                 return -ENOMEM;
210
211         *ret = path;
212         return 0;
213 }
214
215 int import_make_verification_jobs(
216                 ImportJob **ret_checksum_job,
217                 ImportJob **ret_signature_job,
218                 ImportVerify verify,
219                 const char *url,
220                 CurlGlue *glue,
221                 ImportJobFinished on_finished,
222                 void *userdata) {
223
224         _cleanup_(import_job_unrefp) ImportJob *checksum_job = NULL, *signature_job = NULL;
225         int r;
226
227         assert(ret_checksum_job);
228         assert(ret_signature_job);
229         assert(verify >= 0);
230         assert(verify < _IMPORT_VERIFY_MAX);
231         assert(url);
232         assert(glue);
233
234         if (verify != IMPORT_VERIFY_NO) {
235                 _cleanup_free_ char *checksum_url = NULL;
236
237                 /* Queue job for the SHA256SUMS file for the image */
238                 r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url);
239                 if (r < 0)
240                         return r;
241
242                 r = import_job_new(&checksum_job, checksum_url, glue, userdata);
243                 if (r < 0)
244                         return r;
245
246                 checksum_job->on_finished = on_finished;
247                 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
248         }
249
250         if (verify == IMPORT_VERIFY_SIGNATURE) {
251                 _cleanup_free_ char *signature_url = NULL;
252
253                 /* Queue job for the SHA256SUMS.gpg file for the image. */
254                 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
255                 if (r < 0)
256                         return r;
257
258                 r = import_job_new(&signature_job, signature_url, glue, userdata);
259                 if (r < 0)
260                         return r;
261
262                 signature_job->on_finished = on_finished;
263                 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
264         }
265
266         *ret_checksum_job = checksum_job;
267         *ret_signature_job = signature_job;
268
269         checksum_job = signature_job = NULL;
270
271         return 0;
272 }
273
274 int import_verify(
275                 ImportJob *main_job,
276                 ImportJob *checksum_job,
277                 ImportJob *signature_job) {
278
279         _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
280         _cleanup_free_ char *fn = NULL;
281         _cleanup_close_ int sig_file = -1;
282         const char *p, *line;
283         char sig_file_path[] = "/tmp/sigXXXXXX";
284         _cleanup_sigkill_wait_ pid_t pid = 0;
285         int r;
286
287         assert(main_job);
288         assert(main_job->state == IMPORT_JOB_DONE);
289
290         if (!checksum_job)
291                 return 0;
292
293         assert(main_job->calc_checksum);
294         assert(main_job->checksum);
295         assert(checksum_job->state == IMPORT_JOB_DONE);
296
297         if (!checksum_job->payload || checksum_job->payload_size <= 0) {
298                 log_error("Checksum is empty, cannot verify.");
299                 return -EBADMSG;
300         }
301
302         r = import_url_last_component(main_job->url, &fn);
303         if (r < 0)
304                 return log_oom();
305
306         if (!filename_is_valid(fn)) {
307                 log_error("Cannot verify checksum, could not determine valid server-side file name.");
308                 return -EBADMSG;
309         }
310
311         line = strappenda(main_job->checksum, " *", fn, "\n");
312
313         p = memmem(checksum_job->payload,
314                    checksum_job->payload_size,
315                    line,
316                    strlen(line));
317
318         if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
319                 log_error("Checksum did not check out, payload has been tempered with.");
320                 return -EBADMSG;
321         }
322
323         log_info("SHA256 checksum of %s is valid.", main_job->url);
324
325         if (!signature_job)
326                 return 0;
327
328         assert(signature_job->state == IMPORT_JOB_DONE);
329
330         if (!signature_job->payload || signature_job->payload_size <= 0) {
331                 log_error("Signature is empty, cannot verify.");
332                 return -EBADMSG;
333         }
334
335         r = pipe2(gpg_pipe, O_CLOEXEC);
336         if (r < 0)
337                 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
338
339         sig_file = mkostemp(sig_file_path, O_RDWR);
340         if (sig_file < 0)
341                 return log_error_errno(errno, "Failed to create temporary file: %m");
342
343         r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
344         if (r < 0) {
345                 log_error_errno(r, "Failed to write to temporary file: %m");
346                 goto finish;
347         }
348
349         pid = fork();
350         if (pid < 0)
351                 return log_error_errno(errno, "Failed to fork off gpg: %m");
352         if (pid == 0) {
353                 const char *cmd[] = {
354                         "gpg",
355                         "--no-options",
356                         "--no-default-keyring",
357                         "--no-auto-key-locate",
358                         "--no-auto-check-trustdb",
359                         "--batch",
360                         "--trust-model=always",
361                         "--keyring=" VENDOR_KEYRING_PATH,
362                         NULL, /* maybe user keyring */
363                         NULL, /* --verify */
364                         NULL, /* signature file */
365                         NULL, /* dash */
366                         NULL  /* trailing NULL */
367                 };
368                 unsigned k = ELEMENTSOF(cmd) - 5;
369                 int null_fd;
370
371                 /* Child */
372
373                 reset_all_signal_handlers();
374                 reset_signal_mask();
375                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
376
377                 gpg_pipe[1] = safe_close(gpg_pipe[1]);
378
379                 if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
380                         log_error_errno(errno, "Failed to dup2() fd: %m");
381                         _exit(EXIT_FAILURE);
382                 }
383
384                 if (gpg_pipe[0] != STDIN_FILENO)
385                         gpg_pipe[0] = safe_close(gpg_pipe[0]);
386
387                 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
388                 if (null_fd < 0) {
389                         log_error_errno(errno, "Failed to open /dev/null: %m");
390                         _exit(EXIT_FAILURE);
391                 }
392
393                 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
394                         log_error_errno(errno, "Failed to dup2() fd: %m");
395                         _exit(EXIT_FAILURE);
396                 }
397
398                 if (null_fd != STDOUT_FILENO)
399                         null_fd = safe_close(null_fd);
400
401                 /* We add the user keyring only to the command line
402                  * arguments, if it's around since gpg fails
403                  * otherwise. */
404                 if (access(USER_KEYRING_PATH, F_OK) >= 0)
405                         cmd[k++] = "--keyring=" USER_KEYRING_PATH;
406
407                 cmd[k++] = "--verify";
408                 cmd[k++] = sig_file_path;
409                 cmd[k++] = "-";
410                 cmd[k++] = NULL;
411
412                 fd_cloexec(STDIN_FILENO, false);
413                 fd_cloexec(STDOUT_FILENO, false);
414                 fd_cloexec(STDERR_FILENO, false);
415
416                 execvp("gpg", (char * const *) cmd);
417                 log_error_errno(errno, "Failed to execute gpg: %m");
418                 _exit(EXIT_FAILURE);
419         }
420
421         gpg_pipe[0] = safe_close(gpg_pipe[0]);
422
423         r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
424         if (r < 0) {
425                 log_error_errno(r, "Failed to write to pipe: %m");
426                 goto finish;
427         }
428
429         gpg_pipe[1] = safe_close(gpg_pipe[1]);
430
431         r = wait_for_terminate_and_warn("gpg", pid, true);
432         pid = 0;
433         if (r < 0)
434                 goto finish;
435         if (r > 0) {
436                 log_error("Signature verification failed.");
437                 r = -EBADMSG;
438         } else {
439                 log_info("Signature verification succeeded.");
440                 r = 0;
441         }
442
443 finish:
444         if (sig_file >= 0)
445                 unlink(sig_file_path);
446
447         return r;
448 }