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