Home | History | Annotate | Download | only in idmapd
      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