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