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
parse_uid(char * s,struct netid_userdata * argp)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
parse_gidlist(char * p,struct netid_userdata * argp)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
parse_netid_str(char * s,struct netid_userdata * argp)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
netname2user_files(int * err,char * netname,struct netid_userdata * argp)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
netname2user_nis(int * err,char * netname,struct netid_userdata * argp)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
netname2user_ldap(int * err,char * netname,struct netid_userdata * argp)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
netname2user(const char netname[MAXNETNAMELEN+1],uid_t * uidp,gid_t * gidp,int * gidlenp,gid_t * gidlist)375 netname2user(const char netname[MAXNETNAMELEN + 1], uid_t *uidp, gid_t *gidp,
376 int *gidlenp, 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
netname2host(const char netname[MAXNETNAMELEN+1],char * hostname,const int hostlen)467 netname2host(const char netname[MAXNETNAMELEN + 1], char *hostname,
468 const int hostlen)
469 {
470 char *p, *domainname;
471 int len, dlen;
472
473 if (!netname) {
474 syslog(LOG_ERR, "netname2host: null netname");
475 goto bad_exit;
476 }
477
478 if (strncmp(netname, OPSYS, OPSYS_LEN) != 0)
479 goto bad_netname;
480 p = (char *)netname + OPSYS_LEN; /* skip OPSYS part */
481 if (*p != '.')
482 goto bad_netname;
483 ++p; /* skip '.' */
484
485 domainname = strchr(p, '@'); /* get domain name */
486 if (domainname == 0)
487 goto bad_netname;
488
489 len = domainname - p; /* host sits between '.' and '@' */
490 domainname++; /* skip '@' sign */
491
492 if (len <= 0)
493 goto bad_netname;
494
495 if (hostlen < len) {
496 syslog(LOG_ERR,
497 "netname2host: insufficient space for hostname");
498 goto bad_exit;
499 }
500
501 if (isdigit(*p)) /* don't want uid here */
502 goto bad_netname;
503
504 if (*p == '\0') /* check for null hostname */
505 goto bad_netname;
506
507 (void) strncpy(hostname, p, len);
508
509 /* make into fully qualified hostname by concatenating domain part */
510 dlen = strlen(domainname);
511 if (hostlen < (len + dlen + 2)) {
512 syslog(LOG_ERR,
513 "netname2host: insufficient space for hostname");
514 goto bad_exit;
515 }
516
517 hostname[len] = '.';
518 (void) strncpy(hostname+len+1, domainname, dlen);
519 hostname[len+dlen+1] = '\0';
520
521 return (1);
522
523 bad_netname:
524 syslog(LOG_ERR, "netname2host: invalid host netname %s", netname);
525
526 bad_exit:
527 hostname[0] = '\0';
528 return (0);
529 }
530