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 }