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, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #pragma ident   "%Z%%M% %I%     %E% SMI"
  28 
  29 /*
  30  * This file contains public API functions for managing the legacy dhcptab
  31  * container format.  For the semantics of these functions, please see the
  32  * Enterprise DHCP Architecture Document.
  33  */
  34 
  35 #include <alloca.h>
  36 #include <dhcp_svc_public.h>
  37 #include <errno.h>
  38 #include <fcntl.h>
  39 #include <netinet/in.h>
  40 #include <stdio.h>
  41 #include <stdlib.h>
  42 #include <string.h>
  43 #include <sys/socket.h>
  44 #include <sys/stat.h>
  45 #include <sys/types.h>
  46 #include <unistd.h>
  47 
  48 #include "dhcptab.h"
  49 #include "util.h"
  50 
  51 static void dt2path(char *, size_t, const char *, const char *);
  52 static int write_rec(int, dt_rec_t *, off_t);
  53 
  54 int
  55 open_dt(void **handlep, const char *location, uint_t flags)
  56 {
  57         dt_handle_t     *dhp;
  58         int             retval;
  59         int             fd;
  60         char            dtpath[MAXPATHLEN];
  61 
  62         dhp = malloc(sizeof (dt_handle_t));
  63         if (dhp == NULL)
  64                 return (DSVC_NO_MEMORY);
  65 
  66         dhp->dh_oflags = flags;
  67         (void) strlcpy(dhp->dh_location, location, MAXPATHLEN);
  68 
  69         /*
  70          * This is a legacy format which has no header, so we neither write
  71          * nor verify a header (we just create the file or make sure it
  72          * exists, depending on the value of `flags').
  73          */
  74         dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
  75         retval = open_file(dtpath, flags, &fd);
  76         if (retval != DSVC_SUCCESS) {
  77                 free(dhp);
  78                 return (retval);
  79         }
  80         (void) close(fd);
  81 
  82         *handlep = dhp;
  83         return (DSVC_SUCCESS);
  84 }
  85 
  86 int
  87 close_dt(void **handlep)
  88 {
  89         free(*handlep);
  90         return (DSVC_SUCCESS);
  91 }
  92 
  93 int
  94 remove_dt(const char *location)
  95 {
  96         char dtpath[MAXPATHLEN];
  97 
  98         dt2path(dtpath, MAXPATHLEN, location, "");
  99         if (unlink(dtpath) == -1)
 100                 return (syserr_to_dsvcerr(errno));
 101 
 102         return (DSVC_SUCCESS);
 103 }
 104 
 105 /*
 106  * Internal version of lookup_dt() used by both lookup_dt() and
 107  * update_dt(); same semantics as lookup_dt() except that the `partial'
 108  * argument has been generalized into a `flags' field and the handle has
 109  * been turned into a FILE pointer.
 110  */
 111 static int
 112 find_dt(FILE *fp, uint_t flags, uint_t query, int count,
 113     const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
 114 {
 115         int             retval = DSVC_SUCCESS;
 116         char            *buf = NULL, *fields[DTF_MAX_FIELDS];
 117         uint_t          nrecords;
 118         dt_rec_t        *recordp;
 119         dt_rec_list_t   *records, *new_records;
 120         unsigned int    nfields;
 121         off_t           recoff;
 122 
 123         if (fseek(fp, 0, SEEK_SET) == -1)
 124                 return (DSVC_INTERNAL);
 125 
 126         records = NULL;
 127         for (nrecords = 0; count < 0 || nrecords < count; ) {
 128                 free(buf);
 129 
 130                 if (flags & FIND_POSITION)
 131                         recoff = ftello(fp);
 132 
 133                 buf = read_entry(fp);
 134                 if (buf == NULL) {
 135                         if (!feof(fp))
 136                                 retval = DSVC_NO_MEMORY;
 137                         break;
 138                 }
 139 
 140                 /*
 141                  * Skip pure comment lines; for now this just skips the
 142                  * header information at the top of the container.
 143                  */
 144                 if (buf[0] == DTF_COMMENT_CHAR)
 145                         continue;
 146 
 147                 /*
 148                  * Parse out the entry into the dt_rec_t
 149                  */
 150                 nfields = field_split(buf, DTF_MAX_FIELDS, fields, " \t");
 151                 if (nfields < DTF_MAX_FIELDS)
 152                         continue;
 153 
 154                 /*
 155                  * See if we've got a match.  If so, allocate the new
 156                  * record, fill it in, and continue.
 157                  */
 158                 if (DSVC_QISEQ(query, DT_QTYPE) &&
 159                     targetp->dt_type != fields[DTF_TYPE][0])
 160                         continue;
 161                 else if (DSVC_QISNEQ(query, DT_QTYPE) &&
 162                     targetp->dt_type == fields[DTF_TYPE][0])
 163                         continue;
 164 
 165                 if (DSVC_QISEQ(query, DT_QKEY) &&
 166                     strcmp(targetp->dt_key, fields[DTF_KEY]) != 0)
 167                         continue;
 168                 else if (DSVC_QISNEQ(query, DT_QKEY) &&
 169                     strcmp(targetp->dt_key, fields[DTF_KEY]) == 0)
 170                         continue;
 171 
 172                 /*
 173                  * Caller just wants a count of the number of matching
 174                  * records, not the records themselves; continue.
 175                  */
 176                 if (recordsp == NULL) {
 177                         nrecords++;
 178                         continue;
 179                 }
 180 
 181                 /*
 182                  * Allocate record; if FIND_POSITION flag is set, then we
 183                  * need to allocate an extended (dt_recpos_t) record.
 184                  */
 185                 if (flags & FIND_POSITION)
 186                         recordp = malloc(sizeof (dt_recpos_t));
 187                 else
 188                         recordp = malloc(sizeof (dt_rec_t));
 189 
 190                 if (recordp == NULL) {
 191                         if ((flags & FIND_PARTIAL) == 0)
 192                                 retval = DSVC_NO_MEMORY;
 193                         break;
 194                 }
 195 
 196                 /*
 197                  * Fill in record; if FIND_POSITION flag is set, then pass
 198                  * back additional location information.
 199                  */
 200                 (void) strlcpy(recordp->dt_key, fields[DTF_KEY],
 201                     sizeof (recordp->dt_key));
 202                 recordp->dt_sig = 1;
 203                 recordp->dt_type = fields[DTF_TYPE][0];
 204                 recordp->dt_value = strdup(fields[DTF_VALUE]);
 205                 if (recordp->dt_value == NULL) {
 206                         free(recordp);
 207                         if ((flags & FIND_PARTIAL) == 0)
 208                                 retval = DSVC_NO_MEMORY;
 209                         break;
 210                 }
 211 
 212                 if (flags & FIND_POSITION) {
 213                         ((dt_recpos_t *)recordp)->dtp_off = recoff;
 214                         ((dt_recpos_t *)recordp)->dtp_size = ftello(fp) -
 215                             recoff;
 216                 }
 217 
 218                 /*
 219                  * Chuck the record on the list; up the counter.
 220                  */
 221                 new_records = add_dtrec_to_list(recordp, records);
 222                 if (new_records == NULL) {
 223                         free_dtrec(recordp);
 224                         if ((flags & FIND_PARTIAL) == 0)
 225                                 retval = DSVC_NO_MEMORY;
 226                         break;
 227                 }
 228                 records = new_records;
 229                 nrecords++;
 230         }
 231 
 232         free(buf);
 233 
 234         if (retval == DSVC_SUCCESS) {
 235                 *nrecordsp = nrecords;
 236                 if (recordsp != NULL)
 237                         *recordsp = records;
 238                 return (DSVC_SUCCESS);
 239         }
 240 
 241         if (records != NULL)
 242                 free_dtrec_list(records);
 243 
 244         return (retval);
 245 }
 246 
 247 int
 248 lookup_dt(void *handle, boolean_t partial, uint_t query, int count,
 249     const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
 250 {
 251         int             retval;
 252         char            dtpath[MAXPATHLEN];
 253         FILE            *fp;
 254         dt_handle_t     *dhp = (dt_handle_t *)handle;
 255 
 256         if ((dhp->dh_oflags & DSVC_READ) == 0)
 257                 return (DSVC_ACCESS);
 258 
 259         dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
 260         fp = fopen(dtpath, "r");
 261         if (fp == NULL)
 262                 return (syserr_to_dsvcerr(errno));
 263 
 264         retval = find_dt(fp, partial ? FIND_PARTIAL : 0, query, count, targetp,
 265             recordsp, nrecordsp);
 266 
 267         (void) fclose(fp);
 268         return (retval);
 269 }
 270 
 271 /*
 272  * Internal dhcptab record update routine, used to factor out the
 273  * common code between add_dt(), delete_dt(), and modify_dt().  If
 274  * `origp' is NULL, then act like add_dt(); if `newp' is NULL, then
 275  * act like delete_dt(); otherwise act like modify_dt().
 276  */
 277 static int
 278 update_dt(const dt_handle_t *dhp, const dt_rec_t *origp, dt_rec_t *newp)
 279 {
 280         char            dtpath[MAXPATHLEN], newpath[MAXPATHLEN];
 281         int             retval = DSVC_SUCCESS;
 282         off_t           recoff, recnext;
 283         dt_rec_list_t   *reclist;
 284         FILE            *fp;
 285         int             newfd;
 286         uint_t          found;
 287         int             query;
 288         struct stat     st;
 289 
 290         if ((dhp->dh_oflags & DSVC_WRITE) == 0)
 291                 return (DSVC_ACCESS);
 292 
 293         /*
 294          * Open the container to update and a new container file which we
 295          * will store the updated version of the container in.  When the
 296          * update is done, rename the new file to be the real container.
 297          */
 298         dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
 299         fp = fopen(dtpath, "r");
 300         if (fp == NULL)
 301                 return (syserr_to_dsvcerr(errno));
 302 
 303         dt2path(newpath, MAXPATHLEN, dhp->dh_location, ".new");
 304         (void) unlink(newpath);
 305         newfd = open(newpath, O_CREAT|O_EXCL|O_WRONLY, 0644);
 306         if (newfd == -1) {
 307                 (void) fclose(fp);
 308                 return (syserr_to_dsvcerr(errno));
 309         }
 310 
 311         DSVC_QINIT(query);
 312         DSVC_QEQ(query, DT_QKEY|DT_QTYPE);
 313 
 314         /*
 315          * If we're adding a new record or changing a key for an existing
 316          * record, bail if the record we want to add already exists.
 317          */
 318         if (newp != NULL) {
 319                 if (origp == NULL || origp->dt_type != newp->dt_type ||
 320                     strcmp(origp->dt_key, newp->dt_key) != 0) {
 321                         retval = find_dt(fp, 0, query, 1, newp, NULL, &found);
 322                         if (retval != DSVC_SUCCESS)
 323                                 goto out;
 324                         if (found != 0) {
 325                                 retval = DSVC_EXISTS;
 326                                 goto out;
 327                         }
 328                 }
 329         }
 330 
 331         /*
 332          * If we're deleting or modifying record, make sure the record
 333          * still exists.  Note that we don't check signatures because this
 334          * is a legacy format that has no signatures.
 335          */
 336         if (origp != NULL) {
 337                 retval = find_dt(fp, FIND_POSITION, query, 1, origp, &reclist,
 338                     &found);
 339                 if (retval != DSVC_SUCCESS)
 340                         goto out;
 341                 if (found == 0) {
 342                         retval = DSVC_NOENT;
 343                         goto out;
 344                 }
 345 
 346                 /*
 347                  * Note the offset of the record we're modifying or deleting
 348                  * for use down below.
 349                  */
 350                 recoff  = ((dt_recpos_t *)reclist->dtl_rec)->dtp_off;
 351                 recnext = recoff + ((dt_recpos_t *)reclist->dtl_rec)->dtp_size;
 352 
 353                 free_dtrec_list(reclist);
 354         } else {
 355                 /*
 356                  * No record to modify or delete, so set `recoff' and
 357                  * `recnext' appropriately.
 358                  */
 359                 recoff = 0;
 360                 recnext = 0;
 361         }
 362 
 363         /*
 364          * Make a new copy of the container.  If we're deleting or
 365          * modifying a record, don't copy that record to the new container.
 366          */
 367         if (fstat(fileno(fp), &st) == -1) {
 368                 retval = DSVC_INTERNAL;
 369                 goto out;
 370         }
 371 
 372         retval = copy_range(fileno(fp), 0, newfd, 0, recoff);
 373         if (retval != DSVC_SUCCESS)
 374                 goto out;
 375 
 376         retval = copy_range(fileno(fp), recnext, newfd, recoff,
 377             st.st_size - recnext);
 378         if (retval != DSVC_SUCCESS)
 379                 goto out;
 380 
 381         /*
 382          * If there's a new record, append it to the new container.
 383          */
 384         if (newp != NULL) {
 385                 retval = write_rec(newfd, newp, recoff + st.st_size - recnext);
 386                 if (retval != DSVC_SUCCESS)
 387                         goto out;
 388         }
 389 
 390         /*
 391          * Note: we close these descriptors before the rename(2) (rather
 392          * than just having the `out:' label clean them up) to save NFS
 393          * some work (otherwise, NFS has to save `dtpath' to an alternate
 394          * name since its vnode would still be active).
 395          */
 396         (void) fclose(fp);
 397         (void) close(newfd);
 398 
 399         if (rename(newpath, dtpath) == -1)
 400                 retval = syserr_to_dsvcerr(errno);
 401 
 402         return (retval);
 403 out:
 404         (void) fclose(fp);
 405         (void) close(newfd);
 406         (void) unlink(newpath);
 407         return (retval);
 408 }
 409 
 410 int
 411 delete_dt(void *handle, const dt_rec_t *delp)
 412 {
 413         return (update_dt((dt_handle_t *)handle, delp, NULL));
 414 }
 415 
 416 int
 417 add_dt(void *handle, dt_rec_t *addp)
 418 {
 419         return (update_dt((dt_handle_t *)handle, NULL, addp));
 420 }
 421 
 422 int
 423 modify_dt(void *handle, const dt_rec_t *origp, dt_rec_t *newp)
 424 {
 425         return (update_dt((dt_handle_t *)handle, origp, newp));
 426 }
 427 
 428 int
 429 list_dt(const char *location, char ***listppp, uint_t *countp)
 430 {
 431         char    dtpath[MAXPATHLEN];
 432         char    **listpp;
 433 
 434         if (access(location, F_OK|R_OK) == -1) {
 435                 switch (errno) {
 436                 case EACCES:
 437                 case EPERM:
 438                         return (DSVC_ACCESS);
 439                 case ENOENT:
 440                         return (DSVC_NO_LOCATION);
 441                 default:
 442                         break;
 443                 }
 444                 return (DSVC_INTERNAL);
 445         }
 446 
 447         dt2path(dtpath, MAXPATHLEN, location, "");
 448         if (access(dtpath, F_OK|R_OK) == -1) {
 449                 *countp = 0;
 450                 *listppp = NULL;
 451                 return (DSVC_SUCCESS);
 452         }
 453 
 454         listpp = malloc(sizeof (char **));
 455         if (listpp == NULL)
 456                 return (DSVC_NO_MEMORY);
 457         listpp[0] = strdup(DT_DHCPTAB);
 458         if (listpp[0] == NULL) {
 459                 free(listpp);
 460                 return (DSVC_NO_MEMORY);
 461         }
 462 
 463         *listppp = listpp;
 464         *countp = 1;
 465         return (DSVC_SUCCESS);
 466 }
 467 
 468 /*
 469  * Given a buffer `path' of `pathlen' bytes, fill it in with a path to
 470  * the dhcptab in directory `dir' with a suffix of `suffix'.
 471  */
 472 static void
 473 dt2path(char *path, size_t pathlen, const char *dir, const char *suffix)
 474 {
 475         (void) snprintf(path, pathlen, "%s/%s%s", dir, DT_DHCPTAB, suffix);
 476 }
 477 
 478 /*
 479  * Write the dt_rec_t pointed to by `recp' into the open container `fd' at
 480  * offset `recoff'.  Returns DSVC_* error code.
 481  */
 482 static int
 483 write_rec(int fd, dt_rec_t *recp, off_t recoff)
 484 {
 485         char    entbuf[1024], *ent = entbuf;
 486         size_t  entsize = sizeof (entbuf);
 487         int     entlen;
 488 
 489 again:
 490         entlen = snprintf(ent, entsize, "%s\t%c\t%s\n", recp->dt_key,
 491             recp->dt_type, recp->dt_value);
 492         if (entlen == -1)
 493                 return (syserr_to_dsvcerr(errno));
 494 
 495         if (entlen > entsize) {
 496                 entsize = entlen;
 497                 ent = alloca(entlen);
 498                 goto again;
 499         }
 500 
 501         if (pnwrite(fd, ent, entlen, recoff) == -1)
 502                 return (syserr_to_dsvcerr(errno));
 503 
 504         return (DSVC_SUCCESS);
 505 }