Call febootstrap-supermin-helper using the new -u and -g options
[libguestfs.git] / src / appliance.c
1 /* libguestfs
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <time.h>
28 #include <sys/stat.h>
29 #include <sys/select.h>
30 #include <sys/wait.h>
31 #include <utime.h>
32
33 #ifdef HAVE_SYS_TYPES_H
34 #include <sys/types.h>
35 #endif
36
37 #include "guestfs.h"
38 #include "guestfs-internal.h"
39 #include "guestfs-internal-actions.h"
40 #include "guestfs_protocol.h"
41
42 static const char *kernel_name = "vmlinuz." host_cpu;
43 static const char *initrd_name = "initramfs." host_cpu ".img";
44
45 static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem);
46 static int dir_contains_file (const char *dir, const char *file);
47 static int dir_contains_files (const char *dir, ...);
48 static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data);
49 static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data);
50 static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path);
51 static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
52 static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance);
53 static int run_supermin_helper (guestfs_h *g, const char *supermin_path, const char *cachedir, size_t cdlen);
54
55 /* Locate or build the appliance.
56  *
57  * This function locates or builds the appliance as necessary,
58  * handling the supermin appliance, caching of supermin-built
59  * appliances, or using an ordinary appliance.
60  *
61  * The return value is 0 = good, -1 = error.  Returned in '*kernel'
62  * will be the name of the kernel to use, '*initrd' the name of the
63  * initrd, '*appliance' the name of the ext2 root filesystem.
64  * '*appliance' can be NULL, meaning that we are using an ordinary
65  * (non-ext2) appliance.  All three strings must be freed by the
66  * caller.  However the referenced files themselves must not be
67  * deleted.
68  *
69  * The process is as follows:
70  *
71  * (1) Look for the first element of g->path which contains a
72  * supermin appliance skeleton.  If no element has this, skip
73  * straight to step (5).
74  * (2) Calculate the checksum of this supermin appliance.
75  * (3) Check whether $TMPDIR/$checksum/ directory exists, contains
76  * a cached appliance, and passes basic security checks.  If so,
77  * return this appliance.
78  * (4) Try to build the supermin appliance into $TMPDIR/$checksum/.
79  * If this is successful, return it.
80  * (5) Check each element of g->path, looking for an ordinary appliance.
81  * If one is found, return it.
82  */
83 int
84 guestfs___build_appliance (guestfs_h *g,
85                            char **kernel, char **initrd, char **appliance)
86 {
87   int r;
88
89   /* Step (1). */
90   char *supermin_path;
91   r = find_path (g, contains_supermin_appliance, NULL, &supermin_path);
92   if (r == -1)
93     return -1;
94
95   if (r == 1) {
96     /* Step (2): calculate checksum. */
97     char *checksum = calculate_supermin_checksum (g, supermin_path);
98     if (checksum) {
99       /* Step (3): cached appliance exists? */
100       r = check_for_cached_appliance (g, supermin_path, checksum,
101                                       kernel, initrd, appliance);
102       if (r != 0) {
103         free (supermin_path);
104         free (checksum);
105         return r == 1 ? 0 : -1;
106       }
107
108       /* Step (4): build supermin appliance. */
109       r = build_supermin_appliance (g, supermin_path, checksum,
110                                     kernel, initrd, appliance);
111       free (supermin_path);
112       free (checksum);
113       return r;
114     }
115     free (supermin_path);
116   }
117
118   /* Step (5). */
119   char *path;
120   r = find_path (g, contains_ordinary_appliance, NULL, &path);
121   if (r == -1)
122     return -1;
123
124   if (r == 1) {
125     size_t len = strlen (path);
126     *kernel = safe_malloc (g, len + strlen (kernel_name) + 2);
127     *initrd = safe_malloc (g, len + strlen (initrd_name) + 2);
128     sprintf (*kernel, "%s/%s", path, kernel_name);
129     sprintf (*initrd, "%s/%s", path, initrd_name);
130     *appliance = NULL;
131
132     free (path);
133     return 0;
134   }
135
136   error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"),
137          g->path);
138   return -1;
139 }
140
141 static int
142 contains_supermin_appliance (guestfs_h *g, const char *path, void *data)
143 {
144   return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL);
145 }
146
147 static int
148 contains_ordinary_appliance (guestfs_h *g, const char *path, void *data)
149 {
150   return dir_contains_files (path, kernel_name, initrd_name, NULL);
151 }
152
153 /* supermin_path is a path which is known to contain a supermin
154  * appliance.  Using febootstrap-supermin-helper -f checksum calculate
155  * the checksum so we can see if it is cached.
156  */
157 static char *
158 calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
159 {
160   size_t len = 2 * strlen (supermin_path) + 256;
161   char cmd[len];
162   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
163
164   if (!pass_u_g_args)
165     snprintf (cmd, len,
166               "febootstrap-supermin-helper%s "
167               "-f checksum "
168               "'%s/supermin.d' "
169               host_cpu,
170               g->verbose ? " --verbose" : "",
171               supermin_path);
172   else
173     snprintf (cmd, len,
174               "febootstrap-supermin-helper%s "
175               "-u %i "
176               "-g %i "
177               "-f checksum "
178               "'%s/supermin.d' "
179               host_cpu,
180               g->verbose ? " --verbose" : "",
181               geteuid (), getegid (),
182               supermin_path);
183
184   if (g->verbose)
185     guestfs___print_timestamped_message (g, "%s", cmd);
186
187   /* Errors here are non-fatal, so we don't need to call error(). */
188   FILE *pp = popen (cmd, "r");
189   if (pp == NULL)
190     return NULL;
191
192   char checksum[256];
193   if (fgets (checksum, sizeof checksum, pp) == NULL) {
194     pclose (pp);
195     return NULL;
196   }
197
198   if (pclose (pp) == -1) {
199     perror ("pclose");
200     return NULL;
201   }
202
203   len = strlen (checksum);
204
205   if (len < 16) {               /* sanity check */
206     fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
207     return NULL;
208   }
209
210   if (len > 0 && checksum[len-1] == '\n')
211     checksum[--len] = '\0';
212
213   return safe_strndup (g, checksum, len);
214 }
215
216 /* Check for cached appliance in $TMPDIR/$checksum.  Check it exists
217  * and passes some basic security checks.
218  *
219  * Returns:
220  * 1 = exists, and passes
221  * 0 = does not exist
222  * -1 = error which should abort the whole launch process
223  */
224 static int
225 security_check_cache_file (guestfs_h *g, const char *filename,
226                            const struct stat *statbuf)
227 {
228   uid_t uid = geteuid ();
229
230   if (statbuf->st_uid != uid) {
231     error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"),
232            filename, uid);
233     return -1;
234   }
235
236   if ((statbuf->st_mode & 0022) != 0) {
237     error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"),
238            filename, statbuf->st_mode);
239     return -1;
240   }
241
242   return 0;
243 }
244
245 static int
246 check_for_cached_appliance (guestfs_h *g,
247                             const char *supermin_path, const char *checksum,
248                             char **kernel, char **initrd, char **appliance)
249 {
250   const char *tmpdir = guestfs_tmpdir ();
251
252   size_t len = strlen (tmpdir) + strlen (checksum) + 2;
253   char cachedir[len];
254   snprintf (cachedir, len, "%s/%s", tmpdir, checksum);
255
256   /* Touch the directory to prevent it being deleting in a rare race
257    * between us doing the checks and a tmp cleaner running.  Note this
258    * doesn't create the directory, and we ignore any error.
259    */
260   (void) utime (cachedir, NULL);
261
262   /* See if the cache directory exists and passes some simple checks
263    * to make sure it has not been tampered with.  Note that geteuid()
264    * forms a part of the checksum.
265    */
266   struct stat statbuf;
267   if (lstat (cachedir, &statbuf) == -1)
268     return 0;
269
270   if (security_check_cache_file (g, cachedir, &statbuf) == -1)
271     return -1;
272
273   int ret;
274
275   *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */);
276   *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */);
277   *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */);
278   sprintf (*kernel, "%s/kernel", cachedir);
279   sprintf (*initrd, "%s/initrd", cachedir);
280   sprintf (*appliance, "%s/root", cachedir);
281
282   /* Touch the files to prevent them being deleted, and to bring the
283    * cache up to date.  Note this doesn't create the files.
284    */
285   (void) utime (*kernel, NULL);
286
287   /* NB. *kernel is a symlink, so we want to check the kernel, not the
288    * link (stat, not lstat).  We don't do a security check on the
289    * kernel since it's always under /boot.
290    */
291   if (stat (*kernel, &statbuf) == -1) {
292     ret = 0;
293     goto error;
294   }
295
296   (void) utime (*initrd, NULL);
297
298   if (lstat (*initrd, &statbuf) == -1) {
299     ret = 0;
300     goto error;
301   }
302
303   if (security_check_cache_file (g, *initrd, &statbuf) == -1) {
304     ret = -1;
305     goto error;
306   }
307
308   (void) utime (*appliance, NULL);
309
310   if (lstat (*appliance, &statbuf) == -1) {
311     ret = 0;
312     goto error;
313   }
314
315   if (security_check_cache_file (g, *appliance, &statbuf) == -1) {
316     ret = -1;
317     goto error;
318   }
319
320   /* Exists! */
321   return 1;
322
323  error:
324   free (*kernel);
325   free (*initrd);
326   free (*appliance);
327   return ret;
328 }
329
330 /* Build supermin appliance from supermin_path to $TMPDIR/$checksum.
331  *
332  * Returns:
333  * 0 = built
334  * -1 = error (aborts launch)
335  */
336 static int
337 build_supermin_appliance (guestfs_h *g,
338                           const char *supermin_path, const char *checksum,
339                           char **kernel, char **initrd, char **appliance)
340 {
341   if (g->verbose)
342     guestfs___print_timestamped_message (g, "begin building supermin appliance");
343
344   const char *tmpdir = guestfs_tmpdir ();
345   size_t cdlen = strlen (tmpdir) + strlen (checksum) + 2;
346   char cachedir[cdlen];
347   snprintf (cachedir, cdlen, "%s/%s", tmpdir, checksum);
348
349   /* Don't worry about this failing, because the
350    * febootstrap-supermin-helper command will fail if the directory
351    * doesn't exist.  Note the directory might already exist, eg. if a
352    * tmp cleaner has removed the existing appliance but not the
353    * directory itself.
354    */
355   (void) mkdir (cachedir, 0755);
356
357   if (g->verbose)
358     guestfs___print_timestamped_message (g, "run febootstrap-supermin-helper");
359
360   int r = run_supermin_helper (g, supermin_path, cachedir, cdlen);
361   if (r == -1)
362     return -1;
363
364   if (g->verbose)
365     guestfs___print_timestamped_message (g, "finished building supermin appliance");
366
367   *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */);
368   *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */);
369   *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */);
370   sprintf (*kernel, "%s/kernel", cachedir);
371   sprintf (*initrd, "%s/initrd", cachedir);
372   sprintf (*appliance, "%s/root", cachedir);
373
374   return 0;
375 }
376
377 /* Run febootstrap-supermin-helper and tell it to generate the
378  * appliance.
379  */
380 static int
381 run_supermin_helper (guestfs_h *g, const char *supermin_path,
382                      const char *cachedir, size_t cdlen)
383 {
384   size_t pathlen = strlen (supermin_path);
385
386   const char *argv[30];
387   size_t i = 0;
388
389   char uid[32];
390   snprintf (uid, sizeof uid, "%i", geteuid ());
391   char gid[32];
392   snprintf (gid, sizeof gid, "%i", getegid ());
393   char supermin_d[pathlen + 32];
394   snprintf (supermin_d, pathlen + 32, "%s/supermin.d", supermin_path);
395   char kernel[cdlen + 32];
396   snprintf (kernel, cdlen + 32, "%s/kernel", cachedir);
397   char initrd[cdlen + 32];
398   snprintf (initrd, cdlen + 32, "%s/initrd", cachedir);
399   char root[cdlen + 32];
400   snprintf (root, cdlen + 32, "%s/root", cachedir);
401
402   int pass_u_g_args = getuid () != geteuid () || getgid () != getegid ();
403
404   argv[i++] = "febootstrap-supermin-helper";
405   if (g->verbose)
406     argv[i++] = "--verbose";
407   if (pass_u_g_args) {
408     argv[i++] = "-u";
409     argv[i++] = uid;
410     argv[i++] = "-g";
411     argv[i++] = gid;
412   }
413   argv[i++] = "-f";
414   argv[i++] = "ext2";
415   argv[i++] = supermin_d;
416   argv[i++] = host_cpu;
417   argv[i++] = kernel;
418   argv[i++] = initrd;
419   argv[i++] = root;
420   argv[i++] = NULL;
421
422   pid_t pid = fork ();
423   if (pid == -1) {
424     perrorf (g, "fork");
425     return -1;
426   }
427
428   if (pid > 0) {                /* Parent. */
429     if (g->verbose)
430       guestfs___print_timestamped_argv (g, argv);
431
432     int status;
433     if (waitpid (pid, &status, 0) == -1) {
434       perrorf (g, "waitpid");
435       return -1;
436     }
437     if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
438       error (g, _("external command failed, see earlier error messages"));
439       return -1;
440     }
441     return 0;
442   }
443
444   /* Child. */
445
446   /* Set a sensible umask in the subprocess, so kernel and initrd
447    * output files are world-readable (RHBZ#610880).
448    */
449   umask (0022);
450
451   execvp ("febootstrap-supermin-helper", (char * const *) argv);
452   perror ("execvp");
453   _exit (EXIT_FAILURE);
454 }
455
456 /* Search elements of g->path, returning the first path element which
457  * matches the predicate function 'pred'.
458  *
459  * Function 'pred' must return a true or false value.  If it returns
460  * -1 then the entire search is aborted.
461  *
462  * Return values:
463  * 1 = a path element matched, it is returned in *pelem_ret and must be
464  *     freed by the caller,
465  * 0 = no path element matched, *pelem_ret is set to NULL, or
466  * -1 = error which aborts the launch process
467  */
468 static int
469 find_path (guestfs_h *g,
470            int (*pred) (guestfs_h *g, const char *pelem, void *data),
471            void *data,
472            char **pelem_ret)
473 {
474   size_t len;
475   int r;
476   const char *pelem = g->path;
477
478   /* Note that if g->path is an empty string, we want to check the
479    * current directory (for backwards compatibility with
480    * libguestfs < 1.5.4).
481    */
482   do {
483     len = strcspn (pelem, ":");
484
485     /* Empty element or "." means current directory. */
486     if (len == 0)
487       *pelem_ret = safe_strdup (g, ".");
488     else
489       *pelem_ret = safe_strndup (g, pelem, len);
490
491     r = pred (g, *pelem_ret, data);
492     if (r == -1) {
493       free (*pelem_ret);
494       return -1;
495     }
496
497     if (r != 0)                 /* predicate matched */
498       return 1;
499
500     free (*pelem_ret);
501
502     if (pelem[len] == ':')
503       pelem += len + 1;
504     else
505       pelem += len;
506   } while (*pelem);
507
508   /* Predicate didn't match on any path element. */
509   *pelem_ret = NULL;
510   return 0;
511 }
512
513 /* Returns true iff file is contained in dir. */
514 static int
515 dir_contains_file (const char *dir, const char *file)
516 {
517   size_t dirlen = strlen (dir);
518   size_t filelen = strlen (file);
519   size_t len = dirlen + filelen + 2;
520   char path[len];
521
522   snprintf (path, len, "%s/%s", dir, file);
523   return access (path, F_OK) == 0;
524 }
525
526 /* Returns true iff every listed file is contained in 'dir'. */
527 static int
528 dir_contains_files (const char *dir, ...)
529 {
530   va_list args;
531   const char *file;
532
533   va_start (args, dir);
534   while ((file = va_arg (args, const char *)) != NULL) {
535     if (!dir_contains_file (dir, file)) {
536       va_end (args);
537       return 0;
538     }
539   }
540   va_end (args);
541   return 1;
542 }