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