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