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 /*
28  * Retrieve directory information for standard UNIX users/groups.
29  * (NB:  not just from files, but all nsswitch sources.)
30  */
31 
32 #include <pwd.h>
33 #include <grp.h>
34 #include <malloc.h>
35 #include <string.h>
36 #include <stdlib.h>
37 #include <netdb.h>
38 #include <note.h>
39 #include <errno.h>
40 #include "idmapd.h"
41 #include "directory.h"
42 #include "directory_private.h"
43 #include <rpcsvc/idmap_prot.h>
44 #include "directory_server_impl.h"
45 #include "miscutils.h"
46 #include "sidutil.h"
47 
48 static directory_error_t machine_sid_dav(directory_values_rpc *lvals,
49     unsigned int rid);
50 static directory_error_t directory_provider_nsswitch_populate(
51     directory_entry_rpc *pent, struct passwd *pwd, struct group *grp,
52     idmap_utf8str_list *attrs);
53 
54 /*
55  * Retrieve information by name.
56  * Called indirectly through the directory_provider_static structure.
57  */
58 static
59 directory_error_t
60 directory_provider_nsswitch_get(
61     directory_entry_rpc *del,
62     idmap_utf8str_list *ids,
63     idmap_utf8str types,
64     idmap_utf8str_list *attrs)
65 {
66 	int i;
67 
68 	RDLOCK_CONFIG();
69 
70 	/* 6835280 spurious lint error if the strlen is in the declaration */
71 	int host_name_len = strlen(_idmapdstate.hostname);
72 	char my_host_name[host_name_len + 1];
73 	(void) strcpy(my_host_name, _idmapdstate.hostname);
74 
75 	/* We use len later, so this is not merely a workaround for 6835280 */
76 	int machine_sid_len = strlen(_idmapdstate.cfg->pgcfg.machine_sid);
77 	char my_machine_sid[machine_sid_len + 1];
78 	(void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid);
79 
80 	UNLOCK_CONFIG();
81 
82 	for (i = 0; i < ids->idmap_utf8str_list_len; i++) {
83 		struct passwd *pwd = NULL;
84 		struct group *grp = NULL;
85 		directory_error_t de;
86 		int type;
87 
88 		/*
89 		 * Extract the type for this particular ID.
90 		 * Advance to the next type, if it's there, else keep
91 		 * using this type until we run out of IDs.
92 		 */
93 		type = *types;
94 		if (*(types+1) != '\0')
95 			types++;
96 
97 		/*
98 		 * If this entry has already been handled, one way or another,
99 		 * skip it.
100 		 */
101 		if (del[i].status != DIRECTORY_NOT_FOUND)
102 			continue;
103 
104 		char *id = ids->idmap_utf8str_list_val[i];
105 
106 		if (type == DIRECTORY_ID_SID[0]) {
107 			/*
108 			 * Is it our SID?
109 			 * Check whether the first part matches, then a "-",
110 			 * then a single RID.
111 			 */
112 			if (strncasecmp(id, my_machine_sid, machine_sid_len) !=
113 			    0)
114 				continue;
115 			if (id[machine_sid_len] != '-')
116 				continue;
117 			char *p;
118 			uint32_t rid =
119 			    strtoul(id + machine_sid_len + 1, &p, 10);
120 			if (*p != '\0')
121 				continue;
122 
123 			if (rid < LOCALRID_UID_MIN) {
124 				/* Builtin, not handled here */
125 				continue;
126 			}
127 
128 			if (rid <= LOCALRID_UID_MAX) {
129 				/* User */
130 				errno = 0;
131 				pwd = getpwuid(rid - LOCALRID_UID_MIN);
132 				if (pwd == NULL) {
133 					if (errno == 0)		/* Not found */
134 						continue;
135 					char buf[40];
136 					int err = errno;
137 					(void) snprintf(buf, sizeof (buf),
138 					    "%d", err);
139 					directory_entry_set_error(&del[i],
140 					    directory_error("errno.getpwuid",
141 					    "getpwuid: %2 (%1)",
142 					    buf, strerror(err), NULL));
143 					continue;
144 				}
145 			} else if (rid >= LOCALRID_GID_MIN &&
146 			    rid <= LOCALRID_GID_MAX) {
147 				/* Group */
148 				errno = 0;
149 				grp = getgrgid(rid - LOCALRID_GID_MIN);
150 				if (grp == NULL) {
151 					if (errno == 0)		/* Not found */
152 						continue;
153 					char buf[40];
154 					int err = errno;
155 					(void) snprintf(buf, sizeof (buf),
156 					    "%d", err);
157 					directory_entry_set_error(&del[i],
158 					    directory_error("errno.getgrgid",
159 					    "getgrgid: %2 (%1)",
160 					    buf, strerror(err), NULL));
161 					continue;
162 				}
163 			} else
164 				continue;
165 
166 		} else {
167 			int id_len = strlen(id);
168 			char name[id_len + 1];
169 			char domain[id_len + 1];
170 
171 			split_name(name, domain, id);
172 
173 			if (domain[0] != '\0') {
174 				if (!domain_eq(domain, my_host_name))
175 					continue;
176 			}
177 
178 			/*
179 			 * If the caller has requested user or group
180 			 * information specifically, we only set one of
181 			 * pwd or grp.
182 			 * If the caller has requested either type, we try
183 			 * both in the hopes of getting one.
184 			 * Note that directory_provider_nsswitch_populate
185 			 * considers it to be an error if both are set.
186 			 */
187 			if (type != DIRECTORY_ID_GROUP[0]) {
188 				/* prep for not found / error case */
189 				errno = 0;
190 
191 				pwd = getpwnam(name);
192 				if (pwd == NULL && errno != 0) {
193 					char buf[40];
194 					int err = errno;
195 					(void) snprintf(buf, sizeof (buf),
196 					    "%d", err);
197 					directory_entry_set_error(&del[i],
198 					    directory_error("errno.getpwnam",
199 					    "getpwnam: %2 (%1)",
200 					    buf, strerror(err), NULL));
201 					continue;
202 				}
203 			}
204 
205 			if (type != DIRECTORY_ID_USER[0]) {
206 				/* prep for not found / error case */
207 				errno = 0;
208 
209 				grp = getgrnam(name);
210 				if (grp == NULL && errno != 0) {
211 					char buf[40];
212 					int err = errno;
213 					(void) snprintf(buf, sizeof (buf),
214 					    "%d", err);
215 					directory_entry_set_error(&del[i],
216 					    directory_error("errno.getgrnam",
217 					    "getgrnam: %2 (%1)",
218 					    buf, strerror(err), NULL));
219 					continue;
220 				}
221 			}
222 		}
223 
224 		/*
225 		 * Didn't find it, don't populate the structure.
226 		 * Another provider might populate it.
227 		 */
228 		if (pwd == NULL && grp == NULL)
229 			continue;
230 
231 		de = directory_provider_nsswitch_populate(&del[i], pwd, grp,
232 		    attrs);
233 		if (de != NULL) {
234 			directory_entry_set_error(&del[i], de);
235 			de = NULL;
236 			continue;
237 		}
238 	}
239 
240 	return (NULL);
241 }
242 
243 /*
244  * Given a pwd structure or a grp structure, and a list of attributes that
245  * were requested, populate the structure to return to the caller.
246  */
247 static
248 directory_error_t
249 directory_provider_nsswitch_populate(
250     directory_entry_rpc *pent,
251     struct passwd *pwd,
252     struct group *grp,
253     idmap_utf8str_list *attrs)
254 {
255 	int j;
256 	directory_values_rpc *llvals;
257 	int nattrs;
258 
259 	/*
260 	 * If it wasn't for this case, everything would be a lot simpler.
261 	 * UNIX allows users and groups with the same name.  Windows doesn't.
262 	 */
263 	if (pwd != NULL && grp != NULL) {
264 		return directory_error("Ambiguous.Name",
265 		    "Ambiguous name, is both a user and a group",
266 		    NULL);
267 	}
268 
269 	nattrs = attrs->idmap_utf8str_list_len;
270 
271 	llvals = calloc(nattrs, sizeof (directory_values_rpc));
272 	if (llvals == NULL)
273 		goto nomem;
274 
275 	pent->directory_entry_rpc_u.attrs.attrs_val = llvals;
276 	pent->directory_entry_rpc_u.attrs.attrs_len = nattrs;
277 	pent->status = DIRECTORY_FOUND;
278 
279 	for (j = 0; j < nattrs; j++) {
280 		directory_values_rpc *val;
281 		char *a;
282 		directory_error_t de;
283 
284 		/*
285 		 * We're going to refer to these a lot, so make a shorthand
286 		 * copy.
287 		 */
288 		a = attrs->idmap_utf8str_list_val[j];
289 		val = &llvals[j];
290 
291 		/*
292 		 * Start by assuming no errors and that we don't have
293 		 * the information
294 		 */
295 		val->found = FALSE;
296 		de = NULL;
297 
298 		if (pwd != NULL) {
299 			/*
300 			 * Handle attributes for user entries.
301 			 */
302 			if (strcaseeq(a, "cn")) {
303 				const char *p = pwd->pw_name;
304 				de = str_list_dav(val, &p, 1);
305 			} else if (strcaseeq(a, "objectClass")) {
306 				static const char *objectClasses[] = {
307 					"top",
308 					"posixAccount",
309 				};
310 				de = str_list_dav(val, objectClasses,
311 				    NELEM(objectClasses));
312 			} else if (strcaseeq(a, "gidNumber")) {
313 				de = uint_list_dav(val, &pwd->pw_gid, 1);
314 			} else if (strcaseeq(a, "objectSid")) {
315 				de = machine_sid_dav(val,
316 				    pwd->pw_uid + LOCALRID_UID_MIN);
317 			} else if (strcaseeq(a, "displayName")) {
318 				const char *p = pwd->pw_gecos;
319 				de = str_list_dav(val, &p, 1);
320 			} else if (strcaseeq(a, "distinguishedName")) {
321 				char *dn;
322 				RDLOCK_CONFIG();
323 				(void) asprintf(&dn,
324 				    "uid=%s,ou=people,dc=%s",
325 				    pwd->pw_name, _idmapdstate.hostname);
326 				UNLOCK_CONFIG();
327 				if (dn == NULL)
328 					goto nomem;
329 				const char *cdn = dn;
330 				de = str_list_dav(val, &cdn, 1);
331 				free(dn);
332 			} else if (strcaseeq(a, "uid")) {
333 				const char *p = pwd->pw_name;
334 				de = str_list_dav(val, &p, 1);
335 			} else if (strcaseeq(a, "uidNumber")) {
336 				de = uint_list_dav(val, &pwd->pw_uid, 1);
337 			} else if (strcaseeq(a, "gecos")) {
338 				const char *p = pwd->pw_gecos;
339 				de = str_list_dav(val, &p, 1);
340 			} else if (strcaseeq(a, "homeDirectory")) {
341 				const char *p = pwd->pw_dir;
342 				de = str_list_dav(val, &p, 1);
343 			} else if (strcaseeq(a, "loginShell")) {
344 				const char *p = pwd->pw_shell;
345 				de = str_list_dav(val, &p, 1);
346 			} else if (strcaseeq(a, "x-sun-canonicalName")) {
347 				char *canon;
348 				RDLOCK_CONFIG();
349 				(void) asprintf(&canon, "%s@%s",
350 				    pwd->pw_name, _idmapdstate.hostname);
351 				UNLOCK_CONFIG();
352 				if (canon == NULL)
353 					goto nomem;
354 				const char *ccanon = canon;
355 				de = str_list_dav(val, &ccanon, 1);
356 				free(canon);
357 			} else if (strcaseeq(a, "x-sun-provider")) {
358 				const char *provider = "UNIX-passwd";
359 				de = str_list_dav(val, &provider, 1);
360 			}
361 		} else if (grp != NULL)  {
362 			/*
363 			 * Handle attributes for group entries.
364 			 */
365 			if (strcaseeq(a, "cn")) {
366 				const char *p = grp->gr_name;
367 				de = str_list_dav(val, &p, 1);
368 			} else if (strcaseeq(a, "objectClass")) {
369 				static const char *objectClasses[] = {
370 					"top",
371 					"posixGroup",
372 				};
373 				de = str_list_dav(val, objectClasses,
374 				    NELEM(objectClasses));
375 			} else if (strcaseeq(a, "gidNumber")) {
376 				de = uint_list_dav(val, &grp->gr_gid, 1);
377 			} else if (strcaseeq(a, "objectSid")) {
378 				de = machine_sid_dav(val,
379 				    grp->gr_gid + LOCALRID_GID_MIN);
380 			} else if (strcaseeq(a, "displayName")) {
381 				const char *p = grp->gr_name;
382 				de = str_list_dav(val, &p, 1);
383 			} else if (strcaseeq(a, "distinguishedName")) {
384 				char *dn;
385 				RDLOCK_CONFIG();
386 				(void) asprintf(&dn,
387 				    "cn=%s,ou=group,dc=%s",
388 				    grp->gr_name, _idmapdstate.hostname);
389 				UNLOCK_CONFIG();
390 				if (dn == NULL)
391 					goto nomem;
392 				const char *cdn = dn;
393 				de = str_list_dav(val, &cdn, 1);
394 				free(dn);
395 			} else if (strcaseeq(a, "memberUid")) {
396 				/*
397 				 * NEEDSWORK:  There is probably a non-cast
398 				 * way to do this, but I don't immediately
399 				 * see it.
400 				 */
401 				const char * const *members =
402 				    (const char * const *)grp->gr_mem;
403 				de = str_list_dav(val, members, 0);
404 			} else if (strcaseeq(a, "x-sun-canonicalName")) {
405 				char *canon;
406 				RDLOCK_CONFIG();
407 				(void) asprintf(&canon, "%s@%s",
408 				    grp->gr_name, _idmapdstate.hostname);
409 				UNLOCK_CONFIG();
410 				if (canon == NULL)
411 					goto nomem;
412 				const char *ccanon = canon;
413 				de = str_list_dav(val, &ccanon, 1);
414 				free(canon);
415 			} else if (strcaseeq(a, "x-sun-provider")) {
416 				const char *provider = "UNIX-group";
417 				de = str_list_dav(val, &provider, 1);
418 			}
419 		}
420 
421 		if (de != NULL)
422 			return (de);
423 	}
424 
425 	return (NULL);
426 
427 nomem:
428 	return (directory_error("ENOMEM.users",
429 	    "No memory allocating return value for user lookup", NULL));
430 }
431 
432 /*
433  * Populate a directory attribute value with a SID based on our machine SID
434  * and the specified RID.
435  *
436  * It's a bit perverse that we must take a text-format SID and turn it into
437  * a binary-format SID, only to have the caller probably turn it back into
438  * text format, but SIDs are carried across LDAP in binary format.
439  */
440 static
441 directory_error_t
442 machine_sid_dav(directory_values_rpc *lvals, unsigned int rid)
443 {
444 	sid_t *sid;
445 	directory_error_t de;
446 
447 	RDLOCK_CONFIG();
448 	int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid);
449 	char buf[len + 100];	/* 100 is enough space for any RID */
450 	(void) snprintf(buf, sizeof (buf), "%s-%u",
451 	    _idmapdstate.cfg->pgcfg.machine_sid, rid);
452 	UNLOCK_CONFIG();
453 
454 	sid = sid_fromstr(buf);
455 	if (sid == NULL)
456 		goto nomem;
457 
458 	sid_to_le(sid);
459 
460 	de = bin_list_dav(lvals, sid, 1, sid_len(sid));
461 	sid_free(sid);
462 	return (de);
463 
464 nomem:
465 	return (directory_error("ENOMEM.machine_sid_dav",
466 	    "Out of memory allocating return value for lookup", NULL));
467 }
468 
469 struct directory_provider_static directory_provider_nsswitch = {
470 	"files",
471 	directory_provider_nsswitch_get,
472 };
473