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