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
directory_provider_nsswitch_get(directory_entry_rpc * del,idmap_utf8str_list * ids,idmap_utf8str types,idmap_utf8str_list * attrs)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
directory_provider_nsswitch_populate(directory_entry_rpc * pent,struct passwd * pwd,struct group * grp,idmap_utf8str_list * attrs)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
machine_sid_dav(directory_values_rpc * lvals,unsigned int rid)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