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
parse_uid(char * s,struct netid_userdata * argp)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
parse_gidlist(char * p,struct netid_userdata * argp)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
parse_netid_str(char * s,struct netid_userdata * argp)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
netname2user_files(int * err,char * netname,struct netid_userdata * argp)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
netname2user_nis(int * err,char * netname,struct netid_userdata * argp)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
netname2user_ldap(int * err,char * netname,struct netid_userdata * argp)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
netname2user(const char netname[MAXNETNAMELEN+1],uid_t * uidp,gid_t * gidp,int * gidlenp,gid_t * gidlist)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
netname2host(const char netname[MAXNETNAMELEN+1],char * hostname,const int hostlen)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