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