]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-common.c
shared: add process-util.[ch]
[thirdparty/systemd.git] / src / import / pull-common.c
CommitLineData
56ebfaf1
LP
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
98c38001
LP
22#include <sys/prctl.h>
23
56ebfaf1
LP
24#include "util.h"
25#include "strv.h"
26#include "copy.h"
c6878637 27#include "rm-rf.h"
56ebfaf1 28#include "btrfs-util.h"
2c140ded 29#include "capability.h"
dc2c282b
LP
30#include "pull-job.h"
31#include "pull-common.h"
0b452006 32#include "process-util.h"
56ebfaf1
LP
33
34#define FILENAME_ESCAPE "/.#\"\'"
35
dc2c282b 36int pull_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) {
56ebfaf1
LP
37 _cleanup_free_ char *escaped_url = NULL;
38 _cleanup_closedir_ DIR *d = NULL;
39 _cleanup_strv_free_ char **l = NULL;
40 struct dirent *de;
41 int r;
42
43 assert(url);
44 assert(etags);
45
46 if (!image_root)
47 image_root = "/var/lib/machines";
48
49 escaped_url = xescape(url, FILENAME_ESCAPE);
50 if (!escaped_url)
51 return -ENOMEM;
52
53 d = opendir(image_root);
54 if (!d) {
55 if (errno == ENOENT) {
56 *etags = NULL;
57 return 0;
58 }
59
60 return -errno;
61 }
62
63 FOREACH_DIRENT_ALL(de, d, return -errno) {
64 const char *a, *b;
65 char *u;
66
67 if (de->d_type != DT_UNKNOWN &&
68 de->d_type != dt)
69 continue;
70
71 if (prefix) {
72 a = startswith(de->d_name, prefix);
73 if (!a)
74 continue;
75 } else
76 a = de->d_name;
77
78 a = startswith(a, escaped_url);
79 if (!a)
80 continue;
81
82 a = startswith(a, ".");
83 if (!a)
84 continue;
85
86 if (suffix) {
87 b = endswith(de->d_name, suffix);
88 if (!b)
89 continue;
90 } else
91 b = strchr(de->d_name, 0);
92
93 if (a >= b)
94 continue;
95
527b7a42
LP
96 r = cunescape_length(a, b - a, 0, &u);
97 if (r < 0)
98 return r;
56ebfaf1
LP
99
100 if (!http_etag_is_valid(u)) {
101 free(u);
102 continue;
103 }
104
105 r = strv_consume(&l, u);
106 if (r < 0)
107 return r;
108 }
109
110 *etags = l;
111 l = NULL;
112
113 return 0;
114}
115
dc2c282b 116int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
56ebfaf1
LP
117 const char *p;
118 int r;
119
120 assert(final);
121 assert(local);
122
123 if (!image_root)
124 image_root = "/var/lib/machines";
125
63c372cb 126 p = strjoina(image_root, "/", local);
56ebfaf1 127
d9e2daaf
LP
128 if (force_local)
129 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
56ebfaf1 130
e9bc1871 131 r = btrfs_subvol_snapshot(final, p, 0);
56ebfaf1
LP
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
dc2c282b 144int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
56ebfaf1
LP
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}
85dbc41d 174
dc2c282b
LP
175int pull_make_verification_jobs(
176 PullJob **ret_checksum_job,
177 PullJob **ret_signature_job,
98c38001
LP
178 ImportVerify verify,
179 const char *url,
180 CurlGlue *glue,
dc2c282b 181 PullJobFinished on_finished,
98c38001
LP
182 void *userdata) {
183
dc2c282b 184 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
98c38001
LP
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
dc2c282b 202 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
98c38001
LP
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
dc2c282b 218 r = pull_job_new(&signature_job, signature_url, glue, userdata);
98c38001
LP
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
dc2c282b
LP
234int pull_verify(
235 PullJob *main_job,
236 PullJob *checksum_job,
237 PullJob *signature_job) {
98c38001
LP
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;
0acfdffe 243 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
98c38001 244 _cleanup_sigkill_wait_ pid_t pid = 0;
0acfdffe 245 bool gpg_home_created = false;
98c38001
LP
246 int r;
247
248 assert(main_job);
dc2c282b 249 assert(main_job->state == PULL_JOB_DONE);
98c38001
LP
250
251 if (!checksum_job)
252 return 0;
253
254 assert(main_job->calc_checksum);
255 assert(main_job->checksum);
dc2c282b 256 assert(checksum_job->state == PULL_JOB_DONE);
98c38001
LP
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
63c372cb 272 line = strjoina(main_job->checksum, " *", fn, "\n");
98c38001
LP
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
dc2c282b 289 assert(signature_job->state == PULL_JOB_DONE);
98c38001
LP
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)
0100b6e1 298 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
98c38001
LP
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
0acfdffe
LP
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
98c38001
LP
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",
0acfdffe
LP
329 NULL, /* --homedir= */
330 NULL, /* --keyring= */
98c38001
LP
331 NULL, /* --verify */
332 NULL, /* signature file */
333 NULL, /* dash */
334 NULL /* trailing NULL */
335 };
0acfdffe 336 unsigned k = ELEMENTSOF(cmd) - 6;
98c38001
LP
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
0acfdffe
LP
369 cmd[k++] = strjoina("--homedir=", gpg_home);
370
98c38001
LP
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;
1c49d1ba
LP
376 else
377 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
98c38001
LP
378
379 cmd[k++] = "--verify";
380 cmd[k++] = sig_file_path;
381 cmd[k++] = "-";
382 cmd[k++] = NULL;
383
3d7415f4
LP
384 fd_cloexec(STDIN_FILENO, false);
385 fd_cloexec(STDOUT_FILENO, false);
386 fd_cloexec(STDERR_FILENO, false);
387
0acfdffe 388 execvp("gpg2", (char * const *) cmd);
98c38001
LP
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
416finish:
417 if (sig_file >= 0)
418 unlink(sig_file_path);
419
0acfdffe 420 if (gpg_home_created)
c6878637 421 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
0acfdffe 422
98c38001
LP
423 return r;
424}