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 2009 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * Retrieve directory information for standard UNIX users/groups. 29 * (NB: not just from files, but all nsswitch sources.) 30 */ 31 32 #include <pwd.h> 33 #include <grp.h> 34 #include <malloc.h> 35 #include <string.h> 36 #include <stdlib.h> 37 #include <netdb.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 "miscutils.h" 46 #include "sidutil.h" 47 48 static directory_error_t machine_sid_dav(directory_values_rpc *lvals, 49 unsigned int rid); 50 static directory_error_t directory_provider_nsswitch_populate( 51 directory_entry_rpc *pent, struct passwd *pwd, struct group *grp, 52 idmap_utf8str_list *attrs); 53 54 /* 55 * Retrieve information by name. 56 * Called indirectly through the directory_provider_static structure. 57 */ 58 static 59 directory_error_t 60 directory_provider_nsswitch_get( 61 directory_entry_rpc *del, 62 idmap_utf8str_list *ids, 63 idmap_utf8str types, 64 idmap_utf8str_list *attrs) 65 { 66 int i; 67 68 RDLOCK_CONFIG(); 69 70 /* 6835280 spurious lint error if the strlen is in the declaration */ 71 int host_name_len = strlen(_idmapdstate.hostname); 72 char my_host_name[host_name_len + 1]; 73 (void) strcpy(my_host_name, _idmapdstate.hostname); 74 75 /* We use len later, so this is not merely a workaround for 6835280 */ 76 int machine_sid_len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); 77 char my_machine_sid[machine_sid_len + 1]; 78 (void) strcpy(my_machine_sid, _idmapdstate.cfg->pgcfg.machine_sid); 79 80 UNLOCK_CONFIG(); 81 82 for (i = 0; i < ids->idmap_utf8str_list_len; i++) { 83 struct passwd *pwd = NULL; 84 struct group *grp = NULL; 85 directory_error_t de; 86 int type; 87 88 /* 89 * Extract the type for this particular ID. 90 * Advance to the next type, if it's there, else keep 91 * using this type until we run out of IDs. 92 */ 93 type = *types; 94 if (*(types+1) != '\0') 95 types++; 96 97 /* 98 * If this entry has already been handled, one way or another, 99 * skip it. 100 */ 101 if (del[i].status != DIRECTORY_NOT_FOUND) 102 continue; 103 104 char *id = ids->idmap_utf8str_list_val[i]; 105 106 if (type == DIRECTORY_ID_SID[0]) { 107 /* 108 * Is it our SID? 109 * Check whether the first part matches, then a "-", 110 * then a single RID. 111 */ 112 if (strncasecmp(id, my_machine_sid, machine_sid_len) != 113 0) 114 continue; 115 if (id[machine_sid_len] != '-') 116 continue; 117 char *p; 118 uint32_t rid = 119 strtoul(id + machine_sid_len + 1, &p, 10); 120 if (*p != '\0') 121 continue; 122 123 if (rid < LOCALRID_UID_MIN) { 124 /* Builtin, not handled here */ 125 continue; 126 } 127 128 if (rid <= LOCALRID_UID_MAX) { 129 /* User */ 130 errno = 0; 131 pwd = getpwuid(rid - LOCALRID_UID_MIN); 132 if (pwd == NULL) { 133 if (errno == 0) /* Not found */ 134 continue; 135 char buf[40]; 136 int err = errno; 137 (void) snprintf(buf, sizeof (buf), 138 "%d", err); 139 directory_entry_set_error(&del[i], 140 directory_error("errno.getpwuid", 141 "getpwuid: %2 (%1)", 142 buf, strerror(err), NULL)); 143 continue; 144 } 145 } else if (rid >= LOCALRID_GID_MIN && 146 rid <= LOCALRID_GID_MAX) { 147 /* Group */ 148 errno = 0; 149 grp = getgrgid(rid - LOCALRID_GID_MIN); 150 if (grp == NULL) { 151 if (errno == 0) /* Not found */ 152 continue; 153 char buf[40]; 154 int err = errno; 155 (void) snprintf(buf, sizeof (buf), 156 "%d", err); 157 directory_entry_set_error(&del[i], 158 directory_error("errno.getgrgid", 159 "getgrgid: %2 (%1)", 160 buf, strerror(err), NULL)); 161 continue; 162 } 163 } else 164 continue; 165 166 } else { 167 int id_len = strlen(id); 168 char name[id_len + 1]; 169 char domain[id_len + 1]; 170 171 split_name(name, domain, id); 172 173 if (domain[0] != '\0') { 174 if (!domain_eq(domain, my_host_name)) 175 continue; 176 } 177 178 /* 179 * If the caller has requested user or group 180 * information specifically, we only set one of 181 * pwd or grp. 182 * If the caller has requested either type, we try 183 * both in the hopes of getting one. 184 * Note that directory_provider_nsswitch_populate 185 * considers it to be an error if both are set. 186 */ 187 if (type != DIRECTORY_ID_GROUP[0]) { 188 /* prep for not found / error case */ 189 errno = 0; 190 191 pwd = getpwnam(name); 192 if (pwd == NULL && errno != 0) { 193 char buf[40]; 194 int err = errno; 195 (void) snprintf(buf, sizeof (buf), 196 "%d", err); 197 directory_entry_set_error(&del[i], 198 directory_error("errno.getpwnam", 199 "getpwnam: %2 (%1)", 200 buf, strerror(err), NULL)); 201 continue; 202 } 203 } 204 205 if (type != DIRECTORY_ID_USER[0]) { 206 /* prep for not found / error case */ 207 errno = 0; 208 209 grp = getgrnam(name); 210 if (grp == NULL && errno != 0) { 211 char buf[40]; 212 int err = errno; 213 (void) snprintf(buf, sizeof (buf), 214 "%d", err); 215 directory_entry_set_error(&del[i], 216 directory_error("errno.getgrnam", 217 "getgrnam: %2 (%1)", 218 buf, strerror(err), NULL)); 219 continue; 220 } 221 } 222 } 223 224 /* 225 * Didn't find it, don't populate the structure. 226 * Another provider might populate it. 227 */ 228 if (pwd == NULL && grp == NULL) 229 continue; 230 231 de = directory_provider_nsswitch_populate(&del[i], pwd, grp, 232 attrs); 233 if (de != NULL) { 234 directory_entry_set_error(&del[i], de); 235 de = NULL; 236 continue; 237 } 238 } 239 240 return (NULL); 241 } 242 243 /* 244 * Given a pwd structure or a grp structure, and a list of attributes that 245 * were requested, populate the structure to return to the caller. 246 */ 247 static 248 directory_error_t 249 directory_provider_nsswitch_populate( 250 directory_entry_rpc *pent, 251 struct passwd *pwd, 252 struct group *grp, 253 idmap_utf8str_list *attrs) 254 { 255 int j; 256 directory_values_rpc *llvals; 257 int nattrs; 258 259 /* 260 * If it wasn't for this case, everything would be a lot simpler. 261 * UNIX allows users and groups with the same name. Windows doesn't. 262 */ 263 if (pwd != NULL && grp != NULL) { 264 return directory_error("Ambiguous.Name", 265 "Ambiguous name, is both a user and a group", 266 NULL); 267 } 268 269 nattrs = attrs->idmap_utf8str_list_len; 270 271 llvals = calloc(nattrs, sizeof (directory_values_rpc)); 272 if (llvals == NULL) 273 goto nomem; 274 275 pent->directory_entry_rpc_u.attrs.attrs_val = llvals; 276 pent->directory_entry_rpc_u.attrs.attrs_len = nattrs; 277 pent->status = DIRECTORY_FOUND; 278 279 for (j = 0; j < nattrs; j++) { 280 directory_values_rpc *val; 281 char *a; 282 directory_error_t de; 283 284 /* 285 * We're going to refer to these a lot, so make a shorthand 286 * copy. 287 */ 288 a = attrs->idmap_utf8str_list_val[j]; 289 val = &llvals[j]; 290 291 /* 292 * Start by assuming no errors and that we don't have 293 * the information 294 */ 295 val->found = FALSE; 296 de = NULL; 297 298 if (pwd != NULL) { 299 /* 300 * Handle attributes for user entries. 301 */ 302 if (strcaseeq(a, "cn")) { 303 const char *p = pwd->pw_name; 304 de = str_list_dav(val, &p, 1); 305 } else if (strcaseeq(a, "objectClass")) { 306 static const char *objectClasses[] = { 307 "top", 308 "posixAccount", 309 }; 310 de = str_list_dav(val, objectClasses, 311 NELEM(objectClasses)); 312 } else if (strcaseeq(a, "gidNumber")) { 313 de = uint_list_dav(val, &pwd->pw_gid, 1); 314 } else if (strcaseeq(a, "objectSid")) { 315 de = machine_sid_dav(val, 316 pwd->pw_uid + LOCALRID_UID_MIN); 317 } else if (strcaseeq(a, "displayName")) { 318 const char *p = pwd->pw_gecos; 319 de = str_list_dav(val, &p, 1); 320 } else if (strcaseeq(a, "distinguishedName")) { 321 char *dn; 322 RDLOCK_CONFIG(); 323 (void) asprintf(&dn, 324 "uid=%s,ou=people,dc=%s", 325 pwd->pw_name, _idmapdstate.hostname); 326 UNLOCK_CONFIG(); 327 if (dn == NULL) 328 goto nomem; 329 const char *cdn = dn; 330 de = str_list_dav(val, &cdn, 1); 331 free(dn); 332 } else if (strcaseeq(a, "uid")) { 333 const char *p = pwd->pw_name; 334 de = str_list_dav(val, &p, 1); 335 } else if (strcaseeq(a, "uidNumber")) { 336 de = uint_list_dav(val, &pwd->pw_uid, 1); 337 } else if (strcaseeq(a, "gecos")) { 338 const char *p = pwd->pw_gecos; 339 de = str_list_dav(val, &p, 1); 340 } else if (strcaseeq(a, "homeDirectory")) { 341 const char *p = pwd->pw_dir; 342 de = str_list_dav(val, &p, 1); 343 } else if (strcaseeq(a, "loginShell")) { 344 const char *p = pwd->pw_shell; 345 de = str_list_dav(val, &p, 1); 346 } else if (strcaseeq(a, "x-sun-canonicalName")) { 347 char *canon; 348 RDLOCK_CONFIG(); 349 (void) asprintf(&canon, "%s@%s", 350 pwd->pw_name, _idmapdstate.hostname); 351 UNLOCK_CONFIG(); 352 if (canon == NULL) 353 goto nomem; 354 const char *ccanon = canon; 355 de = str_list_dav(val, &ccanon, 1); 356 free(canon); 357 } else if (strcaseeq(a, "x-sun-provider")) { 358 const char *provider = "UNIX-passwd"; 359 de = str_list_dav(val, &provider, 1); 360 } 361 } else if (grp != NULL) { 362 /* 363 * Handle attributes for group entries. 364 */ 365 if (strcaseeq(a, "cn")) { 366 const char *p = grp->gr_name; 367 de = str_list_dav(val, &p, 1); 368 } else if (strcaseeq(a, "objectClass")) { 369 static const char *objectClasses[] = { 370 "top", 371 "posixGroup", 372 }; 373 de = str_list_dav(val, objectClasses, 374 NELEM(objectClasses)); 375 } else if (strcaseeq(a, "gidNumber")) { 376 de = uint_list_dav(val, &grp->gr_gid, 1); 377 } else if (strcaseeq(a, "objectSid")) { 378 de = machine_sid_dav(val, 379 grp->gr_gid + LOCALRID_GID_MIN); 380 } else if (strcaseeq(a, "displayName")) { 381 const char *p = grp->gr_name; 382 de = str_list_dav(val, &p, 1); 383 } else if (strcaseeq(a, "distinguishedName")) { 384 char *dn; 385 RDLOCK_CONFIG(); 386 (void) asprintf(&dn, 387 "cn=%s,ou=group,dc=%s", 388 grp->gr_name, _idmapdstate.hostname); 389 UNLOCK_CONFIG(); 390 if (dn == NULL) 391 goto nomem; 392 const char *cdn = dn; 393 de = str_list_dav(val, &cdn, 1); 394 free(dn); 395 } else if (strcaseeq(a, "memberUid")) { 396 /* 397 * NEEDSWORK: There is probably a non-cast 398 * way to do this, but I don't immediately 399 * see it. 400 */ 401 const char * const *members = 402 (const char * const *)grp->gr_mem; 403 de = str_list_dav(val, members, 0); 404 } else if (strcaseeq(a, "x-sun-canonicalName")) { 405 char *canon; 406 RDLOCK_CONFIG(); 407 (void) asprintf(&canon, "%s@%s", 408 grp->gr_name, _idmapdstate.hostname); 409 UNLOCK_CONFIG(); 410 if (canon == NULL) 411 goto nomem; 412 const char *ccanon = canon; 413 de = str_list_dav(val, &ccanon, 1); 414 free(canon); 415 } else if (strcaseeq(a, "x-sun-provider")) { 416 const char *provider = "UNIX-group"; 417 de = str_list_dav(val, &provider, 1); 418 } 419 } 420 421 if (de != NULL) 422 return (de); 423 } 424 425 return (NULL); 426 427 nomem: 428 return (directory_error("ENOMEM.users", 429 "No memory allocating return value for user lookup", NULL)); 430 } 431 432 /* 433 * Populate a directory attribute value with a SID based on our machine SID 434 * and the specified RID. 435 * 436 * It's a bit perverse that we must take a text-format SID and turn it into 437 * a binary-format SID, only to have the caller probably turn it back into 438 * text format, but SIDs are carried across LDAP in binary format. 439 */ 440 static 441 directory_error_t 442 machine_sid_dav(directory_values_rpc *lvals, unsigned int rid) 443 { 444 sid_t *sid; 445 directory_error_t de; 446 447 RDLOCK_CONFIG(); 448 int len = strlen(_idmapdstate.cfg->pgcfg.machine_sid); 449 char buf[len + 100]; /* 100 is enough space for any RID */ 450 (void) snprintf(buf, sizeof (buf), "%s-%u", 451 _idmapdstate.cfg->pgcfg.machine_sid, rid); 452 UNLOCK_CONFIG(); 453 454 sid = sid_fromstr(buf); 455 if (sid == NULL) 456 goto nomem; 457 458 sid_to_le(sid); 459 460 de = bin_list_dav(lvals, sid, 1, sid_len(sid)); 461 sid_free(sid); 462 return (de); 463 464 nomem: 465 return (directory_error("ENOMEM.machine_sid_dav", 466 "Out of memory allocating return value for lookup", NULL)); 467 } 468 469 struct directory_provider_static directory_provider_nsswitch = { 470 "files", 471 directory_provider_nsswitch_get, 472 }; 473