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 #define OPSYS_LEN 4 72 73 extern int _getgroupsbymember(const char *, gid_t[], int, int); 74 75 /* 76 * the value for NOBODY_UID is set by the SVID. The following define also 77 * appears in netname.c 78 */ 79 80 #define NOBODY_UID 60001 81 82 /* 83 * default publickey policy: 84 * publickey: nis [NOTFOUND = return] files 85 */ 86 87 88 /* NSW_NOTSUCCESS NSW_NOTFOUND NSW_UNAVAIL NSW_TRYAGAIN */ 89 #define DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE} 90 91 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL}, 92 lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files}; 93 static struct __nsw_switchconfig publickey_default = 94 {0, "publickey", 2, &lookup_nis}; 95 96 static mutex_t serialize_netname_r = DEFAULTMUTEX; 97 98 struct netid_userdata { 99 uid_t *uidp; 100 gid_t *gidp; 101 int *gidlenp; 102 gid_t *gidlist; 103 }; 104 105 static int 106 parse_uid(char *s, struct netid_userdata *argp) 107 { 108 uid_t u; 109 110 if (!s || !isdigit(*s)) { 111 syslog(LOG_ERR, 112 "netname2user: expecting uid '%s'", s); 113 return (__NSW_NOTFOUND); /* xxx need a better error */ 114 } 115 116 /* Fetch the uid */ 117 u = (uid_t)(atoi(s)); 118 119 if (u == 0) { 120 syslog(LOG_ERR, "netname2user: should not have uid 0"); 121 return (__NSW_NOTFOUND); 122 } 123 *(argp->uidp) = u; 124 return (__NSW_SUCCESS); 125 } 126 127 128 /* parse a comma separated gid list */ 129 static int 130 parse_gidlist(char *p, struct netid_userdata *argp) 131 { 132 int len; 133 gid_t g; 134 135 if (!p || (!isdigit(*p))) { 136 syslog(LOG_ERR, 137 "netname2user: missing group id list in '%s'.", 138 p); 139 return (__NSW_NOTFOUND); 140 } 141 142 g = (gid_t)(atoi(p)); 143 *(argp->gidp) = g; 144 145 len = 0; 146 while (p = strchr(p, ',')) 147 argp->gidlist[len++] = (gid_t)atoi(++p); 148 *(argp->gidlenp) = len; 149 return (__NSW_SUCCESS); 150 } 151 152 153 /* 154 * parse_netid_str() 155 * 156 * Parse uid and group information from the passed string. 157 * 158 * The format of the string passed is 159 * uid:gid,grp,grp, ... 160 * 161 */ 162 static int 163 parse_netid_str(char *s, struct netid_userdata *argp) 164 { 165 char *p; 166 int err; 167 168 /* get uid */ 169 err = parse_uid(s, argp); 170 if (err != __NSW_SUCCESS) 171 return (err); 172 173 /* Now get the group list */ 174 p = strchr(s, ':'); 175 if (!p) { 176 syslog(LOG_ERR, 177 "netname2user: missing group id list in '%s'", s); 178 return (__NSW_NOTFOUND); 179 } 180 ++p; /* skip ':' */ 181 err = parse_gidlist(p, argp); 182 return (err); 183 } 184 185 /* 186 * netname2user_files() 187 * 188 * This routine fetches the netid information from the "files" nameservice. 189 * ie /etc/netid. 190 */ 191 static int 192 netname2user_files(int *err, char *netname, struct netid_userdata *argp) 193 { 194 char buf[512]; /* one line from the file */ 195 char *name; 196 char *value; 197 char *res; 198 FILE *fd; 199 200 fd = fopen(NETIDFILE, "rF"); 201 if (fd == NULL) { 202 *err = __NSW_UNAVAIL; 203 return (0); 204 } 205 /* 206 * for each line in the file parse it appropriately 207 * file format is : 208 * netid uid:grp,grp,grp # for users 209 * netid 0:hostname # for hosts 210 */ 211 while (!feof(fd)) { 212 res = fgets(buf, 512, fd); 213 if (res == NULL) 214 break; 215 216 /* Skip comments and blank lines */ 217 if ((*res == '#') || (*res == '\n')) 218 continue; 219 220 name = &(buf[0]); 221 while (isspace(*name)) 222 name++; 223 if (*name == '\0') /* blank line continue */ 224 continue; 225 value = name; /* will contain the value eventually */ 226 while (!isspace(*value)) 227 value++; 228 if (*value == '\0') { 229 syslog(LOG_WARNING, 230 "netname2user: badly formatted line in %s.", 231 NETIDFILE); 232 continue; 233 } 234 *value++ = '\0'; /* nul terminate the name */ 235 236 if (strcasecmp(name, netname) == 0) { 237 (void) fclose(fd); 238 while (isspace(*value)) 239 value++; 240 *err = parse_netid_str(value, argp); 241 return (*err == __NSW_SUCCESS); 242 } 243 } 244 (void) fclose(fd); 245 *err = __NSW_NOTFOUND; 246 return (0); 247 } 248 249 /* 250 * netname2user_nis() 251 * 252 * This function reads the netid from the NIS (YP) nameservice. 253 */ 254 static int 255 netname2user_nis(int *err, char *netname, struct netid_userdata *argp) 256 { 257 char *domain; 258 int yperr; 259 char *lookup; 260 int len; 261 262 domain = strchr(netname, '@'); 263 if (!domain) { 264 *err = __NSW_UNAVAIL; 265 return (0); 266 } 267 268 /* Point past the '@' character */ 269 domain++; 270 lookup = NULL; 271 yperr = yp_match(domain, (char *)NETID, netname, strlen(netname), 272 &lookup, &len); 273 switch (yperr) { 274 case 0: 275 break; /* the successful case */ 276 277 default : 278 /* 279 * XXX not sure about yp_match semantics. 280 * should err be set to NOTFOUND here? 281 */ 282 *err = __NSW_UNAVAIL; 283 return (0); 284 } 285 if (lookup) { 286 lookup[len] = '\0'; 287 *err = parse_netid_str(lookup, argp); 288 free(lookup); 289 return (*err == __NSW_SUCCESS); 290 } 291 *err = __NSW_NOTFOUND; 292 return (0); 293 } 294 295 /* 296 * Build the uid and gid from the netname for users in LDAP. 297 * There is no netid container in LDAP. For this we build 298 * the netname to user data dynamically from the passwd and 299 * group data. This works only for users in a single domain. 300 * This function is an interim solution until we support a 301 * netid container in LDAP which enables us to do netname2user 302 * resolution for multiple domains. 303 */ 304 static int 305 netname2user_ldap(int *err, char *netname, struct netid_userdata *argp) 306 { 307 char buf[NSS_LINELEN_PASSWD]; 308 char *p2, *lasts; 309 struct passwd pw; 310 uid_t uidnu; 311 int ngroups = 0; 312 int count; 313 char pwbuf[NSS_LINELEN_PASSWD]; 314 int maxgrp = sysconf(_SC_NGROUPS_MAX); 315 gid_t *groups = alloca(maxgrp * sizeof (gid_t)); 316 317 if (strlcpy(buf, netname, NSS_LINELEN_PASSWD) >= NSS_LINELEN_PASSWD) { 318 *err = __NSW_UNAVAIL; 319 return (0); 320 } 321 322 /* get the uid from the netname */ 323 if (strtok_r(buf, ".", &lasts) == NULL) { 324 *err = __NSW_UNAVAIL; 325 return (0); 326 } 327 if ((p2 = strtok_r(NULL, "@", &lasts)) == NULL) { 328 *err = __NSW_UNAVAIL; 329 return (0); 330 } 331 uidnu = atoi(p2); 332 333 /* 334 * check out the primary group and crosscheck the uid 335 * with the passwd data 336 */ 337 if ((getpwuid_r(uidnu, &pw, pwbuf, sizeof (pwbuf))) == NULL) { 338 *err = __NSW_UNAVAIL; 339 return (0); 340 } 341 342 *(argp->uidp) = pw.pw_uid; 343 *(argp->gidp) = pw.pw_gid; 344 345 /* search through all groups for membership */ 346 347 groups[0] = pw.pw_gid; 348 349 ngroups = _getgroupsbymember(pw.pw_name, groups, maxgrp, 350 (pw.pw_gid <= MAXUID) ? 1 : 0); 351 352 if (ngroups < 0) { 353 *err = __NSW_UNAVAIL; 354 return (0); 355 } 356 357 *(argp->gidlenp) = ngroups; 358 359 for (count = 0; count < ngroups; count++) { 360 (argp->gidlist[count]) = groups[count]; 361 } 362 363 *err = __NSW_SUCCESS; 364 return (1); 365 366 } 367 368 /* 369 * Convert network-name into unix credential 370 */ 371 int 372 netname2user(const char netname[MAXNETNAMELEN + 1], uid_t *uidp, gid_t *gidp, 373 int *gidlenp, gid_t *gidlist) 374 { 375 struct __nsw_switchconfig *conf; 376 struct __nsw_lookup *look; 377 enum __nsw_parse_err perr; 378 int needfree = 1, res; 379 struct netid_userdata argp; 380 int err; 381 382 /* 383 * Take care of the special case of nobody. Compare the netname 384 * to the string "nobody". If they are equal, return the SVID 385 * standard value for nobody. 386 */ 387 388 if (strcmp(netname, "nobody") == 0) { 389 *uidp = NOBODY_UID; 390 *gidp = NOBODY_UID; 391 *gidlenp = 0; 392 return (1); 393 } 394 395 /* 396 * First we do some generic sanity checks on the name we were 397 * passed. This lets us assume they are correct in the backends. 398 * 399 * NOTE: this code only recognizes names of the form : 400 * unix.UID@domainname 401 */ 402 if (strncmp(netname, OPSYS, OPSYS_LEN) != 0) 403 return (0); 404 if (!isdigit(netname[OPSYS_LEN+1])) /* check for uid string */ 405 return (0); 406 407 argp.uidp = uidp; 408 argp.gidp = gidp; 409 argp.gidlenp = gidlenp; 410 argp.gidlist = gidlist; 411 (void) mutex_lock(&serialize_netname_r); 412 413 conf = __nsw_getconfig("publickey", &perr); 414 if (!conf) { 415 conf = &publickey_default; 416 needfree = 0; 417 } else 418 needfree = 1; /* free the config structure */ 419 420 for (look = conf->lookups; look; look = look->next) { 421 if (strcmp(look->service_name, "nis") == 0) 422 res = netname2user_nis(&err, (char *)netname, &argp); 423 else if (strcmp(look->service_name, "files") == 0) 424 res = netname2user_files(&err, (char *)netname, &argp); 425 else if (strcmp(look->service_name, "ldap") == 0) 426 res = netname2user_ldap(&err, (char *)netname, &argp); 427 else { 428 syslog(LOG_INFO, 429 "netname2user: unknown nameservice for publickey" 430 "info '%s'\n", look->service_name); 431 err = __NSW_UNAVAIL; 432 } 433 switch (look->actions[err]) { 434 case __NSW_CONTINUE : 435 break; 436 case __NSW_RETURN : 437 if (needfree) 438 (void) __nsw_freeconfig(conf); 439 (void) mutex_unlock(&serialize_netname_r); 440 return (res); 441 default : 442 syslog(LOG_ERR, 443 "netname2user: Unknown action for " 444 "nameservice '%s'", look->service_name); 445 } 446 } 447 if (needfree) 448 (void) __nsw_freeconfig(conf); 449 (void) mutex_unlock(&serialize_netname_r); 450 return (0); 451 } 452 453 /* 454 * Convert network-name to hostname (fully qualified) 455 * NOTE: this code only recognizes names of the form : 456 * unix.HOST@domainname 457 * 458 * This is very simple. Since the netname is of the form: 459 * unix.host@domainname 460 * We just construct the hostname using information from the domainname. 461 */ 462 int 463 netname2host(const char netname[MAXNETNAMELEN + 1], char *hostname, 464 const int hostlen) 465 { 466 char *p, *domainname; 467 int len, dlen; 468 469 if (!netname) { 470 syslog(LOG_ERR, "netname2host: null netname"); 471 goto bad_exit; 472 } 473 474 if (strncmp(netname, OPSYS, OPSYS_LEN) != 0) 475 goto bad_netname; 476 p = (char *)netname + OPSYS_LEN; /* skip OPSYS part */ 477 if (*p != '.') 478 goto bad_netname; 479 ++p; /* skip '.' */ 480 481 domainname = strchr(p, '@'); /* get domain name */ 482 if (domainname == 0) 483 goto bad_netname; 484 485 len = domainname - p; /* host sits between '.' and '@' */ 486 domainname++; /* skip '@' sign */ 487 488 if (len <= 0) 489 goto bad_netname; 490 491 if (hostlen < len) { 492 syslog(LOG_ERR, 493 "netname2host: insufficient space for hostname"); 494 goto bad_exit; 495 } 496 497 if (isdigit(*p)) /* don't want uid here */ 498 goto bad_netname; 499 500 if (*p == '\0') /* check for null hostname */ 501 goto bad_netname; 502 503 (void) strncpy(hostname, p, len); 504 505 /* make into fully qualified hostname by concatenating domain part */ 506 dlen = strlen(domainname); 507 if (hostlen < (len + dlen + 2)) { 508 syslog(LOG_ERR, 509 "netname2host: insufficient space for hostname"); 510 goto bad_exit; 511 } 512 513 hostname[len] = '.'; 514 (void) strncpy(hostname+len+1, domainname, dlen); 515 hostname[len+dlen+1] = '\0'; 516 517 return (1); 518 519 bad_netname: 520 syslog(LOG_ERR, "netname2host: invalid host netname %s", netname); 521 522 bad_exit: 523 hostname[0] = '\0'; 524 return (0); 525 } 526