/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include "ns_sldap.h" #include #include #include #include #include #include "passwdutil.h" /* * name_to_int(rep) * * Translate the repository to a bitmask. * if we don't recognise the repository name, we return REP_ERANGE */ int name_to_int(char *rep_name) { int result = REP_ERANGE; if (strcmp(rep_name, "files") == 0) result = REP_FILES; else if (strcmp(rep_name, "nis") == 0) result = REP_NIS; else if (strcmp(rep_name, "ldap") == 0) result = REP_LDAP; else if (strcmp(rep_name, "compat") == 0) { struct __nsw_switchconfig *cfg; enum __nsw_parse_err pserr; cfg = __nsw_getconfig("passwd_compat", &pserr); if (cfg == NULL) { result = REP_FILES | REP_NIS; } else { if (strcmp(cfg->lookups->service_name, "ldap") == 0) result = REP_FILES | REP_LDAP; else result = REP_ERANGE; __nsw_freeconfig(cfg); } } return (result); } /* * Figure out which repository we use in compat mode. */ int get_compat_mode(void) { struct __nsw_switchconfig *cfg; enum __nsw_parse_err pserr; int result = REP_COMPAT_NIS; if ((cfg = __nsw_getconfig("passwd_compat", &pserr)) != NULL) { if (strcmp(cfg->lookups->service_name, "ldap") == 0) result = REP_COMPAT_LDAP; } __nsw_freeconfig(cfg); return (result); } /* * get_ns(rep, accesstype) * * returns a bitmask of repositories to use based on either * 1. the repository that is given as argument * 2. the nsswitch.conf file * 3. the type of access requested * * "accesstype" indicates whether we are reading from or writing to the * repository. We need to know this since "compat" will translate into * REP_NSS (the nss-switch) for READ access (needed to decode * the black-magic '+' entries) but it translates into a bitmask * on WRITE access. * * If we detect read-access in compat mode, we augment the result * with one of REP_COMPAT_{NIS,LDAP}. We need this in order to * implement ATTR_REP_NAME in nss_getpwnam. * * A return value of REP_NOREP indicates an error. */ int get_ns(pwu_repository_t *rep, int accesstype) { struct __nsw_switchconfig *conf = NULL; enum __nsw_parse_err pserr; struct __nsw_lookup *lkp; struct __nsw_lookup *lkp2; struct __nsw_lookup *lkp3; struct __nsw_lookup *lkpn; int result = REP_NOREP; if (rep != PWU_DEFAULT_REP) { result = name_to_int(rep->type); return (result); } conf = __nsw_getconfig("passwd", &pserr); if (conf == NULL) { /* * No config found. The user didn't supply a repository, * so we try to change the password in the default * repositories (files and nis) even though we cannot * find the name service switch entry. (Backward compat) */ syslog(LOG_ERR, "passwdutil.so: nameservice switch entry for " "passwd not found."); result = REP_FILES | REP_NIS; return (result); } lkp = conf->lookups; /* * Supported nsswitch.conf can have a maximum of 3 repositories. * If we encounter an unsupported nsswitch.conf, we return REP_NSS * to fall back to the nsswitch backend. * * Note that specifying 'ad' in the configuration is acceptable * though changing AD users' passwords through passwd(1) is not. * Therefore "ad" will be silently ignored. */ if (conf->num_lookups == 1) { /* files or compat */ if (strcmp(lkp->service_name, "files") == 0) { result = name_to_int(lkp->service_name); } else if (strcmp(lkp->service_name, "compat") == 0) { if (accesstype == PWU_READ) result = REP_NSS | get_compat_mode(); else result = name_to_int(lkp->service_name); } else result = REP_NSS; } else if (conf->num_lookups == 2) { lkp2 = lkp->next; if (strcmp(lkp->service_name, "files") == 0) { result = REP_FILES; if (strcmp(lkp2->service_name, "ldap") == 0) result |= REP_LDAP; else if (strcmp(lkp2->service_name, "nis") == 0) result |= REP_NIS; else if (strcmp(lkp2->service_name, "ad") != 0) result = REP_NSS; /* AD is ignored */ } else { result = REP_NSS; } } else if (conf->num_lookups == 3) { /* * Valid configurations with 3 repositories are: * files ad [nis | ldap ] OR * files [nis | ldap ] ad */ lkp2 = lkp->next; lkp3 = lkp2->next; if (strcmp(lkp2->service_name, "ad") == 0) lkpn = lkp3; else if (strcmp(lkp3->service_name, "ad") == 0) lkpn = lkp2; else lkpn = NULL; if (strcmp(lkp->service_name, "files") == 0 && lkpn != NULL) { result = REP_FILES; if (strcmp(lkpn->service_name, "ldap") == 0) result |= REP_LDAP; else if (strcmp(lkpn->service_name, "nis") == 0) result |= REP_NIS; else result = REP_NSS; } else { result = REP_NSS; } } else { result = REP_NSS; } __nsw_freeconfig(conf); return (result); } static void nss_ldap_passwd(p) nss_db_params_t *p; { p->name = NSS_DBNAM_PASSWD; p->flags |= NSS_USE_DEFAULT_CONFIG; p->default_config = "ldap"; } static void nss_ldap_shadow(p) nss_db_params_t *p; { p->name = NSS_DBNAM_SHADOW; p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ p->flags |= NSS_USE_DEFAULT_CONFIG; p->default_config = "ldap"; } #ifdef PAM_NIS static void nss_nis_passwd(p) nss_db_params_t *p; { p->name = NSS_DBNAM_PASSWD; p->flags |= NSS_USE_DEFAULT_CONFIG; p->default_config = "nis"; } static void nss_nis_shadow(p) nss_db_params_t *p; { p->name = NSS_DBNAM_SHADOW; p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ p->flags |= NSS_USE_DEFAULT_CONFIG; p->default_config = "nis"; } #endif /* PAM_NIS */ static char * gettok(nextpp) char **nextpp; { char *p = *nextpp; char *q = p; char c; if (p == 0) { return (0); } while ((c = *q) != '\0' && c != ':') { q++; } if (c == '\0') { *nextpp = 0; } else { *q++ = '\0'; *nextpp = q; } return (p); } /* * Return values: 0 = success, 1 = parse error, 2 = erange ... * The structure pointer passed in is a structure in the caller's space * wherein the field pointers would be set to areas in the buffer if * need be. instring and buffer should be separate areas. */ static int str2passwd(const char *instr, int lenstr, void *ent, char *buffer, int buflen) { struct passwd *passwd = (struct passwd *)ent; char *p, *next; int black_magic; /* "+" or "-" entry */ if (lenstr + 1 > buflen) { return (NSS_STR_PARSE_ERANGE); } /* * We copy the input string into the output buffer and * operate on it in place. */ (void) memcpy(buffer, instr, lenstr); buffer[lenstr] = '\0'; next = buffer; passwd->pw_name = p = gettok(&next); /* username */ if (*p == '\0') { /* Empty username; not allowed */ return (NSS_STR_PARSE_PARSE); } black_magic = (*p == '+' || *p == '-'); if (black_magic) { passwd->pw_uid = UID_NOBODY; passwd->pw_gid = GID_NOBODY; /* * pwconv tests pw_passwd and pw_age == NULL */ passwd->pw_passwd = ""; passwd->pw_age = ""; /* * the rest of the passwd entry is "optional" */ passwd->pw_comment = ""; passwd->pw_gecos = ""; passwd->pw_dir = ""; passwd->pw_shell = ""; } passwd->pw_passwd = p = gettok(&next); /* password */ if (p == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } for (; *p != '\0'; p++) { /* age */ if (*p == ',') { *p++ = '\0'; break; } } passwd->pw_age = p; p = next; /* uid */ if (p == 0 || *p == '\0') { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } if (!black_magic) { passwd->pw_uid = strtol(p, &next, 10); if (next == p) { /* uid field should be nonempty */ return (NSS_STR_PARSE_PARSE); } /* * The old code (in 2.0 thru 2.5) would check * for the uid being negative, or being greater * than 60001 (the rfs limit). If it met either of * these conditions, the uid was translated to 60001. * * Now we just check for ephemeral uids; anything else * is administrative policy */ if (passwd->pw_uid > MAXUID) passwd->pw_uid = UID_NOBODY; } if (*next++ != ':') { if (black_magic) p = gettok(&next); else return (NSS_STR_PARSE_PARSE); } p = next; /* gid */ if (p == 0 || *p == '\0') { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } if (!black_magic) { passwd->pw_gid = strtol(p, &next, 10); if (next == p) { /* gid field should be nonempty */ return (NSS_STR_PARSE_PARSE); } /* * gid should be non-negative; anything else * is administrative policy. */ if (passwd->pw_gid > MAXUID) passwd->pw_gid = GID_NOBODY; } if (*next++ != ':') { if (black_magic) p = gettok(&next); else return (NSS_STR_PARSE_PARSE); } passwd->pw_gecos = passwd->pw_comment = p = gettok(&next); if (p == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } passwd->pw_dir = p = gettok(&next); if (p == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } passwd->pw_shell = p = gettok(&next); if (p == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } /* Better not be any more fields... */ if (next == 0) { /* Successfully parsed and stored */ return (NSS_STR_PARSE_SUCCESS); } return (NSS_STR_PARSE_PARSE); } typedef const char *constp; /* * Return value 1 means success and more input, 0 means error or no more */ static int getfield(nextp, limit, uns, valp) constp *nextp; constp limit; int uns; void *valp; { constp p = *nextp; char *endfield; char numbuf[12]; /* Holds -2^31 and trailing ':' */ int len; long x; unsigned long ux; if (p == 0 || p >= limit) { return (0); } if (*p == ':') { p++; *nextp = p; return (p < limit); } if ((len = limit - p) > sizeof (numbuf) - 1) { len = sizeof (numbuf) - 1; } /* * We want to use strtol() and we have a readonly non-zero-terminated * string, so first we copy and terminate the interesting bit. * Ugh. (It's convenient to terminate with a colon rather than \0). */ if ((endfield = memccpy(numbuf, p, ':', len)) == 0) { if (len != limit - p) { /* Error -- field is too big to be a legit number */ return (0); } numbuf[len] = ':'; p = limit; } else { p += (endfield - numbuf); } if (uns) { ux = strtoul(numbuf, &endfield, 10); if (*endfield != ':') { /* Error -- expected */ return (0); } *((unsigned int *)valp) = (unsigned int)ux; } else { x = strtol(numbuf, &endfield, 10); if (*endfield != ':') { /* Error -- expected */ return (0); } *((int *)valp) = (int)x; } *nextp = p; return (p < limit); } /* * str2spwd() -- convert a string to a shadow passwd entry. The parser is * more liberal than the passwd or group parsers; since it's legitimate * for almost all the fields here to be blank, the parser lets one omit * any number of blank fields at the end of the entry. The acceptable * forms for '+' and '-' entries are the same as those for normal entries. * === Is this likely to do more harm than good? * * Return values: 0 = success, 1 = parse error, 2 = erange ... * The structure pointer passed in is a structure in the caller's space * wherein the field pointers would be set to areas in the buffer if * need be. instring and buffer should be separate areas. */ int str2spwd(instr, lenstr, ent, buffer, buflen) const char *instr; int lenstr; void *ent; /* really (struct spwd *) */ char *buffer; int buflen; { struct spwd *shadow = (struct spwd *)ent; const char *p = instr, *limit; char *bufp; int lencopy, black_magic; limit = p + lenstr; if ((p = memchr(instr, ':', lenstr)) == 0 || ++p >= limit || (p = memchr(p, ':', limit - p)) == 0) { lencopy = lenstr; p = 0; } else { lencopy = p - instr; p++; } if (lencopy + 1 > buflen) { return (NSS_STR_PARSE_ERANGE); } (void) memcpy(buffer, instr, lencopy); buffer[lencopy] = 0; black_magic = (*instr == '+' || *instr == '-'); shadow->sp_namp = bufp = buffer; shadow->sp_pwdp = 0; shadow->sp_lstchg = -1; shadow->sp_min = -1; shadow->sp_max = -1; shadow->sp_warn = -1; shadow->sp_inact = -1; shadow->sp_expire = -1; shadow->sp_flag = 0; if ((bufp = strchr(bufp, ':')) == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } *bufp++ = '\0'; shadow->sp_pwdp = bufp; if (instr == 0) { if ((bufp = strchr(bufp, ':')) == 0) { if (black_magic) return (NSS_STR_PARSE_SUCCESS); else return (NSS_STR_PARSE_PARSE); } *bufp++ = '\0'; p = bufp; } /* else p was set when we copied name and passwd into the buffer */ if (!getfield(&p, limit, 0, &shadow->sp_lstchg)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 0, &shadow->sp_min)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 0, &shadow->sp_max)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 0, &shadow->sp_warn)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 0, &shadow->sp_inact)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 0, &shadow->sp_expire)) return (NSS_STR_PARSE_SUCCESS); if (!getfield(&p, limit, 1, &shadow->sp_flag)) return (NSS_STR_PARSE_SUCCESS); if (p != limit) { /* Syntax error -- garbage at end of line */ return (NSS_STR_PARSE_PARSE); } return (NSS_STR_PARSE_SUCCESS); } static nss_XbyY_buf_t *buffer; static DEFINE_NSS_DB_ROOT(db_root); #define GETBUF() \ NSS_XbyY_ALLOC(&buffer, sizeof (struct passwd), NSS_BUFLEN_PASSWD) #pragma fini(endutilpwent) static void endutilpwent(void) { NSS_XbyY_FREE(&buffer); nss_delete(&db_root); } /*ARGSUSED*/ struct passwd * getpwnam_from(const char *name, pwu_repository_t *rep, int reptype) { nss_XbyY_buf_t *b = GETBUF(); nss_XbyY_args_t arg; if (b == 0) return (0); NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd); arg.key.name = name; switch (reptype) { case REP_LDAP: (void) nss_search(&db_root, nss_ldap_passwd, NSS_DBOP_PASSWD_BYNAME, &arg); break; #ifdef PAM_NIS case REP_NIS: (void) nss_search(&db_root, nss_nis_passwd, NSS_DBOP_PASSWD_BYNAME, &arg); break; #endif default: return (NULL); } return (struct passwd *)NSS_XbyY_FINI(&arg); } /*ARGSUSED*/ struct passwd * getpwuid_from(uid_t uid, pwu_repository_t *rep, int reptype) { nss_XbyY_buf_t *b = GETBUF(); nss_XbyY_args_t arg; if (b == 0) return (0); NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd); arg.key.uid = uid; switch (reptype) { case REP_LDAP: (void) nss_search(&db_root, nss_ldap_passwd, NSS_DBOP_PASSWD_BYUID, &arg); break; #ifdef PAM_NIS case REP_NIS: (void) nss_search(&db_root, nss_nis_passwd, NSS_DBOP_PASSWD_BYUID, &arg); break; #endif default: return (NULL); } return (struct passwd *)NSS_XbyY_FINI(&arg); } static nss_XbyY_buf_t *spbuf; static DEFINE_NSS_DB_ROOT(spdb_root); #define GETSPBUF() \ NSS_XbyY_ALLOC(&spbuf, sizeof (struct spwd), NSS_BUFLEN_SHADOW) #pragma fini(endutilspent) static void endutilspent(void) { NSS_XbyY_FREE(&spbuf); nss_delete(&spdb_root); } /*ARGSUSED*/ struct spwd * getspnam_from(const char *name, pwu_repository_t *rep, int reptype) { nss_XbyY_buf_t *b = GETSPBUF(); nss_XbyY_args_t arg; if (b == 0) return (0); NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2spwd); arg.key.name = name; switch (reptype) { case REP_LDAP: (void) nss_search(&spdb_root, nss_ldap_shadow, NSS_DBOP_SHADOW_BYNAME, &arg); break; #ifdef PAM_NIS case REP_NIS: (void) nss_search(&spdb_root, nss_nis_shadow, NSS_DBOP_SHADOW_BYNAME, &arg); break; #endif default: return (NULL); } return (struct spwd *)NSS_XbyY_FINI(&arg); }