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 /* 24 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 25 * Use is subject to license terms. 26 */ 27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ 28 /* All Rights Reserved */ 29 /* 30 * Portions of this source code were derived from Berkeley 31 * 4.3 BSD under license from the Regents of the University of 32 * California. 33 */ 34 /* 35 * ==== hack-attack: possibly MT-safe but definitely not MT-hot. 36 * ==== turn this into a real switch frontend and backends 37 * 38 * Well, at least the API doesn't involve pointers-to-static. 39 */ 40 41 #pragma ident "%Z%%M% %I% %E% SMI" 42 43 /* 44 * netname utility routines convert from netnames to unix names (uid, gid) 45 * 46 * This module is operating system dependent! 47 * What we define here will work with any unix system that has adopted 48 * the Sun NIS domain architecture. 49 */ 50 51 #undef NIS 52 #include "mt.h" 53 #include "rpc_mt.h" 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <sys/types.h> 57 #include <ctype.h> 58 #include <grp.h> 59 #include <pwd.h> 60 #include <string.h> 61 #include <syslog.h> 62 #include <sys/param.h> 63 #include <nsswitch.h> 64 #include <rpc/rpc.h> 65 #include <rpcsvc/nis.h> 66 #include <rpcsvc/ypclnt.h> 67 #include "nsl_stdio_prv.h" 68 #include <nss_dbdefs.h> 69 70 static const char OPSYS[] = "unix"; 71 static const char NETIDFILE[] = "/etc/netid"; 72 static const char NETID[] = "netid.byname"; 73 static const char PKTABLE[] = "cred.org_dir"; 74 #define PKTABLE_LEN 12 75 #define OPSYS_LEN 4 76 77 #ifndef NGROUPS 78 #define NGROUPS 16 79 #endif 80 81 extern int _getgroupsbymember(const char *, gid_t[], int, int); 82 83 /* 84 * the value for NOBODY_UID is set by the SVID. The following define also 85 * appears in netname.c 86 */ 87 88 #define NOBODY_UID 60001 89 90 /* 91 * default publickey policy: 92 * publickey: nis [NOTFOUND = return] files 93 */ 94 95 96 /* NSW_NOTSUCCESS NSW_NOTFOUND NSW_UNAVAIL NSW_TRYAGAIN */ 97 #define DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE} 98 99 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL}, 100 lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files}; 101 static struct __nsw_switchconfig publickey_default = 102 {0, "publickey", 2, &lookup_nis}; 103 104 static mutex_t serialize_netname_r = DEFAULTMUTEX; 105 106 struct netid_userdata { 107 uid_t *uidp; 108 gid_t *gidp; 109 int *gidlenp; 110 gid_t *gidlist; 111 }; 112 113 static int 114 parse_uid(char *s, struct netid_userdata *argp) 115 { 116 uid_t u; 117 118 if (!s || !isdigit(*s)) { 119 syslog(LOG_ERR, 120 "netname2user: expecting uid '%s'", s); 121 return (__NSW_NOTFOUND); /* xxx need a better error */ 122 } 123 124 /* Fetch the uid */ 125 u = (uid_t)(atoi(s)); 126 127 if (u == 0) { 128 syslog(LOG_ERR, "netname2user: should not have uid 0"); 129 return (__NSW_NOTFOUND); 130 } 131 *(argp->uidp) = u; 132 return (__NSW_SUCCESS); 133 } 134 135 136 /* parse a comma separated gid list */ 137 static int 138 parse_gidlist(char *p, struct netid_userdata *argp) 139 { 140 int len; 141 gid_t g; 142 143 if (!p || (!isdigit(*p))) { 144 syslog(LOG_ERR, 145 "netname2user: missing group id list in '%s'.", 146 p); 147 return (__NSW_NOTFOUND); 148 } 149 150 g = (gid_t)(atoi(p)); 151 *(argp->gidp) = g; 152 153 len = 0; 154 while (p = strchr(p, ',')) 155 argp->gidlist[len++] = (gid_t)atoi(++p); 156 *(argp->gidlenp) = len; 157 return (__NSW_SUCCESS); 158 } 159 160 161 /* 162 * parse_netid_str() 163 * 164 * Parse uid and group information from the passed string. 165 * 166 * The format of the string passed is 167 * uid:gid,grp,grp, ... 168 * 169 */ 170 static int 171 parse_netid_str(char *s, struct netid_userdata *argp) 172 { 173 char *p; 174 int err; 175 176 /* get uid */ 177 err = parse_uid(s, argp); 178 if (err != __NSW_SUCCESS) 179 return (err); 180 181 /* Now get the group list */ 182 p = strchr(s, ':'); 183 if (!p) { 184 syslog(LOG_ERR, 185 "netname2user: missing group id list in '%s'", s); 186 return (__NSW_NOTFOUND); 187 } 188 ++p; /* skip ':' */ 189 err = parse_gidlist(p, argp); 190 return (err); 191 } 192 193 static int 194 parse_uid_gidlist(char *ustr, char *gstr, struct netid_userdata *argp) 195 { 196 int err; 197 198 /* get uid */ 199 err = parse_uid(ustr, argp); 200 if (err != __NSW_SUCCESS) 201 return (err); 202 203 /* Now get the group list */ 204 return (parse_gidlist(gstr, argp)); 205 } 206 207 208 /* 209 * netname2user_files() 210 * 211 * This routine fetches the netid information from the "files" nameservice. 212 * ie /etc/netid. 213 */ 214 static int 215 netname2user_files(int *err, char *netname, struct netid_userdata *argp) 216 { 217 char buf[512]; /* one line from the file */ 218 char *name; 219 char *value; 220 char *res; 221 __NSL_FILE *fd; 222 223 fd = __nsl_fopen(NETIDFILE, "r"); 224 if (fd == (__NSL_FILE *)0) { 225 *err = __NSW_UNAVAIL; 226 return (0); 227 } 228 /* 229 * for each line in the file parse it appropriately 230 * file format is : 231 * netid uid:grp,grp,grp # for users 232 * netid 0:hostname # for hosts 233 */ 234 while (!__nsl_feof(fd)) { 235 res = __nsl_fgets(buf, 512, fd); 236 if (res == NULL) 237 break; 238 239 /* Skip comments and blank lines */ 240 if ((*res == '#') || (*res == '\n')) 241 continue; 242 243 name = &(buf[0]); 244 while (isspace(*name)) 245 name++; 246 if (*name == '\0') /* blank line continue */ 247 continue; 248 value = name; /* will contain the value eventually */ 249 while (!isspace(*value)) 250 value++; 251 if (*value == '\0') { 252 syslog(LOG_WARNING, 253 "netname2user: badly formatted line in %s.", 254 NETIDFILE); 255 continue; 256 } 257 *value++ = '\0'; /* nul terminate the name */ 258 259 if (strcasecmp(name, netname) == 0) { 260 (void) __nsl_fclose(fd); 261 while (isspace(*value)) 262 value++; 263 *err = parse_netid_str(value, argp); 264 return (*err == __NSW_SUCCESS); 265 } 266 } 267 (void) __nsl_fclose(fd); 268 *err = __NSW_NOTFOUND; 269 return (0); 270 } 271 272 /* 273 * netname2user_nis() 274 * 275 * This function reads the netid from the NIS (YP) nameservice. 276 */ 277 static int 278 netname2user_nis(int *err, char *netname, struct netid_userdata *argp) 279 { 280 char *domain; 281 int yperr; 282 char *lookup; 283 int len; 284 285 domain = strchr(netname, '@'); 286 if (!domain) { 287 *err = __NSW_UNAVAIL; 288 return (0); 289 } 290 291 /* Point past the '@' character */ 292 domain++; 293 lookup = NULL; 294 yperr = yp_match(domain, (char *)NETID, netname, strlen(netname), 295 &lookup, &len); 296 switch (yperr) { 297 case 0: 298 break; /* the successful case */ 299 300 default : 301 /* 302 * XXX not sure about yp_match semantics. 303 * should err be set to NOTFOUND here? 304 */ 305 *err = __NSW_UNAVAIL; 306 return (0); 307 } 308 if (lookup) { 309 lookup[len] = '\0'; 310 *err = parse_netid_str(lookup, argp); 311 free(lookup); 312 return (*err == __NSW_SUCCESS); 313 } 314 *err = __NSW_NOTFOUND; 315 return (0); 316 } 317 318 /* 319 * Obtain user information (uid, gidlist) from nisplus. 320 * What we're trying to do here is to map a netname into 321 * local unix information (uid, gids), relevant in 322 * the *local* domain. 323 * 324 * cname auth_type auth_name public private 325 * ---------------------------------------------------------- 326 * nisname DES netname pubkey prikey 327 * nisname LOCAL uid gidlist 328 * 329 * 1. Find out which 'home' domain to look for user's DES entry. 330 * This is gotten from the domain part of the netname. 331 * 2. Get the nisplus principal name from the DES entry in the cred 332 * table of user's home domain. 333 * 3. Use the nisplus principal name and search in the cred table of 334 * the *local* directory for the LOCAL entry. 335 * 336 * Note that we need this translation of netname to <uid,gidlist> to be 337 * secure, so we *must* use authenticated connections. 338 */ 339 static int 340 netname2user_nisplus(int *err, char *netname, struct netid_userdata *argp) 341 { 342 char *domain; 343 nis_result *res; 344 char sname[NIS_MAXNAMELEN+1]; /* search criteria + table name */ 345 char principal[NIS_MAXNAMELEN+1]; 346 int len; 347 348 /* 1. Get home domain of user. */ 349 domain = strchr(netname, '@'); 350 if (!domain) { 351 *err = __NSW_UNAVAIL; 352 return (0); 353 } 354 domain++; /* skip '@' */ 355 356 357 /* 2. Get user's nisplus principal name. */ 358 if ((strlen(netname)+strlen(domain)+PKTABLE_LEN+32) > 359 (size_t)NIS_MAXNAMELEN) { 360 *err = __NSW_UNAVAIL; 361 return (0); 362 } 363 (void) snprintf(sname, sizeof (sname), 364 "[auth_name=\"%s\",auth_type=DES],%s.%s", 365 netname, PKTABLE, domain); 366 if (sname[strlen(sname) - 1] != '.') 367 (void) strcat(sname, "."); 368 369 /* must use authenticated call here */ 370 /* XXX but we cant, for now. XXX */ 371 res = nis_list(sname, USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH, 372 NULL, NULL); 373 switch (res->status) { 374 case NIS_SUCCESS: 375 case NIS_S_SUCCESS: 376 break; /* go and do something useful */ 377 case NIS_NOTFOUND: 378 case NIS_PARTIAL: 379 case NIS_NOSUCHNAME: 380 case NIS_NOSUCHTABLE: 381 *err = __NSW_NOTFOUND; 382 nis_freeresult(res); 383 return (0); 384 case NIS_S_NOTFOUND: 385 case NIS_TRYAGAIN: 386 *err = __NSW_TRYAGAIN; 387 syslog(LOG_ERR, 388 "netname2user: (nis+ lookup): %s\n", 389 nis_sperrno(res->status)); 390 nis_freeresult(res); 391 return (0); 392 default: 393 *err = __NSW_UNAVAIL; 394 syslog(LOG_ERR, "netname2user: (nis+ lookup): %s\n", 395 nis_sperrno(res->status)); 396 nis_freeresult(res); 397 return (0); 398 } 399 400 if (res->objects.objects_len > 1) { 401 /* 402 * A netname belonging to more than one principal? 403 * Something wrong with cred table. should be unique. 404 * Warn user and continue. 405 */ 406 syslog(LOG_ALERT, 407 "netname2user: DES entry for %s in \ 408 directory %s not unique", 409 netname, domain); 410 } 411 412 len = ENTRY_LEN(res->objects.objects_val, 0); 413 (void) strncpy(principal, ENTRY_VAL(res->objects.objects_val, 0), len); 414 principal[len] = '\0'; 415 nis_freeresult(res); 416 417 if (principal[0] == '\0') { 418 *err = __NSW_UNAVAIL; 419 return (0); 420 } 421 422 /* 423 * 3. Use principal name to look up uid/gid information in 424 * LOCAL entry in **local** cred table. 425 */ 426 domain = nis_local_directory(); 427 if ((strlen(principal)+strlen(domain)+PKTABLE_LEN+30) > 428 (size_t)NIS_MAXNAMELEN) { 429 *err = __NSW_UNAVAIL; 430 syslog(LOG_ERR, "netname2user: principal name '%s' too long", 431 principal); 432 return (0); 433 } 434 (void) snprintf(sname, sizeof (sname), 435 "[cname=\"%s\",auth_type=LOCAL],%s.%s", 436 principal, PKTABLE, domain); 437 if (sname[strlen(sname) - 1] != '.') 438 (void) strcat(sname, "."); 439 440 /* must use authenticated call here */ 441 /* XXX but we cant, for now. XXX */ 442 res = nis_list(sname, USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH, 443 NULL, NULL); 444 switch (res->status) { 445 case NIS_NOTFOUND: 446 case NIS_PARTIAL: 447 case NIS_NOSUCHNAME: 448 case NIS_NOSUCHTABLE: 449 *err = __NSW_NOTFOUND; 450 nis_freeresult(res); 451 return (0); 452 case NIS_S_NOTFOUND: 453 case NIS_TRYAGAIN: 454 *err = __NSW_TRYAGAIN; 455 syslog(LOG_ERR, 456 "netname2user: (nis+ lookup): %s\n", 457 nis_sperrno(res->status)); 458 nis_freeresult(res); 459 return (0); 460 case NIS_SUCCESS: 461 case NIS_S_SUCCESS: 462 break; /* go and do something useful */ 463 default: 464 *err = __NSW_UNAVAIL; 465 syslog(LOG_ERR, "netname2user: (nis+ lookup): %s\n", 466 nis_sperrno(res->status)); 467 nis_freeresult(res); 468 return (0); 469 } 470 471 if (res->objects.objects_len > 1) { 472 /* 473 * A principal can have more than one LOCAL entry? 474 * Something wrong with cred table. 475 * Warn user and continue. 476 */ 477 syslog(LOG_ALERT, 478 "netname2user: LOCAL entry for %s in\ 479 directory %s not unique", 480 netname, domain); 481 } 482 /* nisname LOCAL uid grp,grp,grp */ 483 *err = parse_uid_gidlist(ENTRY_VAL(res->objects.objects_val, 2), 484 /* uid */ 485 ENTRY_VAL(res->objects.objects_val, 3), /* gids */ 486 argp); 487 nis_freeresult(res); 488 return (*err == __NSW_SUCCESS); 489 } 490 491 /* 492 * Build the uid and gid from the netname for users in LDAP. 493 * There is no netid container in LDAP. For this we build 494 * the netname to user data dynamically from the passwd and 495 * group data. This works only for users in a single domain. 496 * This function is an interim solution until we support a 497 * netid container in LDAP which enables us to do netname2user 498 * resolution for multiple domains. 499 */ 500 static int 501 netname2user_ldap(int *err, char *netname, struct netid_userdata *argp) 502 { 503 char buf[NSS_LINELEN_PASSWD]; 504 char *p2, *lasts; 505 struct passwd pw; 506 uid_t uidnu; 507 int ngroups = 0; 508 int count; 509 char pwbuf[NSS_LINELEN_PASSWD]; 510 gid_t groups[NGROUPS_MAX]; 511 512 if (strlcpy(buf, netname, NSS_LINELEN_PASSWD) >= NSS_LINELEN_PASSWD) { 513 *err = __NSW_UNAVAIL; 514 return (0); 515 } 516 517 /* get the uid from the netname */ 518 if (strtok_r(buf, ".", &lasts) == NULL) { 519 *err = __NSW_UNAVAIL; 520 return (0); 521 } 522 if ((p2 = strtok_r(NULL, "@", &lasts)) == NULL) { 523 *err = __NSW_UNAVAIL; 524 return (0); 525 } 526 uidnu = atoi(p2); 527 528 /* 529 * check out the primary group and crosscheck the uid 530 * with the passwd data 531 */ 532 if ((getpwuid_r(uidnu, &pw, pwbuf, sizeof (pwbuf))) == NULL) { 533 *err = __NSW_UNAVAIL; 534 return (0); 535 } 536 537 *(argp->uidp) = pw.pw_uid; 538 *(argp->gidp) = pw.pw_gid; 539 540 /* search through all groups for membership */ 541 542 groups[0] = pw.pw_gid; 543 544 ngroups = _getgroupsbymember(pw.pw_name, groups, NGROUPS_MAX, 545 (pw.pw_gid >= 0) ? 1 : 0); 546 547 if (ngroups < 0) { 548 *err = __NSW_UNAVAIL; 549 return (0); 550 } 551 552 *(argp->gidlenp) = ngroups; 553 554 for (count = 0; count < ngroups; count++) { 555 (argp->gidlist[count]) = groups[count]; 556 } 557 558 *err = __NSW_SUCCESS; 559 return (1); 560 561 } 562 563 /* 564 * Convert network-name into unix credential 565 */ 566 int 567 netname2user(const char netname[MAXNETNAMELEN + 1], uid_t *uidp, gid_t *gidp, 568 int *gidlenp, gid_t *gidlist) 569 { 570 struct __nsw_switchconfig *conf; 571 struct __nsw_lookup *look; 572 enum __nsw_parse_err perr; 573 int needfree = 1, res; 574 struct netid_userdata argp; 575 int err; 576 577 /* 578 * Take care of the special case of nobody. Compare the netname 579 * to the string "nobody". If they are equal, return the SVID 580 * standard value for nobody. 581 */ 582 583 if (strcmp(netname, "nobody") == 0) { 584 *uidp = NOBODY_UID; 585 *gidp = NOBODY_UID; 586 *gidlenp = 0; 587 return (1); 588 } 589 590 /* 591 * First we do some generic sanity checks on the name we were 592 * passed. This lets us assume they are correct in the backends. 593 * 594 * NOTE: this code only recognizes names of the form : 595 * unix.UID@domainname 596 */ 597 if (strncmp(netname, OPSYS, OPSYS_LEN) != 0) 598 return (0); 599 if (!isdigit(netname[OPSYS_LEN+1])) /* check for uid string */ 600 return (0); 601 602 argp.uidp = uidp; 603 argp.gidp = gidp; 604 argp.gidlenp = gidlenp; 605 argp.gidlist = gidlist; 606 (void) mutex_lock(&serialize_netname_r); 607 608 conf = __nsw_getconfig("publickey", &perr); 609 if (!conf) { 610 conf = &publickey_default; 611 needfree = 0; 612 } else 613 needfree = 1; /* free the config structure */ 614 615 for (look = conf->lookups; look; look = look->next) { 616 if (strcmp(look->service_name, "nisplus") == 0) 617 res = netname2user_nisplus(&err, 618 (char *)netname, &argp); 619 else if (strcmp(look->service_name, "nis") == 0) 620 res = netname2user_nis(&err, (char *)netname, &argp); 621 else if (strcmp(look->service_name, "files") == 0) 622 res = netname2user_files(&err, (char *)netname, &argp); 623 else if (strcmp(look->service_name, "ldap") == 0) 624 res = netname2user_ldap(&err, (char *)netname, &argp); 625 else { 626 syslog(LOG_INFO, 627 "netname2user: unknown nameservice for publickey info '%s'\n", 628 look->service_name); 629 err = __NSW_UNAVAIL; 630 } 631 switch (look->actions[err]) { 632 case __NSW_CONTINUE : 633 break; 634 case __NSW_RETURN : 635 if (needfree) 636 __nsw_freeconfig(conf); 637 (void) mutex_unlock(&serialize_netname_r); 638 return (res); 639 default : 640 syslog(LOG_ERR, 641 "netname2user: Unknown action for nameservice '%s'", 642 look->service_name); 643 } 644 } 645 if (needfree) 646 __nsw_freeconfig(conf); 647 (void) mutex_unlock(&serialize_netname_r); 648 return (0); 649 } 650 651 /* 652 * Convert network-name to hostname (fully qualified) 653 * NOTE: this code only recognizes names of the form : 654 * unix.HOST@domainname 655 * 656 * This is very simple. Since the netname is of the form: 657 * unix.host@domainname 658 * We just construct the hostname using information from the domainname. 659 */ 660 int 661 netname2host(const char netname[MAXNETNAMELEN + 1], char *hostname, 662 const int hostlen) 663 { 664 char *p, *domainname; 665 int len, dlen; 666 667 if (!netname) { 668 syslog(LOG_ERR, "netname2host: null netname"); 669 goto bad_exit; 670 } 671 672 if (strncmp(netname, OPSYS, OPSYS_LEN) != 0) 673 goto bad_netname; 674 p = (char *)netname + OPSYS_LEN; /* skip OPSYS part */ 675 if (*p != '.') 676 goto bad_netname; 677 ++p; /* skip '.' */ 678 679 domainname = strchr(p, '@'); /* get domain name */ 680 if (domainname == 0) 681 goto bad_netname; 682 683 len = domainname - p; /* host sits between '.' and '@' */ 684 domainname++; /* skip '@' sign */ 685 686 if (len <= 0) 687 goto bad_netname; 688 689 if (hostlen < len) { 690 syslog(LOG_ERR, 691 "netname2host: insufficient space for hostname"); 692 goto bad_exit; 693 } 694 695 if (isdigit(*p)) /* don't want uid here */ 696 goto bad_netname; 697 698 if (*p == '\0') /* check for null hostname */ 699 goto bad_netname; 700 701 (void) strncpy(hostname, p, len); 702 703 /* make into fully qualified hostname by concatenating domain part */ 704 dlen = strlen(domainname); 705 if (hostlen < (len + dlen + 2)) { 706 syslog(LOG_ERR, 707 "netname2host: insufficient space for hostname"); 708 goto bad_exit; 709 } 710 711 hostname[len] = '.'; 712 (void) strncpy(hostname+len+1, domainname, dlen); 713 hostname[len+dlen+1] = '\0'; 714 715 return (1); 716 717 bad_netname: 718 syslog(LOG_ERR, "netname2host: invalid host netname %s", netname); 719 720 bad_exit: 721 hostname[0] = '\0'; 722 return (0); 723 } 724