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