chiark / gitweb /
network: dhcp - split out the duid structure into a new header file
[elogind.git] / src / import / import-util.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-util.h"
30
31 #define FILENAME_ESCAPE "/.#\"\'"
32
33 bool http_etag_is_valid(const char *etag) {
34         if (!endswith(etag, "\""))
35                 return false;
36
37         if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
38                 return false;
39
40         return true;
41 }
42
43 int import_find_old_etags(const char *url, const char *image_root, int dt, const char *prefix, const char *suffix, char ***etags) {
44         _cleanup_free_ char *escaped_url = NULL;
45         _cleanup_closedir_ DIR *d = NULL;
46         _cleanup_strv_free_ char **l = NULL;
47         struct dirent *de;
48         int r;
49
50         assert(url);
51         assert(etags);
52
53         if (!image_root)
54                 image_root = "/var/lib/machines";
55
56         escaped_url = xescape(url, FILENAME_ESCAPE);
57         if (!escaped_url)
58                 return -ENOMEM;
59
60         d = opendir(image_root);
61         if (!d) {
62                 if (errno == ENOENT) {
63                         *etags = NULL;
64                         return 0;
65                 }
66
67                 return -errno;
68         }
69
70         FOREACH_DIRENT_ALL(de, d, return -errno) {
71                 const char *a, *b;
72                 char *u;
73
74                 if (de->d_type != DT_UNKNOWN &&
75                     de->d_type != dt)
76                         continue;
77
78                 if (prefix) {
79                         a = startswith(de->d_name, prefix);
80                         if (!a)
81                                 continue;
82                 } else
83                         a = de->d_name;
84
85                 a = startswith(a, escaped_url);
86                 if (!a)
87                         continue;
88
89                 a = startswith(a, ".");
90                 if (!a)
91                         continue;
92
93                 if (suffix) {
94                         b = endswith(de->d_name, suffix);
95                         if (!b)
96                                 continue;
97                 } else
98                         b = strchr(de->d_name, 0);
99
100                 if (a >= b)
101                         continue;
102
103                 u = cunescape_length(a, b - a);
104                 if (!u)
105                         return -ENOMEM;
106
107                 if (!http_etag_is_valid(u)) {
108                         free(u);
109                         continue;
110                 }
111
112                 r = strv_consume(&l, u);
113                 if (r < 0)
114                         return r;
115         }
116
117         *etags = l;
118         l = NULL;
119
120         return 0;
121 }
122
123 int import_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
124         const char *p;
125         int r;
126
127         assert(final);
128         assert(local);
129
130         if (!image_root)
131                 image_root = "/var/lib/machines";
132
133         p = strappenda(image_root, "/", local);
134
135         if (force_local) {
136                 (void) btrfs_subvol_remove(p);
137                 (void) rm_rf_dangerous(p, false, true, false);
138         }
139
140         r = btrfs_subvol_snapshot(final, p, false, false);
141         if (r == -ENOTTY) {
142                 r = copy_tree(final, p, false);
143                 if (r < 0)
144                         return log_error_errno(r, "Failed to copy image: %m");
145         } else if (r < 0)
146                 return log_error_errno(r, "Failed to create local image: %m");
147
148         log_info("Created new local image '%s'.", local);
149
150         return 0;
151 }
152
153 int import_make_read_only_fd(int fd) {
154         int r;
155
156         assert(fd >= 0);
157
158         /* First, let's make this a read-only subvolume if it refers
159          * to a subvolume */
160         r = btrfs_subvol_set_read_only_fd(fd, true);
161         if (r == -ENOTTY || r == -ENOTDIR || r == -EINVAL) {
162                 struct stat st;
163
164                 /* This doesn't refer to a subvolume, or the file
165                  * system isn't even btrfs. In that, case fall back to
166                  * chmod()ing */
167
168                 r = fstat(fd, &st);
169                 if (r < 0)
170                         return log_error_errno(errno, "Failed to stat temporary image: %m");
171
172                 /* Drop "w" flag */
173                 if (fchmod(fd, st.st_mode & 07555) < 0)
174                         return log_error_errno(errno, "Failed to chmod() final image: %m");
175
176                 return 0;
177
178         } else if (r < 0)
179                 return log_error_errno(r, "Failed to make subvolume read-only: %m");
180
181         return 0;
182 }
183
184 int import_make_read_only(const char *path) {
185         _cleanup_close_ int fd = 1;
186
187         fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
188         if (fd < 0)
189                 return log_error_errno(errno, "Failed to open %s: %m", path);
190
191         return import_make_read_only_fd(fd);
192 }
193
194 int import_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
195         _cleanup_free_ char *escaped_url = NULL;
196         char *path;
197
198         assert(url);
199         assert(ret);
200
201         if (!image_root)
202                 image_root = "/var/lib/machines";
203
204         escaped_url = xescape(url, FILENAME_ESCAPE);
205         if (!escaped_url)
206                 return -ENOMEM;
207
208         if (etag) {
209                 _cleanup_free_ char *escaped_etag = NULL;
210
211                 escaped_etag = xescape(etag, FILENAME_ESCAPE);
212                 if (!escaped_etag)
213                         return -ENOMEM;
214
215                 path = strjoin(image_root, "/", strempty(prefix), escaped_url, ".", escaped_etag, strempty(suffix), NULL);
216         } else
217                 path = strjoin(image_root, "/", strempty(prefix), escaped_url, strempty(suffix), NULL);
218         if (!path)
219                 return -ENOMEM;
220
221         *ret = path;
222         return 0;
223 }
224
225 int import_url_last_component(const char *url, char **ret) {
226         const char *e, *p;
227         char *s;
228
229         e = strchrnul(url, '?');
230
231         while (e > url && e[-1] == '/')
232                 e--;
233
234         p = e;
235         while (p > url && p[-1] != '/')
236                 p--;
237
238         if (e <= p)
239                 return -EINVAL;
240
241         s = strndup(p, e - p);
242         if (!s)
243                 return -ENOMEM;
244
245         *ret = s;
246         return 0;
247 }
248
249
250 int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
251         const char *e;
252         char *s;
253
254         assert(url);
255         assert(ret);
256
257         e = strchrnul(url, '?');
258
259         while (e > url && e[-1] == '/')
260                 e--;
261
262         while (e > url && e[-1] != '/')
263                 e--;
264
265         if (e <= url)
266                 return -EINVAL;
267
268         s = new(char, (e - url) + strlen(suffix) + 1);
269         if (!s)
270                 return -ENOMEM;
271
272         strcpy(mempcpy(s, url, e - url), suffix);
273         *ret = s;
274         return 0;
275 }
276
277 static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = {
278         [IMPORT_VERIFY_NO] = "no",
279         [IMPORT_VERIFY_SUM] = "sum",
280         [IMPORT_VERIFY_SIGNATURE] = "signature",
281 };
282
283 DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify);
284
285 int import_make_verification_jobs(
286                 ImportJob **ret_checksum_job,
287                 ImportJob **ret_signature_job,
288                 ImportVerify verify,
289                 const char *url,
290                 CurlGlue *glue,
291                 ImportJobFinished on_finished,
292                 void *userdata) {
293
294         _cleanup_(import_job_unrefp) ImportJob *checksum_job = NULL, *signature_job = NULL;
295         int r;
296
297         assert(ret_checksum_job);
298         assert(ret_signature_job);
299         assert(verify >= 0);
300         assert(verify < _IMPORT_VERIFY_MAX);
301         assert(url);
302         assert(glue);
303
304         if (verify != IMPORT_VERIFY_NO) {
305                 _cleanup_free_ char *checksum_url = NULL;
306
307                 /* Queue job for the SHA256SUMS file for the image */
308                 r = import_url_change_last_component(url, "SHA256SUMS", &checksum_url);
309                 if (r < 0)
310                         return r;
311
312                 r = import_job_new(&checksum_job, checksum_url, glue, userdata);
313                 if (r < 0)
314                         return r;
315
316                 checksum_job->on_finished = on_finished;
317                 checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
318         }
319
320         if (verify == IMPORT_VERIFY_SIGNATURE) {
321                 _cleanup_free_ char *signature_url = NULL;
322
323                 /* Queue job for the SHA256SUMS.gpg file for the image. */
324                 r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
325                 if (r < 0)
326                         return r;
327
328                 r = import_job_new(&signature_job, signature_url, glue, userdata);
329                 if (r < 0)
330                         return r;
331
332                 signature_job->on_finished = on_finished;
333                 signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
334         }
335
336         *ret_checksum_job = checksum_job;
337         *ret_signature_job = signature_job;
338
339         checksum_job = signature_job = NULL;
340
341         return 0;
342 }
343
344 int import_verify(
345                 ImportJob *main_job,
346                 ImportJob *checksum_job,
347                 ImportJob *signature_job) {
348
349         _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
350         _cleanup_free_ char *fn = NULL;
351         _cleanup_close_ int sig_file = -1;
352         const char *p, *line;
353         char sig_file_path[] = "/tmp/sigXXXXXX";
354         _cleanup_sigkill_wait_ pid_t pid = 0;
355         int r;
356
357         assert(main_job);
358         assert(main_job->state == IMPORT_JOB_DONE);
359
360         if (!checksum_job)
361                 return 0;
362
363         assert(main_job->calc_checksum);
364         assert(main_job->checksum);
365         assert(checksum_job->state == IMPORT_JOB_DONE);
366
367         if (!checksum_job->payload || checksum_job->payload_size <= 0) {
368                 log_error("Checksum is empty, cannot verify.");
369                 return -EBADMSG;
370         }
371
372         r = import_url_last_component(main_job->url, &fn);
373         if (r < 0)
374                 return log_oom();
375
376         if (!filename_is_valid(fn)) {
377                 log_error("Cannot verify checksum, could not determine valid server-side file name.");
378                 return -EBADMSG;
379         }
380
381         line = strappenda(main_job->checksum, " *", fn, "\n");
382
383         p = memmem(checksum_job->payload,
384                    checksum_job->payload_size,
385                    line,
386                    strlen(line));
387
388         if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) {
389                 log_error("Checksum did not check out, payload has been tempered with.");
390                 return -EBADMSG;
391         }
392
393         log_info("SHA256 checksum of %s is valid.", main_job->url);
394
395         if (!signature_job)
396                 return 0;
397
398         assert(signature_job->state == IMPORT_JOB_DONE);
399
400         if (!signature_job->payload || signature_job->payload_size <= 0) {
401                 log_error("Signature is empty, cannot verify.");
402                 return -EBADMSG;
403         }
404
405         r = pipe2(gpg_pipe, O_CLOEXEC);
406         if (r < 0)
407                 return log_error_errno(errno, "Failed to create pipe for gpg: %m");
408
409         sig_file = mkostemp(sig_file_path, O_RDWR);
410         if (sig_file < 0)
411                 return log_error_errno(errno, "Failed to create temporary file: %m");
412
413         r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
414         if (r < 0) {
415                 log_error_errno(r, "Failed to write to temporary file: %m");
416                 goto finish;
417         }
418
419         pid = fork();
420         if (pid < 0)
421                 return log_error_errno(errno, "Failed to fork off gpg: %m");
422         if (pid == 0) {
423                 const char *cmd[] = {
424                         "gpg",
425                         "--no-options",
426                         "--no-default-keyring",
427                         "--no-auto-key-locate",
428                         "--no-auto-check-trustdb",
429                         "--batch",
430                         "--trust-model=always",
431                         "--keyring=" VENDOR_KEYRING_PATH,
432                         NULL, /* maybe user keyring */
433                         NULL, /* --verify */
434                         NULL, /* signature file */
435                         NULL, /* dash */
436                         NULL  /* trailing NULL */
437                 };
438                 unsigned k = ELEMENTSOF(cmd) - 5;
439                 int null_fd;
440
441                 /* Child */
442
443                 reset_all_signal_handlers();
444                 reset_signal_mask();
445                 assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
446
447                 gpg_pipe[1] = safe_close(gpg_pipe[1]);
448
449                 if (dup2(gpg_pipe[0], STDIN_FILENO) != STDIN_FILENO) {
450                         log_error_errno(errno, "Failed to dup2() fd: %m");
451                         _exit(EXIT_FAILURE);
452                 }
453
454                 if (gpg_pipe[0] != STDIN_FILENO)
455                         gpg_pipe[0] = safe_close(gpg_pipe[0]);
456
457                 null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
458                 if (null_fd < 0) {
459                         log_error_errno(errno, "Failed to open /dev/null: %m");
460                         _exit(EXIT_FAILURE);
461                 }
462
463                 if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
464                         log_error_errno(errno, "Failed to dup2() fd: %m");
465                         _exit(EXIT_FAILURE);
466                 }
467
468                 if (null_fd != STDOUT_FILENO)
469                         null_fd = safe_close(null_fd);
470
471                 /* We add the user keyring only to the command line
472                  * arguments, if it's around since gpg fails
473                  * otherwise. */
474                 if (access(USER_KEYRING_PATH, F_OK) >= 0)
475                         cmd[k++] = "--keyring=" USER_KEYRING_PATH;
476
477                 cmd[k++] = "--verify";
478                 cmd[k++] = sig_file_path;
479                 cmd[k++] = "-";
480                 cmd[k++] = NULL;
481
482                 execvp("gpg", (char * const *) cmd);
483                 log_error_errno(errno, "Failed to execute gpg: %m");
484                 _exit(EXIT_FAILURE);
485         }
486
487         gpg_pipe[0] = safe_close(gpg_pipe[0]);
488
489         r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
490         if (r < 0) {
491                 log_error_errno(r, "Failed to write to pipe: %m");
492                 goto finish;
493         }
494
495         gpg_pipe[1] = safe_close(gpg_pipe[1]);
496
497         r = wait_for_terminate_and_warn("gpg", pid, true);
498         pid = 0;
499         if (r < 0)
500                 goto finish;
501         if (r > 0) {
502                 log_error("Signature verification failed.");
503                 r = -EBADMSG;
504         } else {
505                 log_info("Signature verification succeeded.");
506                 r = 0;
507         }
508
509 finish:
510         if (sig_file >= 0)
511                 unlink(sig_file_path);
512
513         return r;
514 }