1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright 2015 Joyent, Inc.
  24  */
  25 
  26 
  27 /*
  28  * This module contains functions used for reading and writing the index file.
  29  * setzoneent() opens the file.  getzoneent() parses the file, doing the usual
  30  * skipping of comment lines, etc., and using gettok() to deal with the ":"
  31  * delimiters.  endzoneent() closes the file.  putzoneent() updates the file,
  32  * adding, deleting or modifying lines, locking and unlocking appropriately.
  33  */
  34 
  35 #include <stdlib.h>
  36 #include <string.h>
  37 #include <errno.h>
  38 #include <libzonecfg.h>
  39 #include <unistd.h>
  40 #include <fcntl.h>
  41 #include <sys/stat.h>
  42 #include <assert.h>
  43 #include <uuid/uuid.h>
  44 #include "zonecfg_impl.h"
  45 
  46 
  47 #define _PATH_TMPFILE   ZONE_CONFIG_ROOT "/zonecfg.XXXXXX"
  48 
  49 /*
  50  * gettok() is a helper function for parsing the index file, used to split
  51  * the lines by their ":" delimiters.  Note that an entry may contain a ":"
  52  * inside double quotes; this should only affect the zonepath, as zone names
  53  * do not allow such characters, and zone states do not have them either.
  54  * Same with double-quotes themselves: they are not allowed in zone names,
  55  * and do not occur in zone states, and in theory should never occur in a
  56  * zonepath since zonecfg does not support a method for escaping them.
  57  *
  58  * It never returns NULL.
  59  */
  60 
  61 static char *
  62 gettok(char **cpp)
  63 {
  64         char *cp = *cpp, *retv;
  65         boolean_t quoted = B_FALSE;
  66 
  67         if (cp == NULL)
  68                 return ("");
  69         if (*cp == '"') {
  70                 quoted = B_TRUE;
  71                 cp++;
  72         }
  73         retv = cp;
  74         if (quoted) {
  75                 while (*cp != '\0' && *cp != '"')
  76                         cp++;
  77                 if (*cp == '"')
  78                         *cp++ = '\0';
  79         }
  80         while (*cp != '\0' && *cp != ':')
  81                 cp++;
  82         if (*cp == '\0') {
  83                 *cpp = NULL;
  84         } else {
  85                 *cp++ = '\0';
  86                 *cpp = cp;
  87         }
  88         return (retv);
  89 }
  90 
  91 char *
  92 getzoneent(FILE *cookie)
  93 {
  94         struct zoneent *ze;
  95         char *name;
  96 
  97         if ((ze = getzoneent_private(cookie)) == NULL)
  98                 return (NULL);
  99         name = strdup(ze->zone_name);
 100         free(ze);
 101         return (name);
 102 }
 103 
 104 struct zoneent *
 105 getzoneent_private(FILE *cookie)
 106 {
 107         char *cp, buf[MAX_INDEX_LEN], *p;
 108         struct zoneent *ze;
 109 
 110         if (cookie == NULL)
 111                 return (NULL);
 112 
 113         if ((ze = malloc(sizeof (struct zoneent))) == NULL)
 114                 return (NULL);
 115 
 116         for (;;) {
 117                 if (fgets(buf, sizeof (buf), cookie) == NULL) {
 118                         free(ze);
 119                         return (NULL);
 120                 }
 121                 if ((cp = strpbrk(buf, "\r\n")) == NULL) {
 122                         /* this represents a line that's too long */
 123                         continue;
 124                 }
 125                 *cp = '\0';
 126                 cp = buf;
 127                 if (*cp == '#') {
 128                         /* skip comment lines */
 129                         continue;
 130                 }
 131 
 132                 /* zonename */
 133                 p = gettok(&cp);
 134                 if (*p == '\0' || strlen(p) >= ZONENAME_MAX) {
 135                         /*
 136                          * empty or very long zone names are not allowed
 137                          */
 138                         continue;
 139                 }
 140                 (void) strlcpy(ze->zone_name, p, ZONENAME_MAX);
 141 
 142                 /* state */
 143                 p = gettok(&cp);
 144                 if (*p == '\0') {
 145                         /* state field should not be empty */
 146                         continue;
 147                 }
 148                 errno = 0;
 149                 if (strcmp(p, ZONE_STATE_STR_CONFIGURED) == 0) {
 150                         ze->zone_state = ZONE_STATE_CONFIGURED;
 151                 } else if (strcmp(p, ZONE_STATE_STR_INCOMPLETE) == 0) {
 152                         ze->zone_state = ZONE_STATE_INCOMPLETE;
 153                 } else if (strcmp(p, ZONE_STATE_STR_INSTALLED) == 0) {
 154                         ze->zone_state = ZONE_STATE_INSTALLED;
 155                 } else {
 156                         continue;
 157                 }
 158 
 159                 /* zonepath */
 160                 p = gettok(&cp);
 161                 if (strlen(p) >= MAXPATHLEN) {
 162                         /* very long paths are not allowed */
 163                         continue;
 164                 }
 165                 (void) strlcpy(ze->zone_path, p, MAXPATHLEN);
 166 
 167                 /* uuid */
 168                 p = gettok(&cp);
 169                 if (uuid_parse(p, ze->zone_uuid) == -1)
 170                         uuid_clear(ze->zone_uuid);
 171 
 172                 /* brand [optional] */
 173                 p = gettok(&cp);
 174                 if (strlen(p) >= MAXNAMELEN) {
 175                         /* very long names are not allowed */
 176                         continue;
 177                 }
 178                 (void) strlcpy(ze->zone_brand, p, MAXNAMELEN);
 179 
 180                 /* IP type [optional] */
 181                 p = gettok(&cp);
 182                 if (strlen(p) >= MAXNAMELEN) {
 183                         /* very long names are not allowed */
 184                         continue;
 185                 }
 186                 ze->zone_iptype = ZS_SHARED;
 187                 if (*p == 'e') {
 188                         ze->zone_iptype = ZS_EXCLUSIVE;
 189                 }
 190 
 191                 /* debug ID [optional] */
 192                 p = gettok(&cp);
 193                 if (*p != '\0')
 194                         ze->zone_did = atoi(p);
 195 
 196                 break;
 197         }
 198 
 199         return (ze);
 200 }
 201 
 202 static boolean_t
 203 path_common(char *path, size_t path_size, const char *stem)
 204 {
 205         const char *native_root = zone_get_nroot();
 206 
 207         if (native_root == NULL || zonecfg_in_alt_root()) {
 208                 /*
 209                  * Do not prepend the native system root (e.g. "/native") if an
 210                  * alternative configuration root has been selected.
 211                  */
 212                 native_root = "";
 213         }
 214 
 215         return (snprintf(path, path_size, "%s%s%s", native_root, zonecfg_root,
 216             stem) < path_size);
 217 }
 218 
 219 static boolean_t
 220 get_index_path(char *path, size_t path_size)
 221 {
 222         return (path_common(path, path_size, ZONE_INDEX_FILE));
 223 }
 224 
 225 static boolean_t
 226 get_temp_path(char *path, size_t path_size)
 227 {
 228         return (path_common(path, path_size, _PATH_TMPFILE));
 229 }
 230 
 231 FILE *
 232 setzoneent(void)
 233 {
 234         char path[MAXPATHLEN];
 235 
 236         if (!get_index_path(path, sizeof (path))) {
 237                 errno = EINVAL;
 238                 return (NULL);
 239         }
 240         return (fopen(path, "r"));
 241 }
 242 
 243 void
 244 endzoneent(FILE *cookie)
 245 {
 246         if (cookie != NULL)
 247                 (void) fclose(cookie);
 248 }
 249 
 250 static int
 251 lock_index_file(void)
 252 {
 253         int lock_fd;
 254         struct flock lock;
 255         char path[MAXPATHLEN];
 256 
 257         if (!path_common(path, sizeof (path), ZONE_INDEX_LOCK_DIR))
 258                 return (-1);
 259         if ((mkdir(path, S_IRWXU) == -1) && errno != EEXIST)
 260                 return (-1);
 261         if (strlcat(path, ZONE_INDEX_LOCK_FILE, sizeof (path)) >=
 262             sizeof (path))
 263                 return (-1);
 264         lock_fd = open(path, O_CREAT|O_RDWR, 0644);
 265         if (lock_fd == -1)
 266                 return (-1);
 267 
 268         lock.l_type = F_WRLCK;
 269         lock.l_whence = SEEK_SET;
 270         lock.l_start = 0;
 271         lock.l_len = 0;
 272 
 273         if (fcntl(lock_fd, F_SETLKW, &lock) == -1) {
 274                 (void) close(lock_fd);
 275                 return (-1);
 276         }
 277 
 278         return (lock_fd);
 279 }
 280 
 281 static int
 282 unlock_index_file(int lock_fd)
 283 {
 284         struct flock lock;
 285 
 286         lock.l_type = F_UNLCK;
 287         lock.l_whence = SEEK_SET;
 288         lock.l_start = 0;
 289         lock.l_len = 0;
 290 
 291         if (fcntl(lock_fd, F_SETLK, &lock) == -1)
 292                 return (Z_UNLOCKING_FILE);
 293 
 294         if (close(lock_fd) == -1)
 295                 return (Z_UNLOCKING_FILE);
 296 
 297         return (Z_OK);
 298 }
 299 
 300 /*
 301  * This function adds or removes a zone name et al. to the index file.
 302  *
 303  * If ze->zone_state is < 0, it means leave the
 304  * existing value unchanged; this is only meaningful when operation ==
 305  * PZE_MODIFY (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
 306  *
 307  * A zero-length ze->zone_path means leave the existing value
 308  * unchanged; this is only meaningful when operation == PZE_MODIFY
 309  * (i.e., it's bad on PZE_ADD and a no-op on PZE_REMOVE).
 310  *
 311  * A zero-length ze->zone_newname means leave the existing name
 312  * unchanged; otherwise the zone is renamed to zone_newname.  This is
 313  * only meaningful when operation == PZE_MODIFY.
 314  *
 315  * Locking and unlocking is done via the functions above.
 316  * The file itself is not modified in place; rather, a copy is made which
 317  * is modified, then the copy is atomically renamed back to the main file.
 318  */
 319 int
 320 putzoneent(struct zoneent *ze, zoneent_op_t operation)
 321 {
 322         FILE *index_file, *tmp_file;
 323         char buf[MAX_INDEX_LEN];
 324         int tmp_file_desc, lock_fd, err;
 325         boolean_t exist, need_quotes;
 326         char *cp, *tmpp;
 327         char tmp_path[MAXPATHLEN];
 328         char path[MAXPATHLEN];
 329         char uuidstr[UUID_PRINTABLE_STRING_LENGTH];
 330         size_t namelen;
 331         const char *zone_name, *zone_state, *zone_path, *zone_uuid,
 332             *zone_brand = "", *zone_iptype;
 333         zoneid_t zone_did;
 334 
 335         assert(ze != NULL);
 336 
 337         /*
 338          * Don't allow modification of Global Zone entry
 339          * in index file
 340          */
 341         if ((operation == PZE_MODIFY) &&
 342             (strcmp(ze->zone_name, GLOBAL_ZONENAME) == 0)) {
 343                 return (Z_OK);
 344         }
 345 
 346         if (operation == PZE_ADD &&
 347             (ze->zone_state < 0 || strlen(ze->zone_path) == 0))
 348                 return (Z_INVAL);
 349 
 350         if (operation != PZE_MODIFY && strlen(ze->zone_newname) != 0)
 351                 return (Z_INVAL);
 352 
 353         if ((lock_fd = lock_index_file()) == -1)
 354                 return (Z_LOCKING_FILE);
 355 
 356         if (!get_temp_path(tmp_path, sizeof (tmp_path))) {
 357                 (void) unlock_index_file(lock_fd);
 358                 return (Z_NOMEM);
 359         }
 360 
 361         tmp_file_desc = mkstemp(tmp_path);
 362         if (tmp_file_desc == -1) {
 363                 (void) unlink(tmp_path);
 364                 (void) unlock_index_file(lock_fd);
 365                 return (Z_TEMP_FILE);
 366         }
 367         (void) fchmod(tmp_file_desc, ZONE_INDEX_MODE);
 368         (void) fchown(tmp_file_desc, ZONE_INDEX_UID, ZONE_INDEX_GID);
 369         if ((tmp_file = fdopen(tmp_file_desc, "w")) == NULL) {
 370                 (void) close(tmp_file_desc);
 371                 err = Z_MISC_FS;
 372                 goto error;
 373         }
 374         if (!get_index_path(path, sizeof (path))) {
 375                 err = Z_MISC_FS;
 376                 goto error;
 377         }
 378         if ((index_file = fopen(path, "r")) == NULL) {
 379                 err = Z_MISC_FS;
 380                 goto error;
 381         }
 382 
 383         exist = B_FALSE;
 384         zone_name = ze->zone_name;
 385         namelen = strlen(zone_name);
 386         zone_brand = ze->zone_brand;
 387         zone_iptype = (ze->zone_iptype == ZS_SHARED ? "sh" : "ex");
 388         zone_did = ze->zone_did;
 389         for (;;) {
 390                 if (fgets(buf, sizeof (buf), index_file) == NULL) {
 391                         if (operation == PZE_ADD && !exist) {
 392                                 zone_state = zone_state_str(ze->zone_state);
 393                                 zone_path = ze->zone_path;
 394                                 zone_uuid = "";
 395                                 goto add_entry;
 396                         }
 397                         /*
 398                          * It's not considered an error to delete something
 399                          * that doesn't exist, but we can't modify a missing
 400                          * record.
 401                          */
 402                         if (operation == PZE_MODIFY && !exist) {
 403                                 err = Z_UPDATING_INDEX;
 404                                 goto error;
 405                         }
 406                         break;
 407                 }
 408 
 409                 if (buf[0] == '#') {
 410                         /* skip and preserve comment lines */
 411                         (void) fputs(buf, tmp_file);
 412                         continue;
 413                 }
 414 
 415                 if (strncmp(buf, zone_name, namelen) != 0 ||
 416                     buf[namelen] != ':') {
 417                         /* skip and preserve non-target lines */
 418                         (void) fputs(buf, tmp_file);
 419                         continue;
 420                 }
 421 
 422                 if ((cp = strpbrk(buf, "\r\n")) == NULL) {
 423                         /* this represents a line that's too long; delete */
 424                         continue;
 425                 }
 426                 *cp = '\0';
 427 
 428                 /*
 429                  * Skip over the zone name.  Because we've already matched the
 430                  * target zone (above), we know for certain here that the zone
 431                  * name is present and correctly formed.  No need to check.
 432                  */
 433                 cp = strchr(buf, ':') + 1;
 434 
 435                 zone_state = gettok(&cp);
 436                 if (*zone_state == '\0') {
 437                         /* state field should not be empty */
 438                         err = Z_UPDATING_INDEX;
 439                         goto error;
 440                 }
 441                 zone_path = gettok(&cp);
 442                 zone_uuid = gettok(&cp);
 443                 zone_brand = gettok(&cp);
 444                 zone_iptype = gettok(&cp);
 445                 tmpp = gettok(&cp);
 446                 if (*tmpp != '\0')
 447                         zone_did = atoi(tmpp);
 448 
 449                 switch (operation) {
 450                 case PZE_ADD:
 451                         /* can't add same zone */
 452                         err = Z_UPDATING_INDEX;
 453                         goto error;
 454 
 455                 case PZE_MODIFY:
 456                         /*
 457                          * If the caller specified a new state for the zone,
 458                          * then use that.  Otherwise, use the current state.
 459                          */
 460                         if (ze->zone_state >= 0) {
 461                                 zone_state = zone_state_str(ze->zone_state);
 462                         }
 463 
 464                         /* If a new name is supplied, use it. */
 465                         if (ze->zone_newname[0] != '\0')
 466                                 zone_name = ze->zone_newname;
 467 
 468                         if (ze->zone_path[0] != '\0')
 469                                 zone_path = ze->zone_path;
 470 
 471                         /* If new UUID provided, replace it */
 472                         if (!uuid_is_null(ze->zone_uuid)) {
 473                                 uuid_unparse(ze->zone_uuid, uuidstr);
 474                                 zone_uuid = uuidstr;
 475                         }
 476 
 477                         /* If a brand is supplied, use it. */
 478                         if (ze->zone_brand[0] != '\0') {
 479                                 zone_brand = ze->zone_brand;
 480 
 481                                 /*
 482                                  * Since the brand, iptype and did are optional,
 483                                  * we we only reset the iptype and did if the
 484                                  * brand is provided.
 485                                  */
 486                                 zone_iptype = (ze->zone_iptype == ZS_SHARED ?
 487                                     "sh" : "ex");
 488                                 zone_did = ze->zone_did;
 489                         }
 490 
 491                         break;
 492 
 493                 case PZE_REMOVE:
 494                 default:
 495                         continue;
 496                 }
 497 
 498         add_entry:
 499                 /*
 500                  * If the entry in the file is in greater than configured
 501                  * state, then we must have a UUID.  Make sure that we do.
 502                  * (Note that the file entry is only tokenized, not fully
 503                  * parsed, so we need to do a string comparison here.)
 504                  */
 505                 if (strcmp(zone_state, ZONE_STATE_STR_CONFIGURED) != 0 &&
 506                     *zone_uuid == '\0') {
 507                         if (uuid_is_null(ze->zone_uuid))
 508                                 uuid_generate(ze->zone_uuid);
 509                         uuid_unparse(ze->zone_uuid, uuidstr);
 510                         zone_uuid = uuidstr;
 511                 }
 512                 /*
 513                  * We need to quote a path that contains a ":"; this should
 514                  * only affect the zonepath, as zone names do not allow such
 515                  * characters, and zone states do not have them either.  Same
 516                  * with double-quotes themselves: they are not allowed in zone
 517                  * names, and do not occur in zone states, and in theory should
 518                  * never occur in a zonepath since zonecfg does not support a
 519                  * method for escaping them.
 520                  */
 521                 need_quotes = (strchr(zone_path, ':') != NULL);
 522 
 523                 if (*zone_brand != '\0') {
 524                         (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s:%s:%s:%d\n",
 525                             zone_name, zone_state, need_quotes ? "\"" : "",
 526                             zone_path, need_quotes ? "\"" : "", zone_uuid,
 527                             zone_brand, zone_iptype, zone_did);
 528                 } else {
 529                         (void) fprintf(tmp_file, "%s:%s:%s%s%s:%s\n", zone_name,
 530                             zone_state, need_quotes ? "\"" : "", zone_path,
 531                             need_quotes ? "\"" : "", zone_uuid);
 532                 }
 533                 exist = B_TRUE;
 534         }
 535 
 536         (void) fclose(index_file);
 537         index_file = NULL;
 538         if (fclose(tmp_file) != 0) {
 539                 tmp_file = NULL;
 540                 err = Z_MISC_FS;
 541                 goto error;
 542         }
 543         tmp_file = NULL;
 544         if (rename(tmp_path, path) == -1) {
 545                 err = errno == EACCES ? Z_ACCES : Z_MISC_FS;
 546                 goto error;
 547         }
 548         if (unlock_index_file(lock_fd) != Z_OK)
 549                 return (Z_UNLOCKING_FILE);
 550         return (Z_OK);
 551 
 552 error:
 553         if (index_file != NULL)
 554                 (void) fclose(index_file);
 555         if (tmp_file != NULL)
 556                 (void) fclose(tmp_file);
 557         (void) unlink(tmp_path);
 558         (void) unlock_index_file(lock_fd);
 559         return (err);
 560 }