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 }