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 2005 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 functions for managing legacy DHCP network 31 * containers. For the semantics of these functions, please see the 32 * Enterprise DHCP Architecture Document. 33 */ 34 35 #include <alloca.h> 36 #include <arpa/inet.h> 37 #include <ctype.h> 38 #include <dhcp_svc_public.h> 39 #include <dirent.h> 40 #include <errno.h> 41 #include <fcntl.h> 42 #include <libgen.h> 43 #include <libinetutil.h> 44 #include <netinet/in.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <sys/socket.h> 48 #include <sys/stat.h> 49 #include <sys/types.h> 50 #include <unistd.h> 51 52 #include "dhcp_network.h" 53 #include "util.h" 54 55 static void net2path(char *, size_t, const char *, ipaddr_t, const char *); 56 static boolean_t record_match(char *[], dn_rec_t *, const dn_rec_t *, uint_t); 57 static int write_rec(int, dn_rec_t *, off_t); 58 59 /* ARGSUSED */ 60 int 61 open_dn(void **handlep, const char *location, uint_t flags, 62 const struct in_addr *netp, const struct in_addr *maskp) 63 { 64 char dnpath[MAXPATHLEN]; 65 dn_handle_t *dhp; 66 int retval; 67 int fd; 68 69 dhp = malloc(sizeof (dn_handle_t)); 70 if (dhp == NULL) 71 return (DSVC_NO_MEMORY); 72 73 dhp->dh_net = netp->s_addr; 74 dhp->dh_oflags = flags; 75 (void) strlcpy(dhp->dh_location, location, MAXPATHLEN); 76 77 /* 78 * This is a legacy format which has no header, so we neither write 79 * nor verify a header (we just create the file or make sure it 80 * exists, depending on the value of `flags'). 81 */ 82 net2path(dnpath, MAXPATHLEN, location, netp->s_addr, ""); 83 retval = open_file(dnpath, flags, &fd); 84 if (retval != DSVC_SUCCESS) { 85 free(dhp); 86 return (retval); 87 } 88 (void) close(fd); 89 90 *handlep = dhp; 91 return (DSVC_SUCCESS); 92 } 93 94 int 95 close_dn(void **handlep) 96 { 97 free(*handlep); 98 return (DSVC_SUCCESS); 99 } 100 101 int 102 remove_dn(const char *dir, const struct in_addr *netp) 103 { 104 char dnpath[MAXPATHLEN]; 105 106 net2path(dnpath, MAXPATHLEN, dir, netp->s_addr, ""); 107 if (unlink(dnpath) == -1) 108 return (syserr_to_dsvcerr(errno)); 109 110 return (DSVC_SUCCESS); 111 } 112 113 static int 114 find_dn(FILE *fp, uint_t flags, uint_t query, int count, 115 const dn_rec_t *targetp, dn_rec_list_t **recordsp, uint_t *nrecordsp) 116 { 117 int retval = DSVC_SUCCESS; 118 char *commentp, *fields[DNF_MAX_FIELDS]; 119 char *buf = NULL; 120 uint_t nrecords; 121 dn_rec_t dn, *recordp; 122 dn_rec_list_t *records, *new_records; 123 unsigned int nfields; 124 off_t recoff; 125 126 if (fseek(fp, 0, SEEK_SET) == -1) 127 return (DSVC_INTERNAL); 128 129 records = NULL; 130 for (nrecords = 0; count < 0 || nrecords < count; ) { 131 free(buf); 132 133 if (flags & FIND_POSITION) 134 recoff = ftello(fp); 135 136 buf = read_entry(fp); 137 if (buf == NULL) { 138 if (!feof(fp)) 139 retval = DSVC_NO_MEMORY; 140 break; 141 } 142 143 /* 144 * Skip pure comment lines; for now this just skips the 145 * header information at the top of the container. 146 */ 147 if (buf[0] == DNF_COMMENT_CHAR) 148 continue; 149 150 /* 151 * Tell field_split() that there's one less field than 152 * there really is. We do this so that the comment and the 153 * macro field both end up in the DNF_MACRO field, since 154 * both fields are optional and it requires some fancy 155 * footwork (below) to tell which (if any) the record 156 * contains. 157 */ 158 nfields = field_split(buf, DNF_MAX_FIELDS - 1, fields, " \t"); 159 if (nfields < DNF_REQ_FIELDS) 160 continue; 161 162 if (nfields == DNF_REQ_FIELDS) { 163 fields[DNF_MACRO] = ""; 164 fields[DNF_COMMENT] = ""; 165 } else { 166 /* 167 * Assume there is a comment; if we hit a comment 168 * delimiter char (DNF_COMMENT_CHAR), then simply 169 * change it to a NUL and advance commentp. If we 170 * hit whitespace, replace the first instance with 171 * NUL, and go searching for DNF_COMMENT_CHAR. 172 * This step is important since it efficiently 173 * handles the common case where a comment is 174 * preceded by a space. 175 */ 176 commentp = fields[DNF_MACRO]; 177 while (!isspace(*commentp) && 178 *commentp != DNF_COMMENT_CHAR && *commentp != '\0') 179 commentp++; 180 181 if (isspace(*commentp)) { 182 *commentp++ = '\0'; 183 commentp = strchr(commentp, DNF_COMMENT_CHAR); 184 if (commentp == NULL) 185 commentp = ""; 186 } 187 188 if (*commentp == DNF_COMMENT_CHAR) 189 *commentp++ = '\0'; 190 191 fields[DNF_COMMENT] = commentp; 192 } 193 194 /* 195 * See if we've got a match, filling in dnf.dnf_rec as 196 * we go. If record_match() succeeds, dnf.dnf_rec will 197 * be completely filled in. 198 */ 199 if (!record_match(fields, &dn, targetp, query)) 200 continue; 201 202 /* 203 * Caller just wants a count of the number of matching 204 * records, not the records themselves; continue. 205 */ 206 if (recordsp == NULL) { 207 nrecords++; 208 continue; 209 } 210 211 /* 212 * Allocate record; if FIND_POSITION flag is set, then 213 * we need to allocate an extended (dn_recpos_t) record. 214 */ 215 if (flags & FIND_POSITION) 216 recordp = malloc(sizeof (dn_recpos_t)); 217 else 218 recordp = malloc(sizeof (dn_rec_t)); 219 220 if (recordp == NULL) { 221 if ((flags & FIND_PARTIAL) == 0) 222 retval = DSVC_NO_MEMORY; 223 break; 224 } 225 226 /* 227 * Fill in record; do a structure copy from our automatic 228 * dn. If FIND_POSITION flag is on, pass back additional 229 * position information. 230 */ 231 *recordp = dn; 232 if (flags & FIND_POSITION) { 233 ((dn_recpos_t *)recordp)->dnp_off = recoff; 234 ((dn_recpos_t *)recordp)->dnp_size = ftello(fp) - 235 recoff; 236 } 237 238 /* 239 * Chuck the record on the list and up the counter. 240 */ 241 new_records = add_dnrec_to_list(recordp, records); 242 if (new_records == NULL) { 243 free(recordp); 244 if ((flags & FIND_PARTIAL) == 0) 245 retval = DSVC_NO_MEMORY; 246 break; 247 } 248 249 records = new_records; 250 nrecords++; 251 } 252 253 free(buf); 254 255 if (retval == DSVC_SUCCESS) { 256 *nrecordsp = nrecords; 257 if (recordsp != NULL) 258 *recordsp = records; 259 return (DSVC_SUCCESS); 260 } 261 262 if (records != NULL) 263 free_dnrec_list(records); 264 265 return (retval); 266 } 267 268 int 269 lookup_dn(void *handle, boolean_t partial, uint_t query, int count, 270 const dn_rec_t *targetp, dn_rec_list_t **recordsp, uint_t *nrecordsp) 271 { 272 int retval; 273 char dnpath[MAXPATHLEN]; 274 FILE *fp; 275 dn_handle_t *dhp = (dn_handle_t *)handle; 276 277 if ((dhp->dh_oflags & DSVC_READ) == 0) 278 return (DSVC_ACCESS); 279 280 net2path(dnpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, ""); 281 fp = fopen(dnpath, "r"); 282 if (fp == NULL) 283 return (syserr_to_dsvcerr(errno)); 284 285 retval = find_dn(fp, partial ? FIND_PARTIAL : 0, query, count, targetp, 286 recordsp, nrecordsp); 287 288 (void) fclose(fp); 289 return (retval); 290 } 291 292 /* 293 * Compares the fields in fields[] agains the fields in target `targetp', 294 * using `query' to decide what fields to compare. Returns B_TRUE if `dnp' 295 * matches `targetp', B_FALSE if not. On success, `dnp' is completely 296 * filled in. 297 */ 298 static boolean_t 299 record_match(char *fields[], dn_rec_t *dnp, const dn_rec_t *targetp, 300 uint_t query) 301 { 302 unsigned int qflags[] = { DN_QFDYNAMIC, DN_QFAUTOMATIC, DN_QFMANUAL, 303 DN_QFUNUSABLE, DN_QFBOOTP_ONLY }; 304 unsigned int flags[] = { DN_FDYNAMIC, DN_FAUTOMATIC, DN_FMANUAL, 305 DN_FUNUSABLE, DN_FBOOTP_ONLY }; 306 unsigned int i; 307 uint_t dn_cid_len; 308 309 dnp->dn_cip.s_addr = ntohl(inet_addr(fields[DNF_CIP])); 310 if (DSVC_QISEQ(query, DN_QCIP) && 311 dnp->dn_cip.s_addr != targetp->dn_cip.s_addr) 312 return (B_FALSE); 313 if (DSVC_QISNEQ(query, DN_QCIP) && 314 dnp->dn_cip.s_addr == targetp->dn_cip.s_addr) 315 return (B_FALSE); 316 317 dnp->dn_lease = atoi(fields[DNF_LEASE]); 318 if (DSVC_QISEQ(query, DN_QLEASE) && targetp->dn_lease != dnp->dn_lease) 319 return (B_FALSE); 320 if (DSVC_QISNEQ(query, DN_QLEASE) && targetp->dn_lease == dnp->dn_lease) 321 return (B_FALSE); 322 323 /* 324 * We use dn_cid_len since dnp->dn_cid_len is of type uchar_t but 325 * hexascii_to_octet() expects a uint_t * 326 */ 327 dn_cid_len = DN_MAX_CID_LEN; 328 if (hexascii_to_octet(fields[DNF_CID], strlen(fields[DNF_CID]), 329 dnp->dn_cid, &dn_cid_len) != 0) 330 return (B_FALSE); 331 332 dnp->dn_cid_len = dn_cid_len; 333 if (DSVC_QISEQ(query, DN_QCID) && 334 (dnp->dn_cid_len != targetp->dn_cid_len || 335 (memcmp(dnp->dn_cid, targetp->dn_cid, dnp->dn_cid_len) != 0))) 336 return (B_FALSE); 337 if (DSVC_QISNEQ(query, DN_QCID) && 338 (dnp->dn_cid_len == targetp->dn_cid_len && 339 (memcmp(dnp->dn_cid, targetp->dn_cid, dnp->dn_cid_len) == 0))) 340 return (B_FALSE); 341 342 dnp->dn_sip.s_addr = ntohl(inet_addr(fields[DNF_SIP])); 343 if (DSVC_QISEQ(query, DN_QSIP) && 344 dnp->dn_sip.s_addr != targetp->dn_sip.s_addr) 345 return (B_FALSE); 346 if (DSVC_QISNEQ(query, DN_QSIP) && 347 dnp->dn_sip.s_addr == targetp->dn_sip.s_addr) 348 return (B_FALSE); 349 350 (void) strlcpy(dnp->dn_macro, fields[DNF_MACRO], 351 sizeof (dnp->dn_macro)); 352 if (DSVC_QISEQ(query, DN_QMACRO) && 353 strcmp(targetp->dn_macro, dnp->dn_macro) != 0) 354 return (B_FALSE); 355 if (DSVC_QISNEQ(query, DN_QMACRO) && 356 strcmp(targetp->dn_macro, dnp->dn_macro) == 0) 357 return (B_FALSE); 358 359 dnp->dn_flags = atoi(fields[DNF_FLAGS]); 360 for (i = 0; i < sizeof (qflags) / sizeof (unsigned int); i++) { 361 if (DSVC_QISEQ(query, qflags[i]) && 362 (dnp->dn_flags & flags[i]) != 363 (targetp->dn_flags & flags[i])) 364 return (B_FALSE); 365 if (DSVC_QISNEQ(query, qflags[i]) && 366 (dnp->dn_flags & flags[i]) == 367 (targetp->dn_flags & flags[i])) 368 return (B_FALSE); 369 } 370 (void) strlcpy(dnp->dn_comment, fields[DNF_COMMENT], 371 sizeof (dnp->dn_comment)); 372 373 return (B_TRUE); 374 } 375 376 /* 377 * Internal dhcp_network record update routine, used to factor out the 378 * common code between add_dn(), delete_dn(), and modify_dn(). If `origp' 379 * is NULL, then act like add_dn(); if `newp' is NULL, then act like 380 * delete_dn(); otherwise act like modify_dn(). 381 */ 382 static int 383 update_dn(const dn_handle_t *dhp, const dn_rec_t *origp, dn_rec_t *newp) 384 { 385 char dnpath[MAXPATHLEN], newpath[MAXPATHLEN]; 386 int retval = DSVC_SUCCESS; 387 off_t recoff, recnext; 388 dn_rec_list_t *reclist; 389 FILE *fp; 390 int newfd; 391 uint_t found; 392 int query; 393 struct stat st; 394 395 if ((dhp->dh_oflags & DSVC_WRITE) == 0) 396 return (DSVC_ACCESS); 397 398 /* 399 * Open the container to update and a new container file which we 400 * will store the updated version of the container in. When the 401 * update is done, rename the new file to be the real container. 402 */ 403 net2path(dnpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, ""); 404 fp = fopen(dnpath, "r"); 405 if (fp == NULL) 406 return (syserr_to_dsvcerr(errno)); 407 408 net2path(newpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, ".new"); 409 newfd = open(newpath, O_CREAT|O_TRUNC|O_WRONLY, 0644); 410 if (newfd == -1) { 411 (void) fclose(fp); 412 return (syserr_to_dsvcerr(errno)); 413 } 414 415 DSVC_QINIT(query); 416 DSVC_QEQ(query, DN_QCIP); 417 418 /* 419 * If we're adding a new record or changing a key for an existing 420 * record, bail if the record we want to add already exists. 421 */ 422 if (newp != NULL) { 423 if (origp == NULL || 424 origp->dn_cip.s_addr != newp->dn_cip.s_addr) { 425 retval = find_dn(fp, 0, query, 1, newp, NULL, &found); 426 if (retval != DSVC_SUCCESS) 427 goto out; 428 if (found != 0) { 429 retval = DSVC_EXISTS; 430 goto out; 431 } 432 } 433 } 434 435 /* 436 * If we're deleting or modifying record, make sure the record 437 * still exists. Note that we don't check signatures because this 438 * is a legacy format that has no signatures. 439 */ 440 if (origp != NULL) { 441 retval = find_dn(fp, FIND_POSITION, query, 1, origp, &reclist, 442 &found); 443 if (retval != DSVC_SUCCESS) 444 goto out; 445 if (found == 0) { 446 retval = DSVC_NOENT; 447 goto out; 448 } 449 450 /* 451 * Note the offset of the record we're modifying or deleting 452 * for use down below. 453 */ 454 recoff = ((dn_recpos_t *)reclist->dnl_rec)->dnp_off; 455 recnext = recoff + ((dn_recpos_t *)reclist->dnl_rec)->dnp_size; 456 457 free_dnrec_list(reclist); 458 } else { 459 /* 460 * No record to modify or delete, so set `recoff' and 461 * `recnext' appropriately. 462 */ 463 recoff = 0; 464 recnext = 0; 465 } 466 467 /* 468 * Make a new copy of the container. If we're deleting or 469 * modifying a record, don't copy that record to the new container. 470 */ 471 if (fstat(fileno(fp), &st) == -1) { 472 retval = DSVC_INTERNAL; 473 goto out; 474 } 475 476 retval = copy_range(fileno(fp), 0, newfd, 0, recoff); 477 if (retval != DSVC_SUCCESS) 478 goto out; 479 480 retval = copy_range(fileno(fp), recnext, newfd, recoff, 481 st.st_size - recnext); 482 if (retval != DSVC_SUCCESS) 483 goto out; 484 485 /* 486 * If there's a new/modified record, append it to the new container. 487 */ 488 if (newp != NULL) { 489 retval = write_rec(newfd, newp, recoff + st.st_size - recnext); 490 if (retval != DSVC_SUCCESS) 491 goto out; 492 } 493 494 /* 495 * Note: we close these descriptors before the rename(2) (rather 496 * than just having the `out:' label clean them up) to save NFS 497 * some work (otherwise, NFS has to save `dnpath' to an alternate 498 * name since its vnode would still be active). 499 */ 500 (void) fclose(fp); 501 (void) close(newfd); 502 503 if (rename(newpath, dnpath) == -1) 504 retval = syserr_to_dsvcerr(errno); 505 506 return (retval); 507 out: 508 (void) fclose(fp); 509 (void) close(newfd); 510 (void) unlink(newpath); 511 return (retval); 512 } 513 514 int 515 add_dn(void *handle, dn_rec_t *addp) 516 { 517 return (update_dn((dn_handle_t *)handle, NULL, addp)); 518 } 519 520 int 521 modify_dn(void *handle, const dn_rec_t *origp, dn_rec_t *newp) 522 { 523 return (update_dn((dn_handle_t *)handle, origp, newp)); 524 } 525 526 int 527 delete_dn(void *handle, const dn_rec_t *delp) 528 { 529 return (update_dn((dn_handle_t *)handle, delp, NULL)); 530 } 531 532 int 533 list_dn(const char *location, char ***listppp, uint_t *countp) 534 { 535 char ipaddr[INET_ADDRSTRLEN]; 536 struct dirent *result; 537 DIR *dirp; 538 unsigned int i, count = 0; 539 char *re, **new_listpp, **listpp = NULL; 540 int error; 541 542 dirp = opendir(location); 543 if (dirp == NULL) { 544 switch (errno) { 545 case EACCES: 546 case EPERM: 547 return (DSVC_ACCESS); 548 case ENOENT: 549 return (DSVC_NO_LOCATION); 550 default: 551 break; 552 } 553 return (DSVC_INTERNAL); 554 } 555 556 /* 557 * Compile a regular expression matching an IP address delimited by 558 * underscores. Note that the `$0' at the end allows us to save the 559 * IP address in ipaddr when calling regex(3C). 560 */ 561 re = regcmp("^(([0-9]{1,3}\\_){3}[0-9]{1,3})$0$", (char *)0); 562 if (re == NULL) 563 return (DSVC_NO_MEMORY); 564 565 while ((result = readdir(dirp)) != NULL) { 566 if (regex(re, result->d_name, ipaddr) != NULL) { 567 new_listpp = realloc(listpp, 568 (sizeof (char **)) * (count + 1)); 569 if (new_listpp == NULL) { 570 error = DSVC_NO_MEMORY; 571 goto fail; 572 } 573 listpp = new_listpp; 574 listpp[count] = strdup(ipaddr); 575 if (listpp[count] == NULL) { 576 error = DSVC_NO_MEMORY; 577 goto fail; 578 } 579 580 /* 581 * Change all underscores to dots. 582 */ 583 for (i = 0; listpp[count][i] != '\0'; i++) { 584 if (listpp[count][i] == '_') 585 listpp[count][i] = '.'; 586 } 587 588 count++; 589 } 590 } 591 free(re); 592 (void) closedir(dirp); 593 594 *countp = count; 595 *listppp = listpp; 596 return (DSVC_SUCCESS); 597 598 fail: 599 free(re); 600 (void) closedir(dirp); 601 602 for (i = 0; i < count; i++) 603 free(listpp[i]); 604 free(listpp); 605 return (error); 606 } 607 608 /* 609 * Given a buffer `path' of `pathlen' bytes, fill it in with a path to the 610 * DHCP Network table for IP network `ip' located in directory `dir' with a 611 * suffix of `suffix'. 612 */ 613 static void 614 net2path(char *path, size_t pathlen, const char *dir, ipaddr_t ip, 615 const char *suffix) 616 { 617 (void) snprintf(path, pathlen, "%s/%d_%d_%d_%d%s", dir, ip >> 24, 618 (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff, suffix); 619 } 620 621 /* 622 * Write the dn_rec_t `recp' into the open container `fd' at offset 623 * `recoff'. Returns DSVC_* error code. 624 */ 625 static int 626 write_rec(int fd, dn_rec_t *recp, off_t recoff) 627 { 628 char entbuf[1024], *ent = entbuf; 629 size_t entsize = sizeof (entbuf); 630 int entlen; 631 char dn_cip[INET_ADDRSTRLEN], dn_sip[INET_ADDRSTRLEN]; 632 char dn_cid[DN_MAX_CID_LEN * 2 + 1]; 633 unsigned int dn_cid_len = sizeof (dn_cid); 634 struct in_addr nip; 635 636 if (octet_to_hexascii(recp->dn_cid, recp->dn_cid_len, dn_cid, 637 &dn_cid_len) != 0) 638 return (DSVC_INTERNAL); 639 640 nip.s_addr = htonl(recp->dn_cip.s_addr); 641 (void) inet_ntop(AF_INET, &nip, dn_cip, sizeof (dn_cip)); 642 nip.s_addr = htonl(recp->dn_sip.s_addr); 643 (void) inet_ntop(AF_INET, &nip, dn_sip, sizeof (dn_sip)); 644 again: 645 if (recp->dn_comment[0] != '\0') { 646 entlen = snprintf(ent, entsize, "%s %02hu %s %s %u %s %c%s\n", 647 dn_cid, recp->dn_flags, dn_cip, dn_sip, recp->dn_lease, 648 recp->dn_macro, DNF_COMMENT_CHAR, recp->dn_comment); 649 } else { 650 entlen = snprintf(ent, entsize, "%s %02hu %s %s %u %s\n", 651 dn_cid, recp->dn_flags, dn_cip, dn_sip, recp->dn_lease, 652 recp->dn_macro); 653 } 654 655 if (entlen == -1) 656 return (syserr_to_dsvcerr(errno)); 657 658 if (entlen > entsize) { 659 entsize = entlen; 660 ent = alloca(entlen); 661 goto again; 662 } 663 664 if (pnwrite(fd, ent, entlen, recoff) == -1) 665 return (syserr_to_dsvcerr(errno)); 666 667 return (DSVC_SUCCESS); 668 }