]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/pull-common.c
util: rework rm_rf() logic
[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
95 u = cunescape_length(a, b - a);
96 if (!u)
97 return -ENOMEM;
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
LP
126
127 if (force_local) {
128 (void) btrfs_subvol_remove(p);
c6878637 129 (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL);
56ebfaf1
LP
130 }
131
132 r = btrfs_subvol_snapshot(final, p, false, false);
133 if (r == -ENOTTY) {
134 r = copy_tree(final, p, false);
135 if (r < 0)
136 return log_error_errno(r, "Failed to copy image: %m");
137 } else if (r < 0)
138 return log_error_errno(r, "Failed to create local image: %m");
139
140 log_info("Created new local image '%s'.", local);
141
142 return 0;
143}
144
dc2c282b 145int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
56ebfaf1
LP
146 _cleanup_free_ char *escaped_url = NULL;
147 char *path;
148
149 assert(url);
150 assert(ret);
151
152 if (!image_root)
153 image_root = "/var/lib/machines";
154
155 escaped_url = xescape(url, FILENAME_ESCAPE);
156 if (!escaped_url)
157 return -ENOMEM;
158
159 if (etag) {
160 _cleanup_free_ char *escaped_etag = NULL;
161
162 escaped_etag = xescape(etag, FILENAME_ESCAPE);
163 if (!escaped_etag)
164 return -ENOMEM;
165
166 path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL);
167 } else
168 path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL);
169 if (!path)
170 return -ENOMEM;
171
172 *ret = path;
173 return 0;
174}
85dbc41d 175
dc2c282b
LP
176int pull_make_verification_jobs(
177 PullJob **ret_checksum_job,
178 PullJob **ret_signature_job,
98c38001
LP
179 ImportVerify verify,
180 const char *url,
181 CurlGlue *glue,
dc2c282b 182 PullJobFinished on_finished,
98c38001
LP
183 void *userdata) {
184
dc2c282b 185 _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
98c38001
LP
186 int r;
187
188 assert(ret_checksum_job);
189 assert(ret_signature_job);
190 assert(verify >= 0);
191 assert(verify < _IMPORT_VERIFY_MAX);
192 assert(url);
193 assert(glue);
194
195 if (verify != IMPORT_VERIFY_NO) {
196 _cleanup_free_ char *checksum_url = NULL;
197
198 /* Queue job for the SHA256SUMS file for the image */
199 r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url);
200 if (r < 0)
201 return r;
202
dc2c282b 203 r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
98c38001
LP
204 if (r < 0)
205 return r;
206
207 checksum_job->on_finished = on_finished;
208 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
209 }
210
211 if (verify == IMPORT_VERIFY_SIGNATURE) {
212 _cleanup_free_ char *signature_url = NULL;
213
214 /* Queue job for the SHA256SUMS.gpg file for the image. */
215 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
216 if (r < 0)
217 return r;
218
dc2c282b 219 r = pull_job_new(&signature_job, signature_url, glue, userdata);
98c38001
LP
220 if (r < 0)
221 return r;
222
223 signature_job->on_finished = on_finished;
224 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
225 }
226
227 *ret_checksum_job = checksum_job;
228 *ret_signature_job = signature_job;
229
230 checksum_job = signature_job = NULL;
231
232 return 0;
233}
234
dc2c282b
LP
235int pull_verify(
236 PullJob *main_job,
237 PullJob *checksum_job,
238 PullJob *signature_job) {
98c38001
LP
239
240 _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
241 _cleanup_free_ char *fn = NULL;
242 _cleanup_close_ int sig_file = -1;
243 const char *p, *line;
0acfdffe 244 char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
98c38001 245 _cleanup_sigkill_wait_ pid_t pid = 0;
0acfdffe 246 bool gpg_home_created = false;
98c38001
LP
247 int r;
248
249 assert(main_job);
dc2c282b 250 assert(main_job->state == PULL_JOB_DONE);
98c38001
LP
251
252 if (!checksum_job)
253 return 0;
254
255 assert(main_job->calc_checksum);
256 assert(main_job->checksum);
dc2c282b 257 assert(checksum_job->state == PULL_JOB_DONE);
98c38001
LP
258
259 if (!checksum_job->payload || checksum_job->payload_size <= 0) {
260 log_error("Checksum is empty, cannot verify.");
261 return -EBADMSG;
262 }
263
264 r = import_url_last_component(main_job->url, &fn);
265 if (r < 0)
266 return log_oom();
267
268 if (!filename_is_valid(fn)) {
269 log_error("Cannot verify checksum, could not determine valid server-side file name.");
270 return -EBADMSG;
271 }
272
63c372cb 273 line = strjoina(main_job->checksum, " *", fn, "\n");
98c38001
LP
274
275 p = memmem(checksum_job->payload,
276 checksum_job->payload_size,
277 line,
278 strlen(line));
279
280 if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
281 log_error("Checksum did not check out, payload has been tempered with.");
282 return -EBADMSG;
283 }
284
285 log_info("SHA256 checksum of %s is valid.", main_job->url);
286
287 if (!signature_job)
288 return 0;
289
dc2c282b 290 assert(signature_job->state == PULL_JOB_DONE);
98c38001
LP
291
292 if (!signature_job->payload || signature_job->payload_size <= 0) {
293 log_error("Signature is empty, cannot verify.");
294 return -EBADMSG;
295 }
296
297 r = pipe2(gpg_pipe, O_CLOEXEC);
298 if (r < 0)
0100b6e1 299 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
98c38001
LP
300
301 sig_file = mkostemp(sig_file_path, O_RDWR);
302 if (sig_file < 0)
303 return log_error_errno(errno, "Failed to create temporary file: %m");
304
305 r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
306 if (r < 0) {
307 log_error_errno(r, "Failed to write to temporary file: %m");
308 goto finish;
309 }
310
0acfdffe
LP
311 if (!mkdtemp(gpg_home)) {
312 r = log_error_errno(errno, "Failed to create tempory home for gpg: %m");
313 goto finish;
314 }
315
316 gpg_home_created = true;
317
98c38001
LP
318 pid = fork();
319 if (pid < 0)
320 return log_error_errno(errno, "Failed to fork off gpg: %m");
321 if (pid == 0) {
322 const char *cmd[] = {
323 "gpg",
324 "--no-options",
325 "--no-default-keyring",
326 "--no-auto-key-locate",
327 "--no-auto-check-trustdb",
328 "--batch",
329 "--trust-model=always",
0acfdffe
LP
330 NULL, /* --homedir= */
331 NULL, /* --keyring= */
98c38001
LP
332 NULL, /* --verify */
333 NULL, /* signature file */
334 NULL, /* dash */
335 NULL /* trailing NULL */
336 };
0acfdffe 337 unsigned k = ELEMENTSOF(cmd) - 6;
98c38001
LP
338 int null_fd;
339
340 /* Child */
341
342 reset_all_signal_handlers();
343 reset_signal_mask();
344 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
345
346 gpg_pipe[1] = safe_close(gpg_pipe[1]);
347
348 if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
349 log_error_errno(errno, "Failed to dup2() fd: %m");
350 _exit(EXIT_FAILURE);
351 }
352
353 if (gpg_pipe[0] != STDIN_FILENO)
354 gpg_pipe[0] = safe_close(gpg_pipe[0]);
355
356 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
357 if (null_fd < 0) {
358 log_error_errno(errno, "Failed to open /dev/null: %m");
359 _exit(EXIT_FAILURE);
360 }
361
362 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
363 log_error_errno(errno, "Failed to dup2() fd: %m");
364 _exit(EXIT_FAILURE);
365 }
366
367 if (null_fd != STDOUT_FILENO)
368 null_fd = safe_close(null_fd);
369
0acfdffe
LP
370 cmd[k++] = strjoina("--homedir=", gpg_home);
371
98c38001
LP
372 /* We add the user keyring only to the command line
373 * arguments, if it's around since gpg fails
374 * otherwise. */
375 if (access(USER_KEYRING_PATH, F_OK) >= 0)
376 cmd[k++] = "--keyring=" USER_KEYRING_PATH;
1c49d1ba
LP
377 else
378 cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
98c38001
LP
379
380 cmd[k++] = "--verify";
381 cmd[k++] = sig_file_path;
382 cmd[k++] = "-";
383 cmd[k++] = NULL;
384
3d7415f4
LP
385 fd_cloexec(STDIN_FILENO, false);
386 fd_cloexec(STDOUT_FILENO, false);
387 fd_cloexec(STDERR_FILENO, false);
388
0acfdffe 389 execvp("gpg2", (char * const *) cmd);
98c38001
LP
390 execvp("gpg", (char * const *) cmd);
391 log_error_errno(errno, "Failed to execute gpg: %m");
392 _exit(EXIT_FAILURE);
393 }
394
395 gpg_pipe[0] = safe_close(gpg_pipe[0]);
396
397 r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
398 if (r < 0) {
399 log_error_errno(r, "Failed to write to pipe: %m");
400 goto finish;
401 }
402
403 gpg_pipe[1] = safe_close(gpg_pipe[1]);
404
405 r = wait_for_terminate_and_warn("gpg", pid, true);
406 pid = 0;
407 if (r < 0)
408 goto finish;
409 if (r > 0) {
410 log_error("Signature verification failed.");
411 r = -EBADMSG;
412 } else {
413 log_info("Signature verification succeeded.");
414 r = 0;
415 }
416
417finish:
418 if (sig_file >= 0)
419 unlink(sig_file_path);
420
0acfdffe 421 if (gpg_home_created)
c6878637 422 (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
0acfdffe 423
98c38001
LP
424 return r;
425}