diff options
author | Baban Kenkre <Baban.Kenkre@Sun.COM> | 2008-11-07 12:09:53 -0800 |
---|---|---|
committer | Baban Kenkre <Baban.Kenkre@Sun.COM> | 2008-11-07 12:09:53 -0800 |
commit | 2b4a78020b9c38d1b95e2f3fefa6d6e4be382d1f (patch) | |
tree | b9f0bc817d950cefb1af4653dad8de547a17e061 /usr/src/lib/nsswitch/ad/common/getpwnam.c | |
parent | 0a2b1d27cac02f57e17b310f8baeb1dda082c83a (diff) | |
download | illumos-joyent-2b4a78020b9c38d1b95e2f3fefa6d6e4be382d1f.tar.gz |
PSARC/2008/441 Active Directory name service module (nss_ad)
6722476 name service switch module for AD (nss_ad) needed
Diffstat (limited to 'usr/src/lib/nsswitch/ad/common/getpwnam.c')
-rw-r--r-- | usr/src/lib/nsswitch/ad/common/getpwnam.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/usr/src/lib/nsswitch/ad/common/getpwnam.c b/usr/src/lib/nsswitch/ad/common/getpwnam.c new file mode 100644 index 0000000000..ece50a54b0 --- /dev/null +++ b/usr/src/lib/nsswitch/ad/common/getpwnam.c @@ -0,0 +1,518 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#include <pwd.h> +#include <idmap.h> +#include <ctype.h> +#include "ad_common.h" + +/* passwd attributes and filters */ +#define _PWD_DN "dn" +#define _PWD_SAN "sAMAccountName" +#define _PWD_OBJSID "objectSid" +#define _PWD_PRIMARYGROUPID "primaryGroupID" +#define _PWD_CN "cn" +#define _PWD_HOMEDIRECTORY "homedirectory" +#define _PWD_LOGINSHELL "loginshell" +#define _PWD_OBJCLASS "objectClass" + +#define _F_GETPWNAM "(sAMAccountName=%.*s)" +#define _F_GETPWUID "(objectSid=%s)" + +static const char *pwd_attrs[] = { + _PWD_SAN, + _PWD_OBJSID, + _PWD_PRIMARYGROUPID, + _PWD_CN, + _PWD_HOMEDIRECTORY, + _PWD_LOGINSHELL, + _PWD_OBJCLASS, + (char *)NULL +}; + +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) +{ + int buflen; + char *buffer; + + if (be->db_type == NSS_AD_DB_PASSWD_BYNAME) { + /* + * The canonical name obtained from AD lookup may not match + * the case of the name (i.e. key) in the request. Therefore, + * use the name from the request to construct the result. + */ + buflen = snprintf(NULL, 0, "%s:%s:%u:%u:%s:%s:%s", + argp->key.name, "x", uid, gid, gecos, homedir, shell) + 1; + } else { + if (domain == NULL) + domain = WK_DOMAIN; + buflen = snprintf(NULL, 0, "%s@%s:%s:%u:%u:%s:%s:%s", + name, domain, "x", uid, gid, gecos, homedir, shell) + 1; + } + + + if (argp->buf.result != NULL) { + buffer = be->buffer = malloc(buflen); + if (be->buffer == NULL) + return (-1); + be->buflen = buflen; + } else { + if (buflen > argp->buf.buflen) + return (-1); + buflen = argp->buf.buflen; + buffer = argp->buf.buffer; + } + + if (be->db_type == NSS_AD_DB_PASSWD_BYNAME) + (void) snprintf(buffer, buflen, "%s:%s:%u:%u:%s:%s:%s", + argp->key.name, "x", uid, gid, gecos, homedir, shell); + else + (void) snprintf(buffer, buflen, "%s@%s:%s:%u:%u:%s:%s:%s", + name, domain, "x", uid, gid, gecos, homedir, shell); + return (0); +} + + +#define NET_SCHEME "/net" + +/* + * 1) If the homeDirectory string is in UNC format then convert it into + * a /net format. This needs to be revisited later but is fine for now + * because Solaris does not support -hosts automount map for CIFS yet. + * + * 2) If homeDirectory contains ':' then return NULL because ':' is the + * delimiter in passwd entries and may break apps that parse these entries. + * + * 3) For all other cases return the same string that was passed to + * this function. + */ +static +char * +process_homedir(char *homedir) +{ + size_t len, smb_len; + char *smb_homedir; + int i, slash = 0; + + len = strlen(homedir); + + if (strchr(homedir, ':') != NULL) + /* + * Ignore paths that have colon ':' because ':' is a + * delimiter for the passwd entry. + */ + return (NULL); + + if (!(len > 1 && homedir[0] == '\\' && homedir[1] == '\\')) + /* Keep homedir intact if not in UNC format */ + return (homedir); + + /* + * Convert UNC string into /net format + * Example: \\server\abc -> /net/server/abc + */ + smb_len = len + 1 + sizeof (NET_SCHEME); + if ((smb_homedir = calloc(1, smb_len)) == NULL) + return (NULL); + (void) strlcpy(smb_homedir, NET_SCHEME, smb_len); + for (i = strlen(smb_homedir); *homedir != '\0'; homedir++) { + if (*homedir == '\\') { + /* Reduce double backslashes into one */ + if (slash) + slash = 0; + else { + slash = 1; + smb_homedir[i++] = '/'; + } + } else { + smb_homedir[i++] = *homedir; + slash = 0; + } + } + return (smb_homedir); +} + +/* + * _nss_ad_passwd2str is the data marshaling method for the passwd getXbyY + * (e.g., getbyuid(), getbyname(), getpwent()) backend processes. This method is + * called after a successful AD search has been performed. This method will + * parse the AD search values into the file format. + * e.g. + * + * blue@whale:x:123456:10:Blue Whale:/: + * + */ +static int +_nss_ad_passwd2str(ad_backend_ptr be, nss_XbyY_args_t *argp) +{ + int nss_result; + adutils_result_t *result = be->result; + const adutils_entry_t *entry; + char **sid_v, *ptr, **pgid_v, *end; + ulong_t tmp; + uint32_t urid, grid; + uid_t uid; + gid_t gid; + idmap_stat gstat; + idmap_get_handle_t *ig = NULL; + char **name_v, **dn_v, *domain = NULL; + char **gecos_v, **shell_v; + char **homedir_v = NULL, *homedir = NULL; + char *NULL_STR = ""; + + if (result == NULL) + return (NSS_STR_PARSE_PARSE); + entry = adutils_getfirstentry(result); + nss_result = NSS_STR_PARSE_PARSE; + + /* Create handles for idmap service */ + if (be->ih == NULL && idmap_init(&be->ih) != 0) + goto result_pwd2str; + if (idmap_get_create(be->ih, &ig) != 0) + goto result_pwd2str; + + /* Get name */ + name_v = adutils_getattr(entry, _PWD_SAN); + if (name_v == NULL || name_v[0] == NULL || *name_v[0] == '\0') + goto result_pwd2str; + + /* Get domain */ + dn_v = adutils_getattr(entry, _PWD_DN); + if (dn_v == NULL || dn_v[0] == NULL || *dn_v[0] == '\0') + goto result_pwd2str; + domain = adutils_dn2dns(dn_v[0]); + + /* Get objectSID (in text format) */ + sid_v = adutils_getattr(entry, _PWD_OBJSID); + if (sid_v == NULL || sid_v[0] == NULL || *sid_v[0] == '\0') + goto result_pwd2str; + + /* Break SID into prefix and rid */ + if ((ptr = strrchr(sid_v[0], '-')) == NULL) + goto result_pwd2str; + *ptr = '\0'; + end = ++ptr; + tmp = strtoul(ptr, &end, 10); + if (end == ptr || tmp > UINT32_MAX) + goto result_pwd2str; + urid = (uint32_t)tmp; + + /* We already have uid -- no need to call idmapd */ + if (be->db_type == NSS_AD_DB_PASSWD_BYUID) + uid = argp->key.uid; + else + uid = be->uid; + + /* Get primaryGroupID */ + pgid_v = adutils_getattr(entry, _PWD_PRIMARYGROUPID); + if (pgid_v == NULL || pgid_v[0] == NULL || *pgid_v[0] == '\0') + /* + * If primaryGroupID is not found then we request + * a GID to be mapped to the given user's objectSID + * (diagonal mapping) and use this GID as the primary + * GID for the entry. + */ + grid = urid; + else { + end = pgid_v[0]; + tmp = strtoul(pgid_v[0], &end, 10); + if (end == pgid_v[0] || tmp > UINT32_MAX) + goto result_pwd2str; + grid = (uint32_t)tmp; + } + + /* Map group SID to GID using idmap service */ + if (idmap_get_gidbysid(ig, sid_v[0], grid, 0, &gid, &gstat) != 0) + goto result_pwd2str; + if (idmap_get_mappings(ig) != 0 || gstat != 0) { + RESET_ERRNO(); + goto result_pwd2str; + } + + /* Get gecos, homedirectory and shell information if available */ + gecos_v = adutils_getattr(entry, _PWD_CN); + if (gecos_v == NULL || gecos_v[0] == NULL || *gecos_v[0] == '\0') + gecos_v = &NULL_STR; + + homedir_v = adutils_getattr(entry, _PWD_HOMEDIRECTORY); + if (homedir_v == NULL || homedir_v[0] == NULL || *homedir_v[0] == '\0') + homedir = NULL_STR; + else if ((homedir = process_homedir(homedir_v[0])) == NULL) + homedir = NULL_STR; + + shell_v = adutils_getattr(entry, _PWD_LOGINSHELL); + if (shell_v == NULL || shell_v[0] == NULL || *shell_v[0] == '\0') + shell_v = &NULL_STR; + + if (update_buffer(be, argp, name_v[0], domain, uid, gid, + gecos_v[0], homedir, shell_v[0]) < 0) + nss_result = NSS_STR_PARSE_ERANGE; + else + nss_result = NSS_STR_PARSE_SUCCESS; + +result_pwd2str: + idmap_get_destroy(ig); + (void) idmap_fini(be->ih); + be->ih = NULL; + (void) adutils_freeresult(&be->result); + free(domain); + if (homedir != NULL_STR && homedir_v != NULL && + homedir != homedir_v[0]) + free(homedir); + return ((int)nss_result); +} + +/* + * getbyname gets a passwd entry by winname. This function constructs an ldap + * search filter using the name invocation parameter and the getpwnam search + * filter defined. Once the filter is constructed, we search for a matching + * entry and marshal the data results into struct passwd for the frontend + * process. The function _nss_ad_passwd2ent performs the data marshaling. + */ + +static nss_status_t +getbyname(ad_backend_ptr be, void *a) +{ + nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a; + char *searchfilter; + char name[SEARCHFILTERLEN]; + char *dname; + int filterlen, namelen; + int flag; + nss_status_t stat; + idmap_stat idmaprc; + uid_t uid; + gid_t gid; + int is_user, is_wuser, try_idmap; + idmap_handle_t *ih; + + be->db_type = NSS_AD_DB_PASSWD_BYNAME; + + /* Sanitize name so that it can be used in our LDAP filter */ + if (_ldap_filter_name(name, argp->key.name, sizeof (name)) != 0) + return ((nss_status_t)NSS_NOTFOUND); + + if ((dname = strchr(name, '@')) == NULL) + return ((nss_status_t)NSS_NOTFOUND); + + *dname = '\0'; + dname++; + + /* + * Map the given name to UID using idmap service. If idmap + * call fails then this will save us doing AD discovery and + * AD lookup here. + */ + if (idmap_init(&be->ih) != IDMAP_SUCCESS) + return ((nss_status_t)NSS_NOTFOUND); + flag = (strcasecmp(dname, WK_DOMAIN) == 0) ? + IDMAP_REQ_FLG_WK_OR_LOCAL_SIDS_ONLY : 0; + is_wuser = -1; + is_user = 1; + if (idmap_get_w2u_mapping(be->ih, NULL, NULL, name, + dname, flag, &is_user, &is_wuser, &be->uid, NULL, + NULL, NULL) != IDMAP_SUCCESS) { + (void) idmap_fini(be->ih); + be->ih = NULL; + RESET_ERRNO(); + return ((nss_status_t)NSS_NOTFOUND); + } + + /* If this is not a Well-Known SID then try AD lookup. */ + if (strcasecmp(dname, WK_DOMAIN) != 0) { + /* Assemble filter using the given name */ + namelen = strlen(name); + filterlen = snprintf(NULL, 0, _F_GETPWNAM, namelen, name) + 1; + if ((searchfilter = (char *)malloc(filterlen)) == NULL) + return ((nss_status_t)NSS_NOTFOUND); + (void) snprintf(searchfilter, filterlen, _F_GETPWNAM, + namelen, name); + stat = _nss_ad_lookup(be, argp, _PASSWD, searchfilter, + dname, &try_idmap); + free(searchfilter); + + if (!try_idmap) { + (void) idmap_fini(be->ih); + be->ih = NULL; + return (stat); + } + + } + + /* + * Either this is a Well-Known SID or AD lookup failed. Map + * the given name to GID using idmap service and construct + * the passwd entry. + */ + is_wuser = -1; + is_user = 0; /* Map name to primary gid */ + idmaprc = idmap_get_w2u_mapping(be->ih, NULL, NULL, name, dname, + flag, &is_user, &is_wuser, &gid, NULL, NULL, NULL); + (void) idmap_fini(be->ih); + be->ih = NULL; + if (idmaprc != IDMAP_SUCCESS) { + RESET_ERRNO(); + return ((nss_status_t)NSS_NOTFOUND); + } + + /* Create passwd(4) style string */ + if (update_buffer(be, argp, name, dname, + be->uid, gid, "", "", "") < 0) + return ((nss_status_t)NSS_NOTFOUND); + + /* Marshall the data, sanitize the return status and return */ + stat = _nss_ad_marshall_data(be, argp); + return (_nss_ad_sanitize_status(be, argp, stat)); +} + + +/* + * getbyuid gets a passwd entry by uid number. This function constructs an ldap + * search filter using the uid invocation parameter and the getpwuid search + * filter defined. Once the filter is constructed, we search for a matching + * entry and marshal the data results into struct passwd for the frontend + * process. The function _nss_ad_passwd2ent performs the data marshaling. + */ + +static nss_status_t +getbyuid(ad_backend_ptr be, void *a) +{ + nss_XbyY_args_t *argp = (nss_XbyY_args_t *)a; + char searchfilter[ADUTILS_MAXHEXBINSID + 14]; + char *sidprefix = NULL; + idmap_rid_t rid; + char cbinsid[ADUTILS_MAXHEXBINSID + 1]; + char *winname = NULL, *windomain = NULL; + int is_user, is_wuser; + gid_t gid; + idmap_stat idmaprc; + int ret, try_idmap; + nss_status_t stat; + + be->db_type = NSS_AD_DB_PASSWD_BYUID; + + stat = (nss_status_t)NSS_NOTFOUND; + + /* nss_ad does not support non ephemeral uids */ + if (argp->key.uid <= MAXUID) + goto out; + + /* Map the given UID to a SID using the idmap service */ + if (idmap_init(&be->ih) != 0) + goto out; + if (idmap_get_u2w_mapping(be->ih, &argp->key.uid, NULL, 0, + 1, NULL, &sidprefix, &rid, &winname, &windomain, + NULL, NULL) != 0) { + RESET_ERRNO(); + goto out; + } + + /* + * NULL winname implies a local SID or unresolvable SID both of + * which cannot be used to generated passwd(4) entry + */ + if (winname == NULL) + goto out; + + /* If this is not a Well-Known SID try AD lookup */ + if (windomain != NULL && strcasecmp(windomain, WK_DOMAIN) != 0) { + if (adutils_txtsid2hexbinsid(sidprefix, &rid, + &cbinsid[0], sizeof (cbinsid)) != 0) + goto out; + + ret = snprintf(searchfilter, sizeof (searchfilter), + _F_GETPWUID, cbinsid); + if (ret >= sizeof (searchfilter) || ret < 0) + goto out; + + stat = _nss_ad_lookup(be, argp, _PASSWD, searchfilter, + windomain, &try_idmap); + + if (!try_idmap) + goto out; + } + + /* Map winname to primary gid using idmap service */ + is_user = 0; + is_wuser = -1; + idmaprc = idmap_get_w2u_mapping(be->ih, NULL, NULL, + winname, windomain, 0, &is_user, &is_wuser, &gid, + NULL, NULL, NULL); + + (void) idmap_fini(be->ih); + be->ih = NULL; + + if (idmaprc != IDMAP_SUCCESS) { + RESET_ERRNO(); + goto out; + } + + /* Create passwd(4) style string */ + if (update_buffer(be, argp, winname, windomain, + argp->key.uid, gid, "", "", "") < 0) + goto out; + + /* Marshall the data, sanitize the return status and return */ + stat = _nss_ad_marshall_data(be, argp); + stat = _nss_ad_sanitize_status(be, argp, stat); + +out: + idmap_free(sidprefix); + idmap_free(winname); + idmap_free(windomain); + (void) idmap_fini(be->ih); + be->ih = NULL; + return (stat); +} + +static ad_backend_op_t passwd_ops[] = { + _nss_ad_destr, + _nss_ad_endent, + _nss_ad_setent, + _nss_ad_getent, + getbyname, + getbyuid +}; + +/* + * _nss_ad_passwd_constr is where life begins. This function calls the + * generic AD constructor function to define and build the abstract + * data types required to support AD operations. + */ + +/*ARGSUSED0*/ +nss_backend_t * +_nss_ad_passwd_constr(const char *dummy1, const char *dummy2, + const char *dummy3) +{ + + return ((nss_backend_t *)_nss_ad_constr(passwd_ops, + sizeof (passwd_ops)/sizeof (passwd_ops[0]), + _PASSWD, pwd_attrs, _nss_ad_passwd2str)); +} |