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 dhcptab
  31  * container.  For the semantics of these functions, please see the
  32  * Enterprise DHCP Architecture Document.
  33  */
  34 
  35 #include <sys/types.h>
  36 #include <sys/socket.h>
  37 #include <unistd.h>
  38 #include <netinet/in.h>
  39 #include <sys/stat.h>
  40 #include <fcntl.h>
  41 #include <alloca.h>
  42 #include <stdlib.h>
  43 #include <stdio.h>
  44 #include <string.h>
  45 #include <errno.h>
  46 #include <dhcp_svc_public.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         unsigned int    conver;
  59         int             nelems;
  60         int             retval;
  61         char            nl;
  62         int             fd;
  63         char            dtpath[MAXPATHLEN];
  64         FILE            *fp;
  65 
  66         dhp = malloc(sizeof (dt_handle_t));
  67         if (dhp == NULL)
  68                 return (DSVC_NO_MEMORY);
  69 
  70         dhp->dh_oflags = flags;
  71         (void) strlcpy(dhp->dh_location, location, MAXPATHLEN);
  72 
  73         dt2path(dtpath, MAXPATHLEN, location, "");
  74         retval = open_file(dtpath, flags, &fd);
  75         if (retval != DSVC_SUCCESS) {
  76                 free(dhp);
  77                 return (retval);
  78         }
  79 
  80         fp = fdopen(fd, flags & DSVC_WRITE ? "r+" : "r");
  81         if (fp == NULL) {
  82                 (void) close(fd);
  83                 free(dhp);
  84                 return (DSVC_INTERNAL);
  85         }
  86 
  87         if (flags & DSVC_CREATE) {
  88                 /*
  89                  * We just created the per-network container; put the
  90                  * header on for future use...
  91                  */
  92                 retval = fprintf(fp, "# SUNWfiles%u_dhcptab\n", DSVC_CONVER);
  93                 if (retval < 0 || fflush(fp) == EOF) {
  94                         (void) fclose(fp);
  95                         (void) free(dhp);
  96                         return (DSVC_INTERNAL);
  97                 }
  98 
  99                 (void) fprintf(fp, "#\n# Do NOT edit this file by hand -- use");
 100                 (void) fprintf(fp, " dhtadm(1M) or dhcpmgr(1M) instead\n#\n");
 101         } else {
 102                 /*
 103                  * Container already exists; sanity check against the
 104                  * header that's on-disk.
 105                  */
 106                 nelems = fscanf(fp, "#%*1[ ]SUNWfiles%u_dhcptab%c", &conver,
 107                     &nl);
 108                 if (nelems != 2 || conver != DSVC_CONVER || nl != '\n') {
 109                         (void) fclose(fp);
 110                         free(dhp);
 111                         return (DSVC_INTERNAL);
 112                 }
 113         }
 114 
 115         (void) fclose(fp);
 116         *handlep = dhp;
 117         return (DSVC_SUCCESS);
 118 }
 119 
 120 int
 121 close_dt(void **handlep)
 122 {
 123         free(*handlep);
 124         return (DSVC_SUCCESS);
 125 }
 126 
 127 int
 128 remove_dt(const char *location)
 129 {
 130         char dtpath[MAXPATHLEN];
 131 
 132         dt2path(dtpath, MAXPATHLEN, location, "");
 133         if (unlink(dtpath) == -1)
 134                 return (syserr_to_dsvcerr(errno));
 135 
 136         return (DSVC_SUCCESS);
 137 }
 138 
 139 /*
 140  * Internal version of lookup_dt() used by both lookup_dt() and
 141  * update_dt(); same semantics as lookup_dt() except that the `partial'
 142  * argument has been generalized into a `flags' field and the handle has
 143  * been turned into a FILE pointer.
 144  */
 145 static int
 146 find_dt(FILE *fp, uint_t flags, uint_t query, int count,
 147     const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
 148 {
 149         int             retval = DSVC_SUCCESS;
 150         char            *buf = NULL, *fields[DTF_FIELDS];
 151         uint_t          nrecords;
 152         dt_rec_t        dt, *recordp;
 153         dt_rec_list_t   *records, *new_records;
 154         off_t           recoff;
 155         unsigned int    nfields;
 156 
 157         if (fseek(fp, 0, SEEK_SET) == -1)
 158                 return (DSVC_INTERNAL);
 159 
 160         records = NULL;
 161         for (nrecords = 0; count < 0 || nrecords < count; ) {
 162                 free(buf);
 163 
 164                 if (flags & FIND_POSITION)
 165                         recoff = ftello(fp);
 166 
 167                 buf = read_entry(fp);
 168                 if (buf == NULL) {
 169                         if (!feof(fp))
 170                                 retval = DSVC_NO_MEMORY;
 171                         break;
 172                 }
 173 
 174                 /*
 175                  * Skip pure comment lines; for now this just skips the
 176                  * header information at the top of the container.
 177                  */
 178                 if (buf[0] == DTF_COMMENT_CHAR)
 179                         continue;
 180 
 181                 /*
 182                  * Split the buffer up into DTF_FIELDS fields.
 183                  */
 184                 nfields = field_split(buf, DTF_FIELDS, fields, "|");
 185                 if (nfields < DTF_FIELDS)
 186                         continue;
 187 
 188                 /*
 189                  * See if we've got a match.  If so, allocate the new
 190                  * record, fill it in, and continue.
 191                  */
 192                 dt.dt_type = fields[DTF_TYPE][0];
 193                 if (DSVC_QISEQ(query, DT_QTYPE) &&
 194                     targetp->dt_type != dt.dt_type)
 195                         continue;
 196                 else if (DSVC_QISNEQ(query, DT_QTYPE) &&
 197                     targetp->dt_type == dt.dt_type)
 198                         continue;
 199 
 200                 unescape('|', fields[DTF_KEY], dt.dt_key, sizeof (dt.dt_key));
 201                 if (DSVC_QISEQ(query, DT_QKEY) &&
 202                     strcmp(targetp->dt_key, dt.dt_key) != 0)
 203                         continue;
 204                 else if (DSVC_QISNEQ(query, DT_QKEY) &&
 205                     strcmp(targetp->dt_key, dt.dt_key) == 0)
 206                         continue;
 207 
 208                 /*
 209                  * Caller just wants a count of the number of matching
 210                  * records, not the records themselves; continue.
 211                  */
 212                 if (recordsp == NULL) {
 213                         nrecords++;
 214                         continue;
 215                 }
 216 
 217                 dt.dt_sig = atoll(fields[DTF_SIG]);
 218                 dt.dt_value = strdup(fields[DTF_VALUE]);
 219                 if (dt.dt_value == NULL) {
 220                         if ((flags & FIND_PARTIAL) == 0)
 221                                 retval = DSVC_NO_MEMORY;
 222                         break;
 223                 }
 224 
 225                 /*
 226                  * Allocate record; if FIND_POSITION flag is set, then
 227                  * we need to allocate an extended (dt_recpos_t) record.
 228                  */
 229                 if (flags & FIND_POSITION)
 230                         recordp = malloc(sizeof (dt_recpos_t));
 231                 else
 232                         recordp = malloc(sizeof (dt_rec_t));
 233 
 234                 if (recordp == NULL) {
 235                         free(dt.dt_value);
 236                         if ((flags & FIND_PARTIAL) == 0)
 237                                 retval = DSVC_NO_MEMORY;
 238                         break;
 239                 }
 240 
 241                 /*
 242                  * Fill in record; do a structure copy from our automatic
 243                  * dt.  If FIND_POSITION flag is on, pass back additional
 244                  * location information.
 245                  */
 246                 *recordp = dt;
 247                 if (flags & FIND_POSITION) {
 248                         ((dt_recpos_t *)recordp)->dtp_off = recoff;
 249                         ((dt_recpos_t *)recordp)->dtp_size = ftello(fp) -
 250                             recoff;
 251                 }
 252 
 253                 /*
 254                  * Chuck the record on the list; up the counter.
 255                  */
 256                 new_records = add_dtrec_to_list(recordp, records);
 257                 if (new_records == NULL) {
 258                         free_dtrec(recordp);
 259                         if ((flags & FIND_PARTIAL) == 0)
 260                                 retval = DSVC_NO_MEMORY;
 261                         break;
 262                 }
 263                 records = new_records;
 264                 nrecords++;
 265         }
 266 
 267         free(buf);
 268 
 269         if (retval == DSVC_SUCCESS) {
 270                 *nrecordsp = nrecords;
 271                 if (recordsp != NULL)
 272                         *recordsp = records;
 273                 return (DSVC_SUCCESS);
 274         }
 275 
 276         if (records != NULL)
 277                 free_dtrec_list(records);
 278 
 279         return (retval);
 280 }
 281 
 282 int
 283 lookup_dt(void *handle, boolean_t partial, uint_t query, int count,
 284     const dt_rec_t *targetp, dt_rec_list_t **recordsp, uint_t *nrecordsp)
 285 {
 286         int             retval;
 287         char            dtpath[MAXPATHLEN];
 288         FILE            *fp;
 289         dt_handle_t     *dhp = (dt_handle_t *)handle;
 290 
 291         if ((dhp->dh_oflags & DSVC_READ) == 0)
 292                 return (DSVC_ACCESS);
 293 
 294         dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
 295         fp = fopen(dtpath, "r");
 296         if (fp == NULL)
 297                 return (syserr_to_dsvcerr(errno));
 298 
 299         retval = find_dt(fp, partial ? FIND_PARTIAL : 0, query, count, targetp,
 300             recordsp, nrecordsp);
 301 
 302         (void) fclose(fp);
 303         return (retval);
 304 }
 305 
 306 /*
 307  * Internal dhcptab record update routine, used to factor out the
 308  * common code between add_dt(), delete_dt(), and modify_dt().  If
 309  * `origp' is NULL, then act like add_dt(); if `newp' is NULL, then
 310  * act like delete_dt(); otherwise act like modify_dt().
 311  */
 312 static int
 313 update_dt(const dt_handle_t *dhp, const dt_rec_t *origp, dt_rec_t *newp)
 314 {
 315         char            dtpath[MAXPATHLEN], newpath[MAXPATHLEN];
 316         int             retval = DSVC_SUCCESS;
 317         off_t           recoff, recnext;
 318         dt_rec_list_t   *reclist;
 319         FILE            *fp;
 320         int             newfd;
 321         uint_t          found;
 322         int             query;
 323         struct stat     st;
 324 
 325         if ((dhp->dh_oflags & DSVC_WRITE) == 0)
 326                 return (DSVC_ACCESS);
 327 
 328         /*
 329          * Open the container to update and a new container file which we
 330          * will store the updated version of the container in.  When the
 331          * update is done, rename the new file to be the real container.
 332          */
 333         dt2path(dtpath, MAXPATHLEN, dhp->dh_location, "");
 334         fp = fopen(dtpath, "r");
 335         if (fp == NULL)
 336                 return (syserr_to_dsvcerr(errno));
 337 
 338         dt2path(newpath, MAXPATHLEN, dhp->dh_location, ".new");
 339         (void) unlink(newpath);
 340         newfd = open(newpath, O_CREAT|O_EXCL|O_WRONLY, 0644);
 341         if (newfd == -1) {
 342                 (void) fclose(fp);
 343                 return (syserr_to_dsvcerr(errno));
 344         }
 345 
 346         DSVC_QINIT(query);
 347         DSVC_QEQ(query, DT_QKEY|DT_QTYPE);
 348 
 349         /*
 350          * If we're changing the key for this record, make sure the key
 351          * we're changing to doesn't already exist.
 352          */
 353         if (origp != NULL && newp != NULL) {
 354                 if ((origp->dt_type != newp->dt_type ||
 355                     strcmp(origp->dt_key, newp->dt_key) != 0)) {
 356                         retval = find_dt(fp, 0, query, 1, newp, NULL, &found);
 357                         if (retval != DSVC_SUCCESS)
 358                                 goto out;
 359                         if (found != 0) {
 360                                 retval = DSVC_EXISTS;
 361                                 goto out;
 362                         }
 363                 }
 364         }
 365 
 366         /*
 367          * If we're adding a new record, make sure the record doesn't
 368          * already exist.
 369          */
 370         if (newp != NULL && origp == NULL) {
 371                 retval = find_dt(fp, 0, query, 1, newp, NULL, &found);
 372                 if (retval != DSVC_SUCCESS)
 373                         goto out;
 374                 if (found != 0) {
 375                         retval = DSVC_EXISTS;
 376                         goto out;
 377                 }
 378         }
 379 
 380         /*
 381          * If we're deleting or modifying record, make sure the record
 382          * still exists and that our copy isn't stale.  Note that we don't
 383          * check signatures if we're deleting the record and origp->dt_sig
 384          * is zero, so that records can be deleted that weren't looked up
 385          * first.
 386          */
 387         if (origp != NULL) {
 388                 retval = find_dt(fp, FIND_POSITION, query, 1, origp, &reclist,
 389                     &found);
 390                 if (retval != DSVC_SUCCESS)
 391                         goto out;
 392                 if (found == 0) {
 393                         retval = DSVC_NOENT;
 394                         goto out;
 395                 }
 396 
 397                 if (reclist->dtl_rec->dt_sig != origp->dt_sig) {
 398                         if (newp != NULL || origp->dt_sig != 0) {
 399                                 free_dtrec_list(reclist);
 400                                 retval = DSVC_COLLISION;
 401                                 goto out;
 402                         }
 403                 }
 404 
 405                 /*
 406                  * Note the offset of the record we're modifying or deleting
 407                  * for use down below.
 408                  */
 409                 recoff  = ((dt_recpos_t *)reclist->dtl_rec)->dtp_off;
 410                 recnext = recoff + ((dt_recpos_t *)reclist->dtl_rec)->dtp_size;
 411 
 412                 free_dtrec_list(reclist);
 413         } else {
 414                 /*
 415                  * No record to modify or delete, so set `recoff' and
 416                  * `recnext' appropriately.
 417                  */
 418                 recoff = 0;
 419                 recnext = 0;
 420         }
 421 
 422         /*
 423          * Make a new copy of the container.  If we're deleting or
 424          * modifying a record, don't copy that record to the new container.
 425          */
 426         if (fstat(fileno(fp), &st) == -1) {
 427                 retval = DSVC_INTERNAL;
 428                 goto out;
 429         }
 430 
 431         retval = copy_range(fileno(fp), 0, newfd, 0, recoff);
 432         if (retval != DSVC_SUCCESS)
 433                 goto out;
 434 
 435         retval = copy_range(fileno(fp), recnext, newfd, recoff,
 436             st.st_size - recnext);
 437         if (retval != DSVC_SUCCESS)
 438                 goto out;
 439 
 440         /*
 441          * If there's a new record, append it to the new container.
 442          */
 443         if (newp != NULL) {
 444                 if (origp == NULL)
 445                         newp->dt_sig = gensig();
 446                 else
 447                         newp->dt_sig = origp->dt_sig + 1;
 448 
 449                 if (fstat(newfd, &st) == -1) {
 450                         retval = DSVC_INTERNAL;
 451                         goto out;
 452                 }
 453 
 454                 retval = write_rec(newfd, newp, st.st_size);
 455                 if (retval != DSVC_SUCCESS)
 456                         goto out;
 457         }
 458 
 459         /*
 460          * Note: we close these descriptors before the rename(2) (rather
 461          * than just having the `out:' label clean them up) to save NFS
 462          * some work (otherwise, NFS has to save `dnpath' to an alternate
 463          * name since its vnode would still be active).
 464          */
 465         (void) fclose(fp);
 466         (void) close(newfd);
 467 
 468         if (rename(newpath, dtpath) == -1)
 469                 retval = syserr_to_dsvcerr(errno);
 470 
 471         return (retval);
 472 out:
 473         (void) fclose(fp);
 474         (void) close(newfd);
 475         (void) unlink(newpath);
 476         return (retval);
 477 }
 478 
 479 int
 480 delete_dt(void *handle, const dt_rec_t *delp)
 481 {
 482         return (update_dt((dt_handle_t *)handle, delp, NULL));
 483 }
 484 
 485 int
 486 add_dt(void *handle, dt_rec_t *addp)
 487 {
 488         return (update_dt((dt_handle_t *)handle, NULL, addp));
 489 }
 490 
 491 int
 492 modify_dt(void *handle, const dt_rec_t *origp, dt_rec_t *newp)
 493 {
 494         return (update_dt((dt_handle_t *)handle, origp, newp));
 495 }
 496 
 497 int
 498 list_dt(const char *location, char ***listppp, uint_t *countp)
 499 {
 500         char    dtpath[MAXPATHLEN];
 501         char    **listpp;
 502 
 503         if (access(location, F_OK|R_OK) == -1) {
 504                 switch (errno) {
 505                 case EACCES:
 506                 case EPERM:
 507                         return (DSVC_ACCESS);
 508                 case ENOENT:
 509                         return (DSVC_NO_LOCATION);
 510                 default:
 511                         break;
 512                 }
 513                 return (DSVC_INTERNAL);
 514         }
 515 
 516         dt2path(dtpath, MAXPATHLEN, location, "");
 517         if (access(dtpath, F_OK|R_OK) == -1) {
 518                 *countp = 0;
 519                 *listppp = NULL;
 520                 return (DSVC_SUCCESS);
 521         }
 522 
 523         listpp = malloc(sizeof (char **));
 524         if (listpp == NULL)
 525                 return (DSVC_NO_MEMORY);
 526         listpp[0] = strdup(DT_DHCPTAB);
 527         if (listpp[0] == NULL) {
 528                 free(listpp);
 529                 return (DSVC_NO_MEMORY);
 530         }
 531 
 532         *listppp = listpp;
 533         *countp = 1;
 534         return (DSVC_SUCCESS);
 535 }
 536 
 537 /*
 538  * Given a buffer `path' of `pathlen' bytes, fill it in with a path to the
 539  * dhcptab in directory `dir' with a suffix of `suffix'.
 540  */
 541 static void
 542 dt2path(char *path, size_t pathlen, const char *dir, const char *suffix)
 543 {
 544         (void) snprintf(path, pathlen, "%s/SUNWfiles%u_%s%s", dir,
 545             DSVC_CONVER, DT_DHCPTAB, suffix);
 546 }
 547 
 548 /*
 549  * Write the dt_rec_t pointed to by `recp' into the open container `fd' at
 550  * offset `recoff'.  Returns DSVC_* error code.
 551  */
 552 static int
 553 write_rec(int fd, dt_rec_t *recp, off_t recoff)
 554 {
 555         char    escaped_key[DSVC_MAX_MACSYM_LEN * 2 + 1];
 556         char    entbuf[1024], *ent = entbuf;
 557         size_t  entsize = sizeof (entbuf);
 558         int     entlen;
 559 
 560         escape('|', recp->dt_key, escaped_key, sizeof (escaped_key));
 561 again:
 562         entlen = snprintf(ent, entsize, "%s|%c|%llu|%s\n", escaped_key,
 563             recp->dt_type, recp->dt_sig, recp->dt_value);
 564         if (entlen == -1)
 565                 return (syserr_to_dsvcerr(errno));
 566 
 567         if (entlen > entsize) {
 568                 entsize = entlen;
 569                 ent = alloca(entlen);
 570                 goto again;
 571         }
 572 
 573         if (pnwrite(fd, ent, entlen, recoff) == -1)
 574                 return (syserr_to_dsvcerr(errno));
 575 
 576         return (DSVC_SUCCESS);
 577 }