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