1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 22 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/cdefs.h> 29 __FBSDID("$FreeBSD$"); 30 31 #include <sys/param.h> 32 #include <sys/jail.h> 33 #include <sys/mount.h> 34 #include <sys/wait.h> 35 #include <err.h> 36 #include <jail.h> 37 #include <stdbool.h> 38 #include <stdio.h> 39 #include <string.h> 40 #include <unistd.h> 41 42 #include <be.h> 43 44 #include "bectl.h" 45 46 static void jailparam_grow(void); 47 static void jailparam_add(const char *name, const char *val); 48 static int jailparam_del(const char *name); 49 static bool jailparam_addarg(char *arg); 50 static int jailparam_delarg(char *arg); 51 52 static int bectl_search_jail_paths(const char *mnt); 53 static int bectl_locate_jail(const char *ident); 54 55 /* We'll start with 8 parameters initially and grow as needed. */ 56 #define INIT_PARAMCOUNT 8 57 58 static struct jailparam *jp; 59 static int jpcnt; 60 static int jpused; 61 static char mnt_loc[BE_MAXPATHLEN]; 62 63 static void 64 jailparam_grow(void) 65 { 66 67 jpcnt *= 2; 68 jp = realloc(jp, jpcnt * sizeof(*jp)); 69 if (jp == NULL) 70 err(2, "realloc"); 71 } 72 73 static void 74 jailparam_add(const char *name, const char *val) 75 { 76 int i; 77 78 for (i = 0; i < jpused; ++i) { 79 if (strcmp(name, jp[i].jp_name) == 0) 80 break; 81 } 82 83 if (i < jpused) 84 jailparam_free(&jp[i], 1); 85 else if (jpused == jpcnt) 86 /* The next slot isn't allocated yet */ 87 jailparam_grow(); 88 89 if (jailparam_init(&jp[i], name) != 0) 90 return; 91 if (jailparam_import(&jp[i], val) != 0) 92 return; 93 ++jpused; 94 } 95 96 static int 97 jailparam_del(const char *name) 98 { 99 int i; 100 char *val; 101 102 for (i = 0; i < jpused; ++i) { 103 if (strcmp(name, jp[i].jp_name) == 0) 104 break; 105 } 106 107 if (i == jpused) 108 return (ENOENT); 109 110 for (; i < jpused - 1; ++i) { 111 val = jailparam_export(&jp[i + 1]); 112 113 jailparam_free(&jp[i], 1); 114 /* 115 * Given the context, the following will really only fail if 116 * they can't allocate the copy of the name or value. 117 */ 118 if (jailparam_init(&jp[i], jp[i + 1].jp_name) != 0) { 119 free(val); 120 return (ENOMEM); 121 } 122 if (jailparam_import(&jp[i], val) != 0) { 123 jailparam_free(&jp[i], 1); 124 free(val); 125 return (ENOMEM); 126 } 127 free(val); 128 } 129 130 jailparam_free(&jp[i], 1); 131 --jpused; 132 return (0); 133 } 134 135 static bool 136 jailparam_addarg(char *arg) 137 { 138 char *name, *val; 139 140 if (arg == NULL) 141 return (false); 142 name = arg; 143 if ((val = strchr(arg, '=')) == NULL) { 144 fprintf(stderr, "bectl jail: malformed jail option '%s'\n", 145 arg); 146 return (false); 147 } 148 149 *val++ = '\0'; 150 if (strcmp(name, "path") == 0) { 151 if (strlen(val) >= BE_MAXPATHLEN) { 152 fprintf(stderr, 153 "bectl jail: skipping too long path assignment '%s' (max length = %d)\n", 154 val, BE_MAXPATHLEN); 155 return (false); 156 } 157 strlcpy(mnt_loc, val, sizeof(mnt_loc)); 158 } 159 jailparam_add(name, val); 160 return (true); 161 } 162 163 static int 164 jailparam_delarg(char *arg) 165 { 166 char *name, *val; 167 168 if (arg == NULL) 169 return (EINVAL); 170 name = arg; 171 if ((val = strchr(name, '=')) != NULL) 172 *val++ = '\0'; 173 174 if (strcmp(name, "path") == 0) 175 *mnt_loc = '\0'; 176 return (jailparam_del(name)); 177 } 178 179 int 180 bectl_cmd_jail(int argc, char *argv[]) 181 { 182 char *bootenv, *mountpoint; 183 int jid, opt, ret; 184 bool default_hostname, interactive, unjail; 185 pid_t pid; 186 187 default_hostname = interactive = unjail = true; 188 jpcnt = INIT_PARAMCOUNT; 189 jp = malloc(jpcnt * sizeof(*jp)); 190 if (jp == NULL) 191 err(2, "malloc"); 192 193 jailparam_add("persist", "true"); 194 jailparam_add("allow.mount", "true"); 195 jailparam_add("allow.mount.devfs", "true"); 196 jailparam_add("enforce_statfs", "1"); 197 198 while ((opt = getopt(argc, argv, "bo:Uu:")) != -1) { 199 switch (opt) { 200 case 'b': 201 interactive = false; 202 break; 203 case 'o': 204 if (jailparam_addarg(optarg)) { 205 /* 206 * optarg has been modified to null terminate 207 * at the assignment operator. 208 */ 209 if (strcmp(optarg, "host.hostname") == 0) 210 default_hostname = false; 211 } 212 break; 213 case 'U': 214 unjail = false; 215 break; 216 case 'u': 217 if ((ret = jailparam_delarg(optarg)) == 0) { 218 if (strcmp(optarg, "host.hostname") == 0) 219 default_hostname = true; 220 } else if (ret != ENOENT) { 221 fprintf(stderr, 222 "bectl jail: error unsetting \"%s\"\n", 223 optarg); 224 return (ret); 225 } 226 break; 227 default: 228 fprintf(stderr, "bectl jail: unknown option '-%c'\n", 229 optopt); 230 return (usage(false)); 231 } 232 } 233 234 argc -= optind; 235 argv += optind; 236 237 /* struct jail be_jail = { 0 }; */ 238 if (argc < 1) { 239 fprintf(stderr, "bectl jail: missing boot environment name\n"); 240 return (usage(false)); 241 } 242 243 bootenv = argv[0]; 244 245 /* 246 * XXX TODO: if its already mounted, perhaps there should be a flag to 247 * indicate its okay to proceed?? 248 */ 249 if (*mnt_loc == '\0') 250 mountpoint = NULL; 251 else 252 mountpoint = mnt_loc; 253 if (be_mount(be, bootenv, mountpoint, 0, mnt_loc) != BE_ERR_SUCCESS) { 254 fprintf(stderr, "could not mount bootenv\n"); 255 return (1); 256 } 257 258 if (default_hostname) 259 jailparam_add("host.hostname", bootenv); 260 261 /* 262 * This is our indicator that path was not set by the user, so we'll use 263 * the path that libbe generated for us. 264 */ 265 if (mountpoint == NULL) 266 jailparam_add("path", mnt_loc); 267 /* Create the jail for now, attach later as-needed */ 268 jid = jailparam_set(jp, jpused, JAIL_CREATE); 269 if (jid == -1) { 270 fprintf(stderr, "unable to create jail. error: %d\n", errno); 271 return (1); 272 } 273 274 jailparam_free(jp, jpused); 275 free(jp); 276 277 /* We're not interactive, nothing more to do here. */ 278 if (!interactive) 279 return (0); 280 281 pid = fork(); 282 switch(pid) { 283 case -1: 284 perror("fork"); 285 return (1); 286 case 0: 287 jail_attach(jid); 288 /* We're attached within the jail... good bye! */ 289 chdir("/"); 290 if (argc > 1) 291 execve(argv[1], &argv[1], NULL); 292 else 293 execl("/bin/sh", "/bin/sh", NULL); 294 fprintf(stderr, "bectl jail: failed to execute %s\n", 295 (argc > 1 ? argv[1] : "/bin/sh")); 296 _exit(1); 297 default: 298 /* Wait for the child to get back, see if we need to unjail */ 299 waitpid(pid, NULL, 0); 300 } 301 302 if (unjail) { 303 jail_remove(jid); 304 unmount(mnt_loc, 0); 305 } 306 307 return (0); 308 } 309 310 static int 311 bectl_search_jail_paths(const char *mnt) 312 { 313 int jid; 314 char lastjid[16]; 315 char jailpath[MAXPATHLEN]; 316 317 /* jail_getv expects name/value strings */ 318 snprintf(lastjid, sizeof(lastjid), "%d", 0); 319 320 jid = 0; 321 while ((jid = jail_getv(0, "lastjid", lastjid, "path", &jailpath, 322 NULL)) != -1) { 323 324 /* the jail we've been looking for */ 325 if (strcmp(jailpath, mnt) == 0) 326 return (jid); 327 328 /* update lastjid and keep on looking */ 329 snprintf(lastjid, sizeof(lastjid), "%d", jid); 330 } 331 332 return (-1); 333 } 334 335 /* 336 * Locate a jail based on an arbitrary identifier. This may be either a name, 337 * a jid, or a BE name. Returns the jid or -1 on failure. 338 */ 339 static int 340 bectl_locate_jail(const char *ident) 341 { 342 nvlist_t *belist, *props; 343 char *mnt; 344 int jid; 345 346 /* Try the easy-match first */ 347 jid = jail_getid(ident); 348 if (jid != -1) 349 return (jid); 350 351 /* Attempt to try it as a BE name, first */ 352 if (be_prop_list_alloc(&belist) != 0) 353 return (-1); 354 355 if (be_get_bootenv_props(be, belist) != 0) 356 return (-1); 357 358 if (nvlist_lookup_nvlist(belist, ident, &props) == 0) { 359 360 /* path where a boot environment is mounted */ 361 if (nvlist_lookup_string(props, "mounted", &mnt) == 0) { 362 363 /* looking for a jail that matches our bootenv path */ 364 jid = bectl_search_jail_paths(mnt); 365 be_prop_list_free(belist); 366 return (jid); 367 } 368 369 be_prop_list_free(belist); 370 } 371 372 return (-1); 373 } 374 375 int 376 bectl_cmd_unjail(int argc, char *argv[]) 377 { 378 char path[MAXPATHLEN]; 379 char *cmd, *name, *target; 380 int jid; 381 382 /* Store alias used */ 383 cmd = argv[0]; 384 385 if (argc != 2) { 386 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 387 return (usage(false)); 388 } 389 390 target = argv[1]; 391 392 /* Locate the jail */ 393 if ((jid = bectl_locate_jail(target)) == -1) { 394 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, 395 target); 396 return (1); 397 } 398 399 bzero(&path, MAXPATHLEN); 400 name = jail_getname(jid); 401 if (jail_getv(0, "name", name, "path", path, NULL) != jid) { 402 free(name); 403 fprintf(stderr, 404 "bectl %s: failed to get path for jail requested by '%s'\n", 405 cmd, target); 406 return (1); 407 } 408 409 free(name); 410 411 if (be_mounted_at(be, path, NULL) != 0) { 412 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", 413 cmd, target); 414 return (1); 415 } 416 417 jail_remove(jid); 418 unmount(path, 0); 419 420 return (0); 421 } 422