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, default_name, interactive, unjail; 185 pid_t pid; 186 187 default_hostname = default_name = 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, "name") == 0) 210 default_name = false; 211 if (strcmp(optarg, "host.hostname") == 0) 212 default_hostname = false; 213 } 214 break; 215 case 'U': 216 unjail = false; 217 break; 218 case 'u': 219 if ((ret = jailparam_delarg(optarg)) == 0) { 220 if (strcmp(optarg, "name") == 0) 221 default_name = true; 222 if (strcmp(optarg, "host.hostname") == 0) 223 default_hostname = true; 224 } else if (ret != ENOENT) { 225 fprintf(stderr, 226 "bectl jail: error unsetting \"%s\"\n", 227 optarg); 228 return (ret); 229 } 230 break; 231 default: 232 fprintf(stderr, "bectl jail: unknown option '-%c'\n", 233 optopt); 234 return (usage(false)); 235 } 236 } 237 238 argc -= optind; 239 argv += optind; 240 241 /* struct jail be_jail = { 0 }; */ 242 if (argc < 1) { 243 fprintf(stderr, "bectl jail: missing boot environment name\n"); 244 return (usage(false)); 245 } 246 247 bootenv = argv[0]; 248 249 /* 250 * XXX TODO: if its already mounted, perhaps there should be a flag to 251 * indicate its okay to proceed?? 252 */ 253 if (*mnt_loc == '\0') 254 mountpoint = NULL; 255 else 256 mountpoint = mnt_loc; 257 if (be_mount(be, bootenv, mountpoint, 0, mnt_loc) != BE_ERR_SUCCESS) { 258 fprintf(stderr, "could not mount bootenv\n"); 259 return (1); 260 } 261 262 if (default_name) 263 jailparam_add("name", bootenv); 264 if (default_hostname) 265 jailparam_add("host.hostname", bootenv); 266 267 /* 268 * This is our indicator that path was not set by the user, so we'll use 269 * the path that libbe generated for us. 270 */ 271 if (mountpoint == NULL) 272 jailparam_add("path", mnt_loc); 273 /* Create the jail for now, attach later as-needed */ 274 jid = jailparam_set(jp, jpused, JAIL_CREATE); 275 if (jid == -1) { 276 fprintf(stderr, "unable to create jail. error: %d\n", errno); 277 return (1); 278 } 279 280 jailparam_free(jp, jpused); 281 free(jp); 282 283 /* We're not interactive, nothing more to do here. */ 284 if (!interactive) 285 return (0); 286 287 pid = fork(); 288 switch(pid) { 289 case -1: 290 perror("fork"); 291 return (1); 292 case 0: 293 jail_attach(jid); 294 /* We're attached within the jail... good bye! */ 295 chdir("/"); 296 if (argc > 1) 297 execve(argv[1], &argv[1], NULL); 298 else 299 execl("/bin/sh", "/bin/sh", NULL); 300 fprintf(stderr, "bectl jail: failed to execute %s\n", 301 (argc > 1 ? argv[1] : "/bin/sh")); 302 _exit(1); 303 default: 304 /* Wait for the child to get back, see if we need to unjail */ 305 waitpid(pid, NULL, 0); 306 } 307 308 if (unjail) { 309 jail_remove(jid); 310 unmount(mnt_loc, 0); 311 } 312 313 return (0); 314 } 315 316 static int 317 bectl_search_jail_paths(const char *mnt) 318 { 319 char jailpath[MAXPATHLEN]; 320 int jid; 321 322 jid = 0; 323 (void)mnt; 324 while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath, 325 NULL)) != -1) { 326 if (strcmp(jailpath, mnt) == 0) 327 return (jid); 328 } 329 330 return (-1); 331 } 332 333 /* 334 * Locate a jail based on an arbitrary identifier. This may be either a name, 335 * a jid, or a BE name. Returns the jid or -1 on failure. 336 */ 337 static int 338 bectl_locate_jail(const char *ident) 339 { 340 nvlist_t *belist, *props; 341 char *mnt; 342 int jid; 343 344 /* Try the easy-match first */ 345 jid = jail_getid(ident); 346 if (jid != -1) 347 return (jid); 348 349 /* Attempt to try it as a BE name, first */ 350 if (be_prop_list_alloc(&belist) != 0) 351 return (-1); 352 353 if (be_get_bootenv_props(be, belist) != 0) 354 return (-1); 355 356 if (nvlist_lookup_nvlist(belist, ident, &props) == 0) { 357 /* We'll attempt to resolve the jid by way of mountpoint */ 358 if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) { 359 jid = bectl_search_jail_paths(mnt); 360 be_prop_list_free(belist); 361 return (jid); 362 } 363 364 be_prop_list_free(belist); 365 } 366 367 return (-1); 368 } 369 370 int 371 bectl_cmd_unjail(int argc, char *argv[]) 372 { 373 char path[MAXPATHLEN]; 374 char *cmd, *name, *target; 375 int jid; 376 377 /* Store alias used */ 378 cmd = argv[0]; 379 380 if (argc != 2) { 381 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 382 return (usage(false)); 383 } 384 385 target = argv[1]; 386 387 /* Locate the jail */ 388 if ((jid = bectl_locate_jail(target)) == -1) { 389 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, 390 target); 391 return (1); 392 } 393 394 bzero(&path, MAXPATHLEN); 395 name = jail_getname(jid); 396 if (jail_getv(0, "name", name, "path", path, NULL) != jid) { 397 free(name); 398 fprintf(stderr, 399 "bectl %s: failed to get path for jail requested by '%s'\n", 400 cmd, target); 401 return (1); 402 } 403 404 free(name); 405 406 if (be_mounted_at(be, path, NULL) != 0) { 407 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", 408 cmd, target); 409 return (1); 410 } 411 412 jail_remove(jid); 413 unmount(path, 0); 414 415 return (0); 416 } 417