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 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 * 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 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 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 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 * 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