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 (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <ctype.h> 27 #include <stdio.h> 28 #include <string.h> 29 #include <strings.h> 30 #include <stdlib.h> 31 #include <pthread.h> 32 #include <sys/varargs.h> 33 #include <sys/types.h> 34 #include <sys/mnttab.h> 35 #include <tiuser.h> 36 #include <netconfig.h> 37 #include <netdir.h> 38 #include <sys/systeminfo.h> 39 #include <sys/utsname.h> 40 #include <libzfs.h> 41 #include <dlfcn.h> 42 #include <smbsrv/string.h> 43 #include <smbsrv/libsmb.h> 44 45 #define SMB_LIB_ALT "/usr/lib/smbsrv/libsmbex.so" 46 47 static uint_t smb_make_mask(char *, uint_t); 48 static boolean_t smb_netmatch(struct netbuf *, char *); 49 static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int); 50 51 extern int __multi_innetgr(); 52 extern int __netdir_getbyaddr_nosrv(struct netconfig *, 53 struct nd_hostservlist **, struct netbuf *); 54 55 #define C2H(c) "0123456789ABCDEF"[(c)] 56 #define H2C(c) (((c) >= '0' && (c) <= '9') ? ((c) - '0') : \ 57 ((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) : \ 58 ((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) : \ 59 '\0') 60 #define DEFAULT_SBOX_SIZE 256 61 62 /* 63 * 64 * hexdump 65 * 66 * Simple hex dump display function. Displays nbytes of buffer in hex and 67 * printable format. Non-printing characters are shown as '.'. It is safe 68 * to pass a null pointer. Each line begins with the offset. If nbytes is 69 * 0, the line will be blank except for the offset. Example output: 70 * 71 * 00000000 54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61 This is a progra 72 * 00000010 6D 20 74 65 73 74 2E 00 m test.. 73 * 74 */ 75 void 76 hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start) 77 { 78 static char *hex = "0123456789ABCDEF"; 79 int i, count; 80 int offset; 81 unsigned char *p; 82 char ascbuf[64]; 83 char hexbuf[64]; 84 char *ap = ascbuf; 85 char *hp = hexbuf; 86 87 if ((p = buffer) == NULL) 88 return; 89 90 offset = *start; 91 92 *ap = '\0'; 93 *hp = '\0'; 94 count = 0; 95 96 for (i = 0; i < nbytes; ++i) { 97 if (i && (i % 16) == 0) { 98 smb_tracef("%06X %s %s", offset, hexbuf, ascbuf); 99 ap = ascbuf; 100 hp = hexbuf; 101 count = 0; 102 offset += 16; 103 } 104 105 ap += sprintf(ap, "%c", 106 (*p >= 0x20 && *p < 0x7F) ? *p : '.'); 107 hp += sprintf(hp, " %c%c", 108 hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]); 109 ++p; 110 ++count; 111 } 112 113 if (count) { 114 smb_tracef("%06X %-48s %s", offset, hexbuf, ascbuf); 115 offset += count; 116 } 117 118 *start = offset; 119 } 120 121 void 122 hexdump(unsigned char *buffer, int nbytes) 123 { 124 unsigned long start = 0; 125 126 hexdump_offset(buffer, nbytes, &start); 127 } 128 129 /* 130 * bintohex 131 * 132 * Converts the given binary data (srcbuf) to 133 * its equivalent hex chars (hexbuf). 134 * 135 * hexlen should be at least twice as srclen. 136 * if hexbuf is not big enough returns 0. 137 * otherwise returns number of valid chars in 138 * hexbuf which is srclen * 2. 139 */ 140 size_t 141 bintohex(const char *srcbuf, size_t srclen, 142 char *hexbuf, size_t hexlen) 143 { 144 size_t outlen; 145 char c; 146 147 outlen = srclen << 1; 148 149 if (hexlen < outlen) 150 return (0); 151 152 while (srclen-- > 0) { 153 c = *srcbuf++; 154 *hexbuf++ = C2H(c & 0xF); 155 *hexbuf++ = C2H((c >> 4) & 0xF); 156 } 157 158 return (outlen); 159 } 160 161 /* 162 * hextobin 163 * 164 * Converts hex to binary. 165 * 166 * Assuming hexbuf only contains hex digits (chars) 167 * this function convert every two bytes of hexbuf 168 * to one byte and put it in dstbuf. 169 * 170 * hexlen should be an even number. 171 * dstlen should be at least half of hexlen. 172 * 173 * Returns 0 if sizes are not correct, otherwise 174 * returns the number of converted bytes in dstbuf 175 * which is half of hexlen. 176 */ 177 size_t 178 hextobin(const char *hexbuf, size_t hexlen, 179 char *dstbuf, size_t dstlen) 180 { 181 size_t outlen; 182 183 if ((hexlen % 2) != 0) 184 return (0); 185 186 outlen = hexlen >> 1; 187 if (dstlen < outlen) 188 return (0); 189 190 while (hexlen > 0) { 191 *dstbuf = H2C(*hexbuf) & 0x0F; 192 hexbuf++; 193 *dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0; 194 hexbuf++; 195 196 hexlen -= 2; 197 } 198 199 return (outlen); 200 } 201 202 /* 203 * Trim leading and trailing characters in the set defined by class 204 * from a buffer containing a null-terminated string. 205 * For example, if the input buffer contained "ABtext23" and class 206 * contains "ABC123", the buffer will contain "text" on return. 207 * 208 * This function modifies the contents of buf in place and returns 209 * a pointer to buf. 210 */ 211 char * 212 strtrim(char *buf, const char *class) 213 { 214 char *p = buf; 215 char *q = buf; 216 217 if (buf == NULL) 218 return (NULL); 219 220 p += strspn(p, class); 221 222 if (p != buf) { 223 while ((*q = *p++) != '\0') 224 ++q; 225 } 226 227 while (q != buf) { 228 --q; 229 if (strspn(q, class) == 0) 230 return (buf); 231 *q = '\0'; 232 } 233 234 return (buf); 235 } 236 237 /* 238 * Strip the characters in the set defined by class from a buffer 239 * containing a null-terminated string. 240 * For example, if the input buffer contained "XYA 1textZ string3" 241 * and class contains "123XYZ", the buffer will contain "A text string" 242 * on return. 243 * 244 * This function modifies the contents of buf in place and returns 245 * a pointer to buf. 246 */ 247 char * 248 strstrip(char *buf, const char *class) 249 { 250 char *p = buf; 251 char *q = buf; 252 253 if (buf == NULL) 254 return (NULL); 255 256 while (*p) { 257 p += strspn(p, class); 258 *q++ = *p++; 259 } 260 261 *q = '\0'; 262 return (buf); 263 } 264 265 /* 266 * trim_whitespace 267 * 268 * Trim leading and trailing whitespace chars (as defined by isspace) 269 * from a buffer. Example; if the input buffer contained " text ", 270 * it will contain "text", when we return. We assume that the buffer 271 * contains a null terminated string. A pointer to the buffer is 272 * returned. 273 */ 274 char * 275 trim_whitespace(char *buf) 276 { 277 char *p = buf; 278 char *q = buf; 279 280 if (buf == NULL) 281 return (NULL); 282 283 while (*p && isspace(*p)) 284 ++p; 285 286 while ((*q = *p++) != 0) 287 ++q; 288 289 if (q != buf) { 290 while ((--q, isspace(*q)) != 0) 291 *q = '\0'; 292 } 293 294 return (buf); 295 } 296 297 /* 298 * randomize 299 * 300 * Randomize the contents of the specified buffer. 301 */ 302 void 303 randomize(char *data, unsigned len) 304 { 305 unsigned dwlen = len / 4; 306 unsigned remlen = len % 4; 307 unsigned tmp; 308 unsigned i; /*LINTED E_BAD_PTR_CAST_ALIGN*/ 309 unsigned *p = (unsigned *)data; 310 311 for (i = 0; i < dwlen; ++i) 312 *p++ = random(); 313 314 if (remlen) { 315 tmp = random(); 316 (void) memcpy(p, &tmp, remlen); 317 } 318 } 319 320 /* 321 * This is the hash mechanism used to encrypt passwords for commands like 322 * SamrSetUserInformation. It uses a 256 byte s-box. 323 */ 324 void 325 rand_hash( 326 unsigned char *data, 327 size_t datalen, 328 unsigned char *key, 329 size_t keylen) 330 { 331 unsigned char sbox[DEFAULT_SBOX_SIZE]; 332 unsigned char tmp; 333 unsigned char index_i = 0; 334 unsigned char index_j = 0; 335 unsigned char j = 0; 336 int i; 337 338 for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) 339 sbox[i] = (unsigned char)i; 340 341 for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) { 342 j += (sbox[i] + key[i % keylen]); 343 344 tmp = sbox[i]; 345 sbox[i] = sbox[j]; 346 sbox[j] = tmp; 347 } 348 349 for (i = 0; i < datalen; ++i) { 350 index_i++; 351 index_j += sbox[index_i]; 352 353 tmp = sbox[index_i]; 354 sbox[index_i] = sbox[index_j]; 355 sbox[index_j] = tmp; 356 357 tmp = sbox[index_i] + sbox[index_j]; 358 data[i] = data[i] ^ sbox[tmp]; 359 } 360 } 361 362 /* 363 * smb_chk_hostaccess 364 * 365 * Determine whether an access list grants rights to a particular host. 366 * We match on aliases of the hostname as well as on the canonical name. 367 * Names in the access list may be either hosts or netgroups; they're 368 * not distinguished syntactically. We check for hosts first because 369 * it's cheaper (just M*N strcmp()s), then try netgroups. 370 * 371 * Currently this function always returns B_TRUE for ipv6 until 372 * the underlying functions support ipv6 373 * 374 * Function returns: 375 * -1 for "all" 376 * 0 not found 377 * 1 found 378 * 379 */ 380 int 381 smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list) 382 { 383 int nentries; 384 char *gr; 385 char *lasts; 386 char *host; 387 int off; 388 int i; 389 int netgroup_match; 390 int response; 391 struct nd_hostservlist *clnames; 392 struct in_addr inaddr; 393 struct sockaddr_in sa; 394 struct netbuf buf; 395 struct netconfig *config; 396 397 if (ipaddr->a_family == AF_INET6) 398 return (B_TRUE); 399 400 inaddr.s_addr = ipaddr->a_ipv4; 401 402 /* 403 * If no access list - then it's "all" 404 */ 405 if (access_list == NULL || *access_list == '\0' || 406 strcmp(access_list, "*") == 0) 407 return (-1); 408 409 nentries = 0; 410 411 sa.sin_family = AF_INET; 412 sa.sin_port = 0; 413 sa.sin_addr = inaddr; 414 415 buf.len = buf.maxlen = sizeof (sa); 416 buf.buf = (char *)&sa; 417 418 config = getnetconfigent("tcp"); 419 if (config == NULL) 420 return (1); 421 422 if (__netdir_getbyaddr_nosrv(config, &clnames, &buf)) { 423 freenetconfigent(config); 424 return (0); 425 } 426 freenetconfigent(config); 427 428 for (gr = strtok_r(access_list, ":", &lasts); 429 gr != NULL; gr = strtok_r(NULL, ":", &lasts)) { 430 431 /* 432 * If the list name has a '-' prepended 433 * then a match of the following name 434 * implies failure instead of success. 435 */ 436 if (*gr == '-') { 437 response = 0; 438 gr++; 439 } else { 440 response = 1; 441 } 442 443 /* 444 * The following loops through all the 445 * client's aliases. Usually it's just one name. 446 */ 447 for (i = 0; i < clnames->h_cnt; i++) { 448 host = clnames->h_hostservs[i].h_host; 449 /* 450 * If the list name begins with a dot then 451 * do a domain name suffix comparison. 452 * A single dot matches any name with no 453 * suffix. 454 */ 455 if (*gr == '.') { 456 if (*(gr + 1) == '\0') { /* single dot */ 457 if (strchr(host, '.') == NULL) 458 return (response); 459 } else { 460 off = strlen(host) - strlen(gr); 461 if (off > 0 && 462 strcasecmp(host + off, gr) == 0) { 463 return (response); 464 } 465 } 466 } else { 467 468 /* 469 * If the list name begins with an at 470 * sign then do a network comparison. 471 */ 472 if (*gr == '@') { 473 if (smb_netmatch(&buf, gr + 1)) 474 return (response); 475 } else { 476 /* 477 * Just do a hostname match 478 */ 479 if (strcasecmp(gr, host) == 0) 480 return (response); 481 } 482 } 483 } 484 485 nentries++; 486 } 487 488 netgroup_match = smb_netgroup_match(clnames, access_list, nentries); 489 490 return (netgroup_match); 491 } 492 493 /* 494 * smb_make_mask 495 * 496 * Construct a mask for an IPv4 address using the @<dotted-ip>/<len> 497 * syntax or use the default mask for the IP address. 498 */ 499 static uint_t 500 smb_make_mask(char *maskstr, uint_t addr) 501 { 502 uint_t mask; 503 uint_t bits; 504 505 /* 506 * If the mask is specified explicitly then 507 * use that value, e.g. 508 * 509 * @109.104.56/28 510 * 511 * otherwise assume a mask from the zero octets 512 * in the least significant bits of the address, e.g. 513 * 514 * @109.104 or @109.104.0.0 515 */ 516 if (maskstr) { 517 bits = atoi(maskstr); 518 mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits) 519 : 0; 520 addr &= mask; 521 } else { 522 if ((addr & IN_CLASSA_HOST) == 0) 523 mask = IN_CLASSA_NET; 524 else if ((addr & IN_CLASSB_HOST) == 0) 525 mask = IN_CLASSB_NET; 526 else if ((addr & IN_CLASSC_HOST) == 0) 527 mask = IN_CLASSC_NET; 528 else 529 mask = IN_CLASSE_NET; 530 } 531 532 return (mask); 533 } 534 535 /* 536 * smb_netmatch 537 * 538 * Check to see if the address in the netbuf matches the "net" 539 * specified by name. The format of "name" can be: 540 * fully qualified domain name 541 * dotted IP address 542 * dotted IP address followed by '/<len>' 543 * See sharen_nfs(1M) for details. 544 */ 545 546 static boolean_t 547 smb_netmatch(struct netbuf *nb, char *name) 548 { 549 uint_t claddr; 550 struct netent n, *np; 551 char *mp, *p; 552 uint_t addr, mask; 553 int i; 554 char buff[256]; 555 556 /* 557 * Check if it's an IPv4 addr 558 */ 559 if (nb->len != sizeof (struct sockaddr_in)) 560 return (B_FALSE); 561 562 (void) memcpy(&claddr, 563 /* LINTED pointer alignment */ 564 &((struct sockaddr_in *)nb->buf)->sin_addr.s_addr, 565 sizeof (struct in_addr)); 566 claddr = ntohl(claddr); 567 568 mp = strchr(name, '/'); 569 if (mp) 570 *mp++ = '\0'; 571 572 if (isdigit(*name)) { 573 /* 574 * Convert a dotted IP address 575 * to an IP address. The conversion 576 * is not the same as that in inet_addr(). 577 */ 578 p = name; 579 addr = 0; 580 for (i = 0; i < 4; i++) { 581 addr |= atoi(p) << ((3-i) * 8); 582 p = strchr(p, '.'); 583 if (p == NULL) 584 break; 585 p++; 586 } 587 } else { 588 /* 589 * Turn the netname into 590 * an IP address. 591 */ 592 np = getnetbyname_r(name, &n, buff, sizeof (buff)); 593 if (np == NULL) { 594 return (B_FALSE); 595 } 596 addr = np->n_net; 597 } 598 599 mask = smb_make_mask(mp, addr); 600 return ((claddr & mask) == addr); 601 } 602 603 /* 604 * smb_netgroup_match 605 * 606 * Check whether any of the hostnames in clnames are 607 * members (or non-members) of the netgroups in glist. 608 * Since the innetgr lookup is rather expensive, the 609 * result is cached. The cached entry is valid only 610 * for VALID_TIME seconds. This works well because 611 * typically these lookups occur in clusters when 612 * a client is mounting. 613 * 614 * Note that this routine establishes a host membership 615 * in a list of netgroups - we've no idea just which 616 * netgroup in the list it is a member of. 617 * 618 * glist is a character array containing grc strings 619 * representing netgroup names (optionally prefixed 620 * with '-'). Each string is ended with '\0' and 621 * followed immediately by the next string. 622 */ 623 static boolean_t 624 smb_netgroup_match(struct nd_hostservlist *clnames, char *glist, int grc) 625 { 626 char **grl; 627 char *gr; 628 int nhosts = clnames->h_cnt; 629 char *host; 630 int i, j, n; 631 boolean_t response; 632 boolean_t belong = B_FALSE; 633 static char *domain = NULL; 634 635 if (domain == NULL) { 636 int ssize; 637 638 domain = malloc(SYS_NMLN); 639 if (domain == NULL) 640 return (B_FALSE); 641 642 ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN); 643 if (ssize > SYS_NMLN) { 644 free(domain); 645 domain = malloc(ssize); 646 if (domain == NULL) 647 return (B_FALSE); 648 ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize); 649 } 650 /* Check for error in syscall or NULL domain name */ 651 if (ssize <= 1) 652 return (B_FALSE); 653 } 654 655 grl = calloc(grc, sizeof (char *)); 656 if (grl == NULL) 657 return (B_FALSE); 658 659 for (i = 0, gr = glist; i < grc && !belong; ) { 660 /* 661 * If the netgroup name has a '-' prepended 662 * then a match of this name implies a failure 663 * instead of success. 664 */ 665 response = (*gr != '-') ? B_TRUE : B_FALSE; 666 667 /* 668 * Subsequent names with or without a '-' (but no mix) 669 * can be grouped together for a single check. 670 */ 671 for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) { 672 if ((response && *gr == '-') || 673 (!response && *gr != '-')) 674 break; 675 676 grl[n] = response ? gr : gr + 1; 677 } 678 679 /* 680 * Check the netgroup for each 681 * of the hosts names (usually just one). 682 */ 683 for (j = 0; j < nhosts && !belong; j++) { 684 host = clnames->h_hostservs[j].h_host; 685 if (__multi_innetgr(n, grl, 1, &host, 0, NULL, 686 1, &domain)) 687 belong = B_TRUE; 688 } 689 } 690 691 free(grl); 692 return (belong ? response : B_FALSE); 693 } 694 695 /* 696 * Resolve the ZFS dataset from a path. 697 * Returns, 698 * 0 = On success. 699 * -1 = Failure to open /etc/mnttab file or to get ZFS dataset. 700 */ 701 int 702 smb_getdataset(const char *path, char *dataset, size_t len) 703 { 704 char tmppath[MAXPATHLEN]; 705 char *cp; 706 FILE *fp; 707 struct mnttab mnttab; 708 struct mnttab mntpref; 709 int rc = -1; 710 711 if ((fp = fopen(MNTTAB, "r")) == NULL) 712 return (-1); 713 714 (void) memset(&mnttab, '\0', sizeof (mnttab)); 715 (void) strlcpy(tmppath, path, MAXPATHLEN); 716 cp = tmppath; 717 718 while (*cp != '\0') { 719 resetmnttab(fp); 720 (void) memset(&mntpref, '\0', sizeof (mntpref)); 721 mntpref.mnt_mountp = tmppath; 722 723 if (getmntany(fp, &mnttab, &mntpref) == 0) { 724 if (mnttab.mnt_fstype == NULL) 725 break; 726 727 if (strcmp(mnttab.mnt_fstype, "zfs") != 0) 728 break; 729 /* 730 * Ensure that there are no leading slashes 731 * (required for zfs_open). 732 */ 733 cp = mnttab.mnt_special; 734 cp += strspn(cp, "/"); 735 (void) strlcpy(dataset, cp, len); 736 rc = 0; 737 break; 738 } 739 740 if (strcmp(tmppath, "/") == 0) 741 break; 742 743 if ((cp = strrchr(tmppath, '/')) == NULL) 744 break; 745 746 /* 747 * The path has multiple components. 748 * Remove the last component and try again. 749 */ 750 *cp = '\0'; 751 if (tmppath[0] == '\0') 752 (void) strcpy(tmppath, "/"); 753 754 cp = tmppath; 755 } 756 757 (void) fclose(fp); 758 return (rc); 759 } 760 761 /* 762 * smb_dlopen 763 * 764 * Check to see if an interposer library exists. If it exists 765 * and reports a valid version number and key (UUID), return 766 * a handle to the library. Otherwise, return NULL. 767 */ 768 void * 769 smb_dlopen(void) 770 { 771 uuid_t uuid; 772 void *interposer_hdl; 773 typedef int (*smbex_versionfn_t)(smbex_version_t *); 774 smbex_versionfn_t getversion; 775 smbex_version_t *version; 776 777 bzero(&uuid, sizeof (uuid_t)); 778 if (uuid_parse(SMBEX_KEY, uuid) < 0) 779 return (NULL); 780 781 interposer_hdl = dlopen(SMB_LIB_ALT, RTLD_NOW | RTLD_LOCAL); 782 if (interposer_hdl == NULL) 783 return (NULL); 784 785 bzero(&getversion, sizeof (smbex_versionfn_t)); 786 getversion = (smbex_versionfn_t)dlsym(interposer_hdl, 787 "smbex_get_version"); 788 if ((getversion == NULL) || 789 (version = malloc(sizeof (smbex_version_t))) == NULL) { 790 (void) dlclose(interposer_hdl); 791 return (NULL); 792 } 793 bzero(version, sizeof (smbex_version_t)); 794 795 if ((getversion(version) != 0) || 796 (version->v_version != SMBEX_VERSION) || 797 (uuid_compare(version->v_uuid, uuid) != 0)) { 798 free(version); 799 (void) dlclose(interposer_hdl); 800 return (NULL); 801 } 802 803 free(version); 804 return (interposer_hdl); 805 } 806 807 /* 808 * smb_dlclose 809 * 810 * Closes handle to the interposed library. 811 */ 812 void 813 smb_dlclose(void *handle) 814 { 815 if (handle) 816 (void) dlclose(handle); 817 } 818 819 /* 820 * Returns the hostname given the IP address. Wrapper for getnameinfo. 821 */ 822 int 823 smb_getnameinfo(smb_inaddr_t *ip, char *hostname, int hostlen, int flags) 824 { 825 socklen_t salen; 826 struct sockaddr_in6 sin6; 827 struct sockaddr_in sin; 828 void *sp; 829 830 if (ip->a_family == AF_INET) { 831 salen = sizeof (struct sockaddr_in); 832 sin.sin_family = ip->a_family; 833 sin.sin_port = 0; 834 sin.sin_addr.s_addr = ip->a_ipv4; 835 sp = &sin; 836 } else { 837 salen = sizeof (struct sockaddr_in6); 838 sin6.sin6_family = ip->a_family; 839 sin6.sin6_port = 0; 840 (void) memcpy(&sin6.sin6_addr.s6_addr, &ip->a_ipv6, 841 sizeof (sin6.sin6_addr.s6_addr)); 842 sp = &sin6; 843 } 844 return (getnameinfo((struct sockaddr *)sp, salen, 845 hostname, hostlen, NULL, 0, flags)); 846 } 847