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
47static directory_error_t machine_sid_dav(directory_values_rpc *lvals,
48    unsigned int rid);
49static 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 */
57static
58directory_error_t
59directory_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 */
246static
247directory_error_t
248directory_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
426nomem:
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 */
439static
440directory_error_t
441machine_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
463nomem:
464	return (directory_error("ENOMEM.machine_sid_dav",
465	    "Out of memory allocating return value for lookup", NULL));
466}
467
468struct directory_provider_static directory_provider_nsswitch = {
469	"files",
470	directory_provider_nsswitch_get,
471};
472