xref: /illumos-gate/usr/src/lib/nsswitch/ad/common/getpwnam.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <pwd.h>
27 #include <idmap.h>
28 #include <ctype.h>
29 #include "ad_common.h"
30 
31 /* passwd attributes and filters */
32 #define	_PWD_DN			"dn"
33 #define	_PWD_SAN		"sAMAccountName"
34 #define	_PWD_OBJSID		"objectSid"
35 #define	_PWD_PRIMARYGROUPID	"primaryGroupID"
36 #define	_PWD_CN			"cn"
37 #define	_PWD_HOMEDIRECTORY	"homedirectory"
38 #define	_PWD_LOGINSHELL		"loginshell"
39 #define	_PWD_OBJCLASS		"objectClass"
40 
41 #define	_F_GETPWNAM		"(sAMAccountName=%.*s)"
42 #define	_F_GETPWUID		"(objectSid=%s)"
43 
44 static const char *pwd_attrs[] = {
45 	_PWD_SAN,
46 	_PWD_OBJSID,
47 	_PWD_PRIMARYGROUPID,
48 	_PWD_CN,
49 	_PWD_HOMEDIRECTORY,
50 	_PWD_LOGINSHELL,
51 	_PWD_OBJCLASS,
52 	(char *)NULL
53 };
54 
55 static int
56 update_buffer(ad_backend_ptr be, nss_XbyY_args_t *argp,
57 		const char *name, const char *domain,
58 		uid_t uid, gid_t gid, const char *gecos,
59 		const char *homedir, const char *shell)
60 {
61 	int	buflen;
62 	char	*buffer;
63 
64 	if (be->db_type == NSS_AD_DB_PASSWD_BYNAME) {
65 		/*
66 		 * The canonical name obtained from AD lookup may not match
67 		 * the case of the name (i.e. key) in the request. Therefore,
68 		 * use the name from the request to construct the result.
69 		 */
70 		buflen = snprintf(NULL, 0, "%s:%s:%u:%u:%s:%s:%s",
71 		    argp->key.name, "x", uid, gid, gecos, homedir, shell) + 1;
72 	} else {
73 		if (domain == NULL)
74 			domain = WK_DOMAIN;
75 		buflen = snprintf(NULL, 0, "%s@%s:%s:%u:%u:%s:%s:%s",
76 		    name, domain, "x", uid, gid, gecos, homedir, shell) + 1;
77 	}
78 
79 
80 	if (argp->buf.result != NULL) {
81 		buffer = be->buffer = malloc(buflen);
82 		if (be->buffer == NULL)
83 			return (-1);
84 		be->buflen = buflen;
85 	} else {
86 		if (buflen > argp->buf.buflen)
87 			return (-1);
88 		buflen = argp->buf.buflen;
89 		buffer = argp->buf.buffer;
90 	}
91 
92 	if (be->db_type == NSS_AD_DB_PASSWD_BYNAME)
93 		(void) snprintf(buffer, buflen, "%s:%s:%u:%u:%s:%s:%s",
94 		    argp->key.name, "x", uid, gid, gecos, homedir, shell);
95 	else
96 		(void) snprintf(buffer, buflen, "%s@%s:%s:%u:%u:%s:%s:%s",
97 		    name, domain, "x", uid, gid, gecos, homedir, shell);
98 	return (0);
99 }
100 
101 
102 #define	NET_SCHEME	"/net"
103 
104 /*
105  * 1) If the homeDirectory string is in UNC format then convert it into
106  * a /net format. This needs to be revisited later but is fine for now
107  * because Solaris does not support -hosts automount map for CIFS yet.
108  *
109  * 2) If homeDirectory contains ':' then return NULL because ':' is the
110  * delimiter in passwd entries and may break apps that parse these entries.
111  *
112  * 3) For all other cases return the same string that was passed to
113  * this function.
114  */
115 static
116 char *
117 process_homedir(char *homedir)
118 {
119 	size_t	len, smb_len;
120 	char	*smb_homedir;
121 	int	i, slash = 0;
122 
123 	len = strlen(homedir);
124 
125 	if (strchr(homedir, ':') != NULL)
126 		/*
127 		 * Ignore paths that have colon ':' because ':' is a
128 		 * delimiter for the passwd entry.
129 		 */
130 		return (NULL);
131 
132 	if (!(len > 1 && homedir[0] == '\\' && homedir[1] == '\\'))
133 		/* Keep homedir intact if not in UNC format */
134 		return (homedir);
135 
136 	/*
137 	 * Convert UNC string into /net format
138 	 * Example: \\server\abc -> /net/server/abc
139 	 */
140 	smb_len = len + 1 + sizeof (NET_SCHEME);
141 	if ((smb_homedir = calloc(1, smb_len)) == NULL)
142 		return (NULL);
143 	(void) strlcpy(smb_homedir, NET_SCHEME, smb_len);
144 	for (i = strlen(smb_homedir); *homedir != '\0'; homedir++) {
145 		if (*homedir == '\\') {
146 			/* Reduce double backslashes into one */
147 			if (slash)
148 				slash = 0;
149 			else {
150 				slash = 1;
151 				smb_homedir[i++] = '/';
152 			}
153 		} else {
154 			smb_homedir[i++] = *homedir;
155 			slash = 0;
156 		}
157 	}
158 	return (smb_homedir);
159 }
160 
161 /*
162  * _nss_ad_passwd2str is the data marshaling method for the passwd getXbyY
163  * (e.g., getbyuid(), getbyname(), getpwent()) backend processes. This method is
164  * called after a successful AD search has been performed. This method will
165  * parse the AD search values into the file format.
166  * e.g.
167  *
168  * blue@whale:x:123456:10:Blue Whale:/:
169  *
170  */
171 static int
172 _nss_ad_passwd2str(ad_backend_ptr be, nss_XbyY_args_t *argp)
173 {
174 	int			nss_result;
175 	adutils_result_t	*result = be->result;
176 	const adutils_entry_t	*entry;
177 	char			**sid_v, *ptr, **pgid_v, *end;
178 	ulong_t			tmp;
179 	uint32_t		urid, grid;
180 	uid_t			uid;
181 	gid_t			gid;
182 	idmap_stat		gstat;
183 	idmap_get_handle_t 	*ig = NULL;
184 	char			**name_v, **dn_v, *domain = NULL;
185 	char			**gecos_v, **shell_v;
186 	char			**homedir_v = NULL, *homedir = NULL;
187 	char			*NULL_STR = "";
188 
189 	if (result == NULL)
190 		return (NSS_STR_PARSE_PARSE);
191 	entry = adutils_getfirstentry(result);
192 	nss_result = NSS_STR_PARSE_PARSE;
193 
194 	/* Create handles for idmap service */
195 	if (be->ih == NULL && idmap_init(&be->ih) != 0)
196 		goto result_pwd2str;
197 	if (idmap_get_create(be->ih, &ig) != 0)
198 		goto result_pwd2str;
199 
200 	/* Get name */
201 	name_v = adutils_getattr(entry, _PWD_SAN);
202 	if (name_v == NULL || name_v[0] == NULL || *name_v[0] == '\0')
203 		goto result_pwd2str;
204 
205 	/* Get domain */
206 	dn_v = adutils_getattr(entry, _PWD_DN);
207 	if (dn_v == NULL || dn_v[0] == NULL || *dn_v[0] == '\0')
208 		goto result_pwd2str;
209 	domain = adutils_dn2dns(dn_v[0]);
210 
211 	/* Get objectSID (in text format) */
212 	sid_v = adutils_getattr(entry, _PWD_OBJSID);
213 	if (sid_v == NULL || sid_v[0] == NULL || *sid_v[0] == '\0')
214 		goto result_pwd2str;
215 
216 	/* Break SID into prefix and rid */
217 	if ((ptr = strrchr(sid_v[0], '-')) == NULL)
218 		goto result_pwd2str;
219 	*ptr = '\0';
220 	end = ++ptr;
221 	tmp = strtoul(ptr, &end, 10);
222 	if (end == ptr || tmp > UINT32_MAX)
223 		goto result_pwd2str;
224 	urid = (uint32_t)tmp;
225 
226 	/* We already have uid -- no need to call idmapd */
227 	if (be->db_type == NSS_AD_DB_PASSWD_BYUID)
228 		uid = argp->key.uid;
229 	else
230 		uid = be->uid;
231 
232 	/* Get primaryGroupID */
233 	pgid_v = adutils_getattr(entry, _PWD_PRIMARYGROUPID);
234 	if (pgid_v == NULL || pgid_v[0] == NULL || *pgid_v[0] == '\0')
235 		/*
236 		 * If primaryGroupID is not found then we request
237 		 * a GID to be mapped to the given user's objectSID
238 		 * (diagonal mapping) and use this GID as the primary
239 		 * GID for the entry.
240 		 */
241 		grid = urid;
242 	else {
243 		end = pgid_v[0];
244 		tmp = strtoul(pgid_v[0], &end, 10);
245 		if (end == pgid_v[0] || tmp > UINT32_MAX)
246 			goto result_pwd2str;
247 		grid = (uint32_t)tmp;
248 	}
249 
250 	/* Map group SID to GID using idmap service */
251 	if (idmap_get_gidbysid(ig, sid_v[0], grid, 0, &gid, &gstat) != 0)
252 		goto result_pwd2str;
253 	if (idmap_get_mappings(ig) != 0 || gstat != 0) {
254 		RESET_ERRNO();
255 		goto result_pwd2str;
256 	}
257 
258 	/* Get gecos, homedirectory and shell information if available */
259 	gecos_v = adutils_getattr(entry, _PWD_CN);
260 	if (gecos_v == NULL || gecos_v[0] == NULL || *gecos_v[0] == '\0')
261 		gecos_v = &NULL_STR;
262 
263 	homedir_v = adutils_getattr(entry, _PWD_HOMEDIRECTORY);
264 	if (homedir_v == NULL || homedir_v[0] == NULL || *homedir_v[0] == '\0')
265 		homedir = NULL_STR;
266 	else if ((homedir = process_homedir(homedir_v[0])) == NULL)
267 		homedir = NULL_STR;
268 
269 	shell_v = adutils_getattr(entry, _PWD_LOGINSHELL);
270 	if (shell_v == NULL || shell_v[0] == NULL || *shell_v[0] == '\0')
271 		shell_v = &NULL_STR;
272 
273 	if (update_buffer(be, argp, name_v[0], domain, uid, gid,
274 	    gecos_v[0], homedir, shell_v[0]) < 0)
275 		nss_result = NSS_STR_PARSE_ERANGE;
276 	else
277 		nss_result = NSS_STR_PARSE_SUCCESS;
278 
279 result_pwd2str:
280 	idmap_get_destroy(ig);
281 	(void) idmap_fini(be->ih);
282 	be->ih = NULL;
283 	(void) adutils_freeresult(&be->result);
284 	free(domain);
285 	if (homedir != NULL_STR && homedir_v != NULL &&
286 	    homedir != homedir_v[0])
287 		free(homedir);
288 	return ((int)nss_result);
289 }
290 
291 /*
292  * getbyname gets a passwd entry by winname. This function constructs an ldap
293  * search filter using the name invocation parameter and the getpwnam search
294  * filter defined. Once the filter is constructed, we search for a matching
295  * entry and marshal the data results into struct passwd for the frontend
296  * process. The function _nss_ad_passwd2ent performs the data marshaling.
297  */
298 
299 static nss_status_t
300 getbyname(ad_backend_ptr be, void *a)
301 {
302 	nss_XbyY_args_t	*argp = (nss_XbyY_args_t *)a;
303 	char		*searchfilter;
304 	char		name[SEARCHFILTERLEN];
305 	char		*dname;
306 	int		filterlen, namelen;
307 	int		flag;
308 	nss_status_t	stat;
309 	idmap_stat	idmaprc;
310 	uid_t		uid;
311 	gid_t		gid;
312 	int		is_user, is_wuser, try_idmap;
313 	idmap_handle_t	*ih;
314 
315 	be->db_type = NSS_AD_DB_PASSWD_BYNAME;
316 
317 	/* Sanitize name so that it can be used in our LDAP filter */
318 	if (_ldap_filter_name(name, argp->key.name, sizeof (name)) != 0)
319 		return ((nss_status_t)NSS_NOTFOUND);
320 
321 	if ((dname = strchr(name, '@')) == NULL)
322 		return ((nss_status_t)NSS_NOTFOUND);
323 
324 	*dname = '\0';
325 	dname++;
326 
327 	/*
328 	 * Map the given name to UID using idmap service. If idmap
329 	 * call fails then this will save us doing AD discovery and
330 	 * AD lookup here.
331 	 */
332 	if (idmap_init(&be->ih) != IDMAP_SUCCESS)
333 		return ((nss_status_t)NSS_NOTFOUND);
334 	flag = (strcasecmp(dname, WK_DOMAIN) == 0) ?
335 	    IDMAP_REQ_FLG_WK_OR_LOCAL_SIDS_ONLY : 0;
336 	is_wuser = -1;
337 	is_user = 1;
338 	if (idmap_get_w2u_mapping(be->ih, NULL, NULL, name,
339 	    dname, flag, &is_user, &is_wuser, &be->uid, NULL,
340 	    NULL, NULL) != IDMAP_SUCCESS) {
341 		(void) idmap_fini(be->ih);
342 		be->ih = NULL;
343 		RESET_ERRNO();
344 		return ((nss_status_t)NSS_NOTFOUND);
345 	}
346 
347 	/* If this is not a Well-Known SID then try AD lookup. */
348 	if (strcasecmp(dname, WK_DOMAIN) != 0) {
349 		/* Assemble filter using the given name */
350 		namelen = strlen(name);
351 		filterlen = snprintf(NULL, 0, _F_GETPWNAM, namelen, name) + 1;
352 		if ((searchfilter = (char *)malloc(filterlen)) == NULL)
353 			return ((nss_status_t)NSS_NOTFOUND);
354 		(void) snprintf(searchfilter, filterlen, _F_GETPWNAM,
355 		    namelen, name);
356 		stat = _nss_ad_lookup(be, argp, _PASSWD, searchfilter,
357 		    dname, &try_idmap);
358 		free(searchfilter);
359 
360 		if (!try_idmap) {
361 			(void) idmap_fini(be->ih);
362 			be->ih = NULL;
363 			return (stat);
364 		}
365 
366 	}
367 
368 	/*
369 	 * Either this is a Well-Known SID or AD lookup failed. Map
370 	 * the given name to GID using idmap service and construct
371 	 * the passwd entry.
372 	 */
373 	is_wuser = -1;
374 	is_user = 0; /* Map name to primary gid */
375 	idmaprc = idmap_get_w2u_mapping(be->ih, NULL, NULL, name, dname,
376 	    flag, &is_user, &is_wuser, &gid, NULL, NULL, NULL);
377 	(void) idmap_fini(be->ih);
378 	be->ih = NULL;
379 	if (idmaprc != IDMAP_SUCCESS) {
380 		RESET_ERRNO();
381 		return ((nss_status_t)NSS_NOTFOUND);
382 	}
383 
384 	/* Create passwd(4) style string */
385 	if (update_buffer(be, argp, name, dname,
386 	    be->uid, gid, "", "", "") < 0)
387 		return ((nss_status_t)NSS_NOTFOUND);
388 
389 	/* Marshall the data, sanitize the return status and return */
390 	stat = _nss_ad_marshall_data(be, argp);
391 	return (_nss_ad_sanitize_status(be, argp, stat));
392 }
393 
394 
395 /*
396  * getbyuid gets a passwd entry by uid number. This function constructs an ldap
397  * search filter using the uid invocation parameter and the getpwuid search
398  * filter defined. Once the filter is constructed, we search for a matching
399  * entry and marshal the data results into struct passwd for the frontend
400  * process. The function _nss_ad_passwd2ent performs the data marshaling.
401  */
402 
403 static nss_status_t
404 getbyuid(ad_backend_ptr be, void *a)
405 {
406 	nss_XbyY_args_t		*argp = (nss_XbyY_args_t *)a;
407 	char			searchfilter[ADUTILS_MAXHEXBINSID + 14];
408 	char			*sidprefix = NULL;
409 	idmap_rid_t		rid;
410 	char			cbinsid[ADUTILS_MAXHEXBINSID + 1];
411 	char			*winname = NULL, *windomain = NULL;
412 	int			is_user, is_wuser;
413 	gid_t			gid;
414 	idmap_stat		idmaprc;
415 	int			ret, try_idmap;
416 	nss_status_t		stat;
417 
418 	be->db_type = NSS_AD_DB_PASSWD_BYUID;
419 
420 	stat = (nss_status_t)NSS_NOTFOUND;
421 
422 	/* nss_ad does not support non ephemeral uids */
423 	if (argp->key.uid <= MAXUID)
424 		goto out;
425 
426 	/* Map the given UID to a SID using the idmap service */
427 	if (idmap_init(&be->ih) != 0)
428 		goto out;
429 	if (idmap_get_u2w_mapping(be->ih, &argp->key.uid, NULL, 0,
430 	    1, NULL, &sidprefix, &rid, &winname, &windomain,
431 	    NULL, NULL) != 0) {
432 		RESET_ERRNO();
433 		goto out;
434 	}
435 
436 	/*
437 	 * NULL winname implies a local SID or unresolvable SID both of
438 	 * which cannot be used to generated passwd(4) entry
439 	 */
440 	if (winname == NULL)
441 		goto out;
442 
443 	/* If this is not a Well-Known SID try AD lookup */
444 	if (windomain != NULL && strcasecmp(windomain, WK_DOMAIN) != 0) {
445 		if (adutils_txtsid2hexbinsid(sidprefix, &rid,
446 		    &cbinsid[0], sizeof (cbinsid)) != 0)
447 			goto out;
448 
449 		ret = snprintf(searchfilter, sizeof (searchfilter),
450 		    _F_GETPWUID, cbinsid);
451 		if (ret >= sizeof (searchfilter) || ret < 0)
452 			goto out;
453 
454 		stat = _nss_ad_lookup(be, argp, _PASSWD, searchfilter,
455 		    windomain, &try_idmap);
456 
457 		if (!try_idmap)
458 			goto out;
459 	}
460 
461 	/* Map winname to primary gid using idmap service */
462 	is_user = 0;
463 	is_wuser = -1;
464 	idmaprc = idmap_get_w2u_mapping(be->ih, NULL, NULL,
465 	    winname, windomain, 0, &is_user, &is_wuser, &gid,
466 	    NULL, NULL, NULL);
467 
468 	(void) idmap_fini(be->ih);
469 	be->ih = NULL;
470 
471 	if (idmaprc != IDMAP_SUCCESS) {
472 		RESET_ERRNO();
473 		goto out;
474 	}
475 
476 	/* Create passwd(4) style string */
477 	if (update_buffer(be, argp, winname, windomain,
478 	    argp->key.uid, gid, "", "", "") < 0)
479 		goto out;
480 
481 	/* Marshall the data, sanitize the return status and return */
482 	stat = _nss_ad_marshall_data(be, argp);
483 	stat = _nss_ad_sanitize_status(be, argp, stat);
484 
485 out:
486 	idmap_free(sidprefix);
487 	idmap_free(winname);
488 	idmap_free(windomain);
489 	(void) idmap_fini(be->ih);
490 	be->ih = NULL;
491 	return (stat);
492 }
493 
494 static ad_backend_op_t passwd_ops[] = {
495 	_nss_ad_destr,
496 	_nss_ad_endent,
497 	_nss_ad_setent,
498 	_nss_ad_getent,
499 	getbyname,
500 	getbyuid
501 };
502 
503 /*
504  * _nss_ad_passwd_constr is where life begins. This function calls the
505  * generic AD constructor function to define and build the abstract
506  * data types required to support AD operations.
507  */
508 
509 /*ARGSUSED0*/
510 nss_backend_t *
511 _nss_ad_passwd_constr(const char *dummy1, const char *dummy2,
512 			const char *dummy3)
513 {
514 
515 	return ((nss_backend_t *)_nss_ad_constr(passwd_ops,
516 	    sizeof (passwd_ops)/sizeof (passwd_ops[0]),
517 	    _PASSWD, pwd_attrs, _nss_ad_passwd2str));
518 }
519