chiark / gitweb /
import: make the user verficiation keyring override the vendor keyring, instead of...
[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                         NULL, /* keyring to use */
362                         NULL, /* --verify */
363                         NULL, /* signature file */
364                         NULL, /* dash */
365                         NULL  /* trailing NULL */
366                 };
367                 unsigned k = ELEMENTSOF(cmd) - 5;
368                 int null_fd;
369
370                 /* Child */
371
372                 reset_all_signal_handlers();
373                 reset_signal_mask();
374                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
375
376                 gpg_pipe[1] = safe_close(gpg_pipe[1]);
377
378                 if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
379                         log_error_errno(errno, "Failed to dup2() fd: %m");
380                         _exit(EXIT_FAILURE);
381                 }
382
383                 if (gpg_pipe[0] != STDIN_FILENO)
384                         gpg_pipe[0] = safe_close(gpg_pipe[0]);
385
386                 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
387                 if (null_fd < 0) {
388                         log_error_errno(errno, "Failed to open /dev/null: %m");
389                         _exit(EXIT_FAILURE);
390                 }
391
392                 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
393                         log_error_errno(errno, "Failed to dup2() fd: %m");
394                         _exit(EXIT_FAILURE);
395                 }
396
397                 if (null_fd != STDOUT_FILENO)
398                         null_fd = safe_close(null_fd);
399
400                 /* We add the user keyring only to the command line
401                  * arguments, if it's around since gpg fails
402                  * otherwise. */
403                 if (access(USER_KEYRING_PATH, F_OK) >= 0)
404                         cmd[k++] = "--keyring=" USER_KEYRING_PATH;
405                 else
406                         cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
407
408                 cmd[k++] = "--verify";
409                 cmd[k++] = sig_file_path;
410                 cmd[k++] = "-";
411                 cmd[k++] = NULL;
412
413                 fd_cloexec(STDIN_FILENO, false);
414                 fd_cloexec(STDOUT_FILENO, false);
415                 fd_cloexec(STDERR_FILENO, false);
416
417                 execvp("gpg", (char * const *) cmd);
418                 log_error_errno(errno, "Failed to execute gpg: %m");
419                 _exit(EXIT_FAILURE);
420         }
421
422         gpg_pipe[0] = safe_close(gpg_pipe[0]);
423
424         r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
425         if (r < 0) {
426                 log_error_errno(r, "Failed to write to pipe: %m");
427                 goto finish;
428         }
429
430         gpg_pipe[1] = safe_close(gpg_pipe[1]);
431
432         r = wait_for_terminate_and_warn("gpg", pid, true);
433         pid = 0;
434         if (r < 0)
435                 goto finish;
436         if (r > 0) {
437                 log_error("Signature verification failed.");
438                 r = -EBADMSG;
439         } else {
440                 log_info("Signature verification succeeded.");
441                 r = 0;
442         }
443
444 finish:
445         if (sig_file >= 0)
446                 unlink(sig_file_path);
447
448         return r;
449 }