>From d1415c9b27fb219c4e992906ba721d12ffd25c40 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Fri, 9 Oct 2009 13:34:35 +0200 Subject: [PATCH] Check for expired passwords in LDAP provider --- server/providers/ldap/ldap_auth.c | 370 +++++++++++++++++++++++++++++++++-- server/providers/ldap/ldap_common.c | 24 ++- server/providers/ldap/sdap.h | 28 +++- server/providers/ldap/sdap_async.c | 25 +++ 4 files changed, 424 insertions(+), 23 deletions(-) diff --git a/server/providers/ldap/ldap_auth.c b/server/providers/ldap/ldap_auth.c index 80726e7..971ece3 100644 --- a/server/providers/ldap/ldap_auth.c +++ b/server/providers/ldap/ldap_auth.c @@ -29,9 +29,13 @@ #define LDAP_TAG_EXOP_MODIFY_PASSWD_NEW ((ber_tag_t) 0x82U) #endif +#define _XOPEN_SOURCE 500 /* for strptime() */ +#include +#undef _XOPEN_SOURCE #include #include +#include #include #include "util/util.h" @@ -40,11 +44,219 @@ #include "providers/ldap/ldap_common.h" #include "providers/ldap/sdap_async.h" +enum pwexpire { + PWEXPIRE_NONE = 0, + PWEXPIRE_LDAP_PASSWORD_POLICY, + PWEXPIRE_KERBEROS, + PWEXPIRE_SHADOW +}; + struct sdap_auth_ctx { struct be_ctx *be; struct sdap_options *opts; }; +static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now, + enum sdap_result *result) +{ + char *end; + struct tm tm = {0, 0, 0, 0, 0, 0, 0, 0, 0}; + time_t expire_time; + + *result = SDAP_AUTH_FAILED; + + end = strptime(expire_date, "%Y%m%d%H%M%SZ", &tm); + if (end == NULL) { + DEBUG(1, ("Kerberos expire date [%s] invalid.\n", expire_date)); + return EINVAL; + } + if (*end != '\0') { + DEBUG(1, ("Kerberos expire date [%s] contains extra characters.\n", + expire_date)); + return EINVAL; + } + + expire_time = mktime(&tm); + if (expire_time == -1) { + DEBUG(1, ("mktime failed to convert [%s].\n", expire_date)); + return EINVAL; + } + + tzset(); + expire_time -= timezone; + DEBUG(9, ("Time info: tzname[0] [%s] tzname[1] [%s] timezone [%d] " + "daylight [%d] now [%d] expire_time [%d].\n", tzname[0], + tzname[1], timezone, daylight, now, expire_time)); + + if (difftime(now, expire_time) > 0.0) { + DEBUG(4, ("Kerberos password expired.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + } else { + *result = SDAP_AUTH_SUCCESS; + } + + return EOK; +} + +static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now, + enum sdap_result *result) +{ + long today; + long password_age; + + if (spwd->sp_lstchg <= 0) { + DEBUG(4, ("Last change day is not set, new password needed.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + return EOK; + } + + today = (long) (now / (60 * 60 *24)); + password_age = today - spwd->sp_lstchg; + if (password_age < 0) { + DEBUG(2, ("The last password change time is in the future!.\n")); + *result = SDAP_AUTH_SUCCESS; + return EOK; + } + + if ((spwd->sp_expire != -1 && today > spwd->sp_expire) || + (spwd->sp_max != -1 && spwd->sp_inact != -1 && + password_age > spwd->sp_max + spwd->sp_inact)) + { + DEBUG(4, ("Account expired.\n")); + *result = SDAP_ACCT_EXPIRED; + return EOK; + } + + if (spwd->sp_max != -1 && password_age > spwd->sp_max) { + DEBUG(4, ("Password expired.\n")); + *result = SDAP_AUTH_PW_EXPIRED; + return EOK; + } + +/* TODO: evaluate spwd->min and spwd->warn */ + + *result = SDAP_AUTH_SUCCESS; + return EOK; +} + +static errno_t string_to_shadowpw_days(const char *s, long *d) +{ + long l; + char *endptr; + + if (s == NULL || *s == '\0') { + *d = -1; + return EOK; + } + + errno = 0; + l = strtol(s, &endptr, 10); + if (errno != 0) { + DEBUG(1, ("strtol failed [%d][%s].\n", errno, strerror(errno))); + return errno; + } + + if (*endptr != '\0') { + DEBUG(1, ("Input string [%s] is invalid.\n", s)); + return EINVAL; + } + + if (*d < -1) { + DEBUG(1, ("Input string contains not allowed negative value [%d].\n", + *d)); + return EINVAL; + } + + *d = l; + + return EOK; +} + +static errno_t find_password_expiration_attributes(TALLOC_CTX *mem_ctx, + const struct ldb_message *msg, + enum pwexpire *type, void **data) +{ + const char *mark; + const char *val; + struct spwd *spwd; + int ret; + + *type = PWEXPIRE_NONE; + *data = NULL; + + mark = ldb_msg_find_attr_as_string(msg, SYSDB_PWD_ATTRIBUTE, NULL); + if (mark != NULL) { + DEBUG(9, ("Found pwdAttribute, " + "assuming LDAP password policies are active.\n")); + + *type = PWEXPIRE_LDAP_PASSWORD_POLICY; + return EOK; + } + + mark = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(9, ("Found Kerberos password expiration attributes.\n")) + val = ldb_msg_find_attr_as_string(msg, SYSDB_KRBPW_EXPIRATION, + NULL); + if (val != NULL) { + *data = talloc_strdup(mem_ctx, val); + if (*data == NULL) { + DEBUG(1, ("talloc_strdup failed.\n")); + return ENOMEM; + } + *type = PWEXPIRE_KERBEROS; + + return EOK; + } + } + + mark = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + if (mark != NULL) { + DEBUG(9, ("Found shadow password expiration attributes.\n")) + spwd = talloc_zero(mem_ctx, struct spwd); + if (spwd == NULL) { + DEBUG(1, ("talloc failed.\n")); + return ENOMEM; + } + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_LASTCHANGE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_lstchg); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MIN, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_min); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_MAX, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_max); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_WARNING, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_warn); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_INACTIVE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_inact); + if (ret != EOK) goto shadow_fail; + + val = ldb_msg_find_attr_as_string(msg, SYSDB_SHADOWPW_EXPIRE, NULL); + ret = string_to_shadowpw_days(val, &spwd->sp_expire); + if (ret != EOK) goto shadow_fail; + + *data = spwd; + *type = PWEXPIRE_SHADOW; + + return EOK; + } + + DEBUG(9, ("No password expiration attributes found.\n")); + return EOK; + +shadow_fail: + talloc_free(spwd); + return ret; +} + /* ==Get-User-DN========================================================== */ struct get_user_dn_state { @@ -56,6 +268,8 @@ struct get_user_dn_state { const char *name; char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; }; static void get_user_dn_done(void *pvt, int err, struct ldb_result *res); @@ -78,13 +292,22 @@ struct tevent_req *get_user_dn_send(TALLOC_CTX *memctx, state->sh = sh; state->name = username; - state->attrs = talloc_array(state, const char *, 2); + state->attrs = talloc_array(state, const char *, 11); if (!state->attrs) { talloc_zfree(req); return NULL; } state->attrs[0] = SYSDB_ORIG_DN; - state->attrs[1] = NULL; + state->attrs[1] = SYSDB_SHADOWPW_LASTCHANGE; + state->attrs[2] = SYSDB_SHADOWPW_MIN; + state->attrs[3] = SYSDB_SHADOWPW_MAX; + state->attrs[4] = SYSDB_SHADOWPW_WARNING; + state->attrs[5] = SYSDB_SHADOWPW_INACTIVE; + state->attrs[6] = SYSDB_SHADOWPW_EXPIRE; + state->attrs[7] = SYSDB_KRBPW_LASTCHANGE; + state->attrs[8] = SYSDB_KRBPW_EXPIRATION; + state->attrs[9] = SYSDB_PWD_ATTRIBUTE; + state->attrs[10] = NULL; /* this sysdb call uses a sysdn operation, which means it will be * schedule only after we return, no timer hack needed */ @@ -105,6 +328,7 @@ static void get_user_dn_done(void *pvt, int err, struct ldb_result *res) struct get_user_dn_state *state = tevent_req_data(req, struct get_user_dn_state); const char *dn; + int ret; if (err != LDB_SUCCESS) { tevent_req_error(req, EIO); @@ -134,12 +358,22 @@ static void get_user_dn_done(void *pvt, int err, struct ldb_result *res) break; } } + state->dn = talloc_strdup(state, dn); if (!state->dn) { tevent_req_error(req, ENOMEM); break; } + ret = find_password_expiration_attributes(state, res->msgs[0], + &state->pw_expire_type, + &state->pw_expire_data); + if (ret != EOK) { + DEBUG(1, ("find_password_expiration_attributes failed.\n")); + tevent_req_error(req, ENOMEM); + break; + } + tevent_req_done(req); break; @@ -152,7 +386,9 @@ static void get_user_dn_done(void *pvt, int err, struct ldb_result *res) } static int get_user_dn_recv(struct tevent_req *req, - TALLOC_CTX *memctx, char **dn) + TALLOC_CTX *memctx, char **dn, + enum pwexpire *pw_expire_type, + void **pw_expire_data) { struct get_user_dn_state *state = tevent_req_data(req, struct get_user_dn_state); @@ -166,6 +402,11 @@ static int get_user_dn_recv(struct tevent_req *req, *dn = talloc_steal(memctx, state->dn); if (!*dn) return ENOMEM; + /* state->pw_expire_data may be NULL */ + *pw_expire_data = talloc_steal(memctx, state->pw_expire_data); + + *pw_expire_type = state->pw_expire_type; + return EOK; } @@ -181,6 +422,8 @@ struct auth_state { enum sdap_result result; char *dn; + enum pwexpire pw_expire_type; + void *pw_expire_data; }; static void auth_connect_done(struct tevent_req *subreq); @@ -250,7 +493,8 @@ static void auth_get_user_dn_done(struct tevent_req *subreq) struct auth_state); int ret; - ret = get_user_dn_recv(subreq, state, &state->dn); + ret = get_user_dn_recv(subreq, state, &state->dn, &state->pw_expire_type, + &state->pw_expire_data); talloc_zfree(subreq); if (ret) { tevent_req_error(req, ret); @@ -287,7 +531,8 @@ static void auth_bind_user_done(struct tevent_req *subreq) } int auth_recv(struct tevent_req *req, enum sdap_result *result, - TALLOC_CTX *memctx, struct sdap_handle **sh, char **dn) + TALLOC_CTX *memctx, struct sdap_handle **sh, char **dn, + enum pwexpire *pw_expire_type, void **pw_expire_data) { struct auth_state *state = tevent_req_data(req, struct auth_state); @@ -310,6 +555,12 @@ int auth_recv(struct tevent_req *req, enum sdap_result *result, if (*dn == NULL) return ENOMEM; } + if (pw_expire_data != NULL) { + *pw_expire_data = talloc_steal(memctx, state->pw_expire_data); + } + + *pw_expire_type = state->pw_expire_type; + *result = state->result; return EOK; } @@ -394,33 +645,79 @@ static void sdap_auth4chpass_done(struct tevent_req *req) struct tevent_req *subreq; enum sdap_result result; int ret; + enum pwexpire pw_expire_type; + void* pw_expire_data; - ret = auth_recv(req, &result, state, &state->sh, &state->dn); + ret = auth_recv(req, &result, state, &state->sh, &state->dn, + &pw_expire_type, &pw_expire_data); talloc_zfree(req); if (ret) { state->pd->pam_status = PAM_SYSTEM_ERR; goto done; } + if (result == SDAP_AUTH_SUCCESS) { + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_shadow failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_kerberos failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + + if (result == SDAP_AUTH_PW_EXPIRED) { + DEBUG(1, ("LDAP provider cannot change kerberos " + "passwords.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + case PWEXPIRE_NONE: + break; + default: + DEBUG(1, ("Unknow pasword expiration type.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } switch (result) { case SDAP_AUTH_SUCCESS: case SDAP_AUTH_PW_EXPIRED: DEBUG(7, ("user [%s] successfully authenticated.\n", state->dn)); - subreq = sdap_exop_modify_passwd_send(state, - state->breq->be_ctx->ev, - state->sh, - state->dn, - state->password, - state->new_password); - - if (!subreq) { - DEBUG(2, ("Failed to change password for %s\n", state->username)); + if (pw_expire_type == PWEXPIRE_SHADOW) { +/* TODO: implement async ldap modify request */ + DEBUG(1, ("Changing shadow password attributes not implemented.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; goto done; - } + } else { + subreq = sdap_exop_modify_passwd_send(state, + state->breq->be_ctx->ev, + state->sh, + state->dn, + state->password, + state->new_password); + + if (!subreq) { + DEBUG(2, ("Failed to change password for %s\n", state->username)); + goto done; + } - tevent_req_set_callback(subreq, sdap_pam_chpass_done, state); - return; + tevent_req_set_callback(subreq, sdap_pam_chpass_done, state); + return; + } break; default: state->pd->pam_status = PAM_SYSTEM_ERR; @@ -526,20 +823,53 @@ static void sdap_pam_auth_done(struct tevent_req *req) struct tevent_req *subreq; enum sdap_result result; int ret; + enum pwexpire pw_expire_type; + void* pw_expire_data; - ret = auth_recv(req, &result, NULL, NULL, NULL); + ret = auth_recv(req, &result, state, NULL, NULL, &pw_expire_type, + &pw_expire_data); talloc_zfree(req); if (ret) { state->pd->pam_status = PAM_SYSTEM_ERR; goto done; } + if (result == SDAP_AUTH_SUCCESS) { + switch (pw_expire_type) { + case PWEXPIRE_SHADOW: + ret = check_pwexpire_shadow(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_shadow failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_KERBEROS: + ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), + &result); + if (ret != EOK) { + DEBUG(1, ("check_pwexpire_kerberos failed.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + break; + case PWEXPIRE_LDAP_PASSWORD_POLICY: + case PWEXPIRE_NONE: + break; + default: + DEBUG(1, ("Unknow pasword expiration type.\n")); + state->pd->pam_status = PAM_SYSTEM_ERR; + goto done; + } + } + switch (result) { case SDAP_AUTH_SUCCESS: state->pd->pam_status = PAM_SUCCESS; break; case SDAP_AUTH_FAILED: - state->pd->pam_status = PAM_CRED_INSUFFICIENT; + state->pd->pam_status = PAM_PERM_DENIED; break; case SDAP_UNAVAIL: state->pd->pam_status = PAM_AUTHINFO_UNAVAIL; diff --git a/server/providers/ldap/ldap_common.c b/server/providers/ldap/ldap_common.c index 43fd67b..bb68ffb 100644 --- a/server/providers/ldap/ldap_common.c +++ b/server/providers/ldap/ldap_common.c @@ -68,7 +68,17 @@ struct sdap_id_map rfc2307_user_map[] = { { "ldap_user_fullname", "cn", SYSDB_FULLNAME, NULL }, { "ldap_user_member_of", NULL, SYSDB_MEMBEROF, NULL }, { "ldap_user_uuid", NULL, SYSDB_UUID, NULL }, - { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL } + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL } }; struct sdap_id_map rfc2307_group_map[] = { @@ -95,7 +105,17 @@ struct sdap_id_map rfc2307bis_user_map[] = { { "ldap_user_member_of", "memberOf", SYSDB_MEMBEROF, NULL }, /* FIXME: this is 389ds specific */ { "ldap_user_uuid", "nsUniqueId", SYSDB_UUID, NULL }, - { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL } + { "ldap_user_modify_timestamp", "modifyTimestamp", SYSDB_ORIG_MODSTAMP, NULL }, + { "ldap_user_shadow_last_change", "shadowLastChange", SYSDB_SHADOWPW_LASTCHANGE, NULL }, + { "ldap_user_shadow_min", "shadowMin", SYSDB_SHADOWPW_MIN, NULL }, + { "ldap_user_shadow_max", "shadowMax", SYSDB_SHADOWPW_MAX, NULL }, + { "ldap_user_shadow_warning", "shadowWarning", SYSDB_SHADOWPW_WARNING, NULL }, + { "ldap_user_shadow_inactive", "shadowInactive", SYSDB_SHADOWPW_INACTIVE, NULL }, + { "ldap_user_shadow_expire", "shadowExpire", SYSDB_SHADOWPW_EXPIRE, NULL }, + { "ldap_user_shadow_flag", "shadowFlag", SYSDB_SHADOWPW_FLAG, NULL }, + { "ldap_user_krb_last_pwd_change", "krbLastPwdChange", SYSDB_KRBPW_LASTCHANGE, NULL }, + { "ldap_user_krb_password_expiration", "krbPasswordExpiration", SYSDB_KRBPW_EXPIRATION, NULL }, + { "ldap_pwd_attribute", "pwdAttribute", SYSDB_PWD_ATTRIBUTE, NULL } }; struct sdap_id_map rfc2307bis_group_map[] = { diff --git a/server/providers/ldap/sdap.h b/server/providers/ldap/sdap.h index 540e4c7..31c7289 100644 --- a/server/providers/ldap/sdap.h +++ b/server/providers/ldap/sdap.h @@ -62,6 +62,19 @@ struct sdap_handle { struct sdap_op *ops; }; +#define SYSDB_SHADOWPW_LASTCHANGE "shadowLastChange" +#define SYSDB_SHADOWPW_MIN "shadowMin" +#define SYSDB_SHADOWPW_MAX "shadowMax" +#define SYSDB_SHADOWPW_WARNING "shadowWarning" +#define SYSDB_SHADOWPW_INACTIVE "shadowInactive" +#define SYSDB_SHADOWPW_EXPIRE "shadowExpire" +#define SYSDB_SHADOWPW_FLAG "shadowFlag" + +#define SYSDB_KRBPW_LASTCHANGE "krbLastPwdChange" +#define SYSDB_KRBPW_EXPIRATION "krbPasswordExpiration" + +#define SYSDB_PWD_ATTRIBUTE "pwdAttribute" + enum sdap_result { SDAP_SUCCESS, SDAP_NOT_FOUND, @@ -70,7 +83,8 @@ enum sdap_result { SDAP_ERROR, SDAP_AUTH_SUCCESS, SDAP_AUTH_FAILED, - SDAP_AUTH_PW_EXPIRED + SDAP_AUTH_PW_EXPIRED, + SDAP_ACCT_EXPIRED }; enum sdap_basic_opt { @@ -121,10 +135,22 @@ enum sdap_user_opt { SDAP_AT_USER_MEMBEROF, SDAP_AT_USER_UUID, SDAP_AT_USER_MODSTAMP, + SDAP_AT_SP_LSTCHG, + SDAP_AT_SP_MIN, + SDAP_AT_SP_MAX, + SDAP_AT_SP_WARN, + SDAP_AT_SP_INACT, + SDAP_AT_SP_EXPIRE, + SDAP_AT_SP_FLAG, + SDAP_AT_KP_LASTCHANGE, + SDAP_AT_KP_EXPIRATION, + SDAP_AT_PWD_ATTRIBUTE, SDAP_OPTS_USER /* attrs counter */ }; +#define SDAP_FIRST_EXTRA_USER_AT SDAP_AT_SP_LSTCHG + /* the objectclass must be the first attribute. * Functions depend on this */ enum sdap_group_opt { diff --git a/server/providers/ldap/sdap_async.c b/server/providers/ldap/sdap_async.c index 93cc976..0949e5f 100644 --- a/server/providers/ldap/sdap_async.c +++ b/server/providers/ldap/sdap_async.c @@ -1281,6 +1281,7 @@ int sdap_auth_recv(struct tevent_req *req, enum sdap_result *result) default: *result = SDAP_ERROR; } + return EOK; } @@ -1326,6 +1327,8 @@ static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx, gid_t gid; struct sysdb_attrs *user_attrs; char *upn = NULL; + int i; + char *val = NULL; req = tevent_req_create(memctx, &state, struct sdap_save_user_state); if (!req) return NULL; @@ -1477,6 +1480,28 @@ static struct tevent_req *sdap_save_user_send(TALLOC_CTX *memctx, } } + for (i = SDAP_FIRST_EXTRA_USER_AT; i < SDAP_OPTS_USER; i++) { + ret = sysdb_attrs_get_el(state->attrs, opts->user_map[i].sys_name, &el); + if (ret) { + goto fail; + } + if (el->num_values > 0) { + DEBUG(9, ("Adding [%s]=[%s] to user attributes.\n", + opts->user_map[i].sys_name, + (const char*) el->values[0].data)); + val = talloc_strdup(user_attrs, (const char*) el->values[0].data); + if (val == NULL) { + ret = ENOMEM; + goto fail; + } + ret = sysdb_attrs_add_string(user_attrs, + opts->user_map[i].sys_name, val); + if (ret) { + goto fail; + } + } + } + DEBUG(6, ("Storing info for user %s\n", state->name)); subreq = sysdb_store_user_send(state, state->ev, state->handle, -- 1.6.2.5