Mon, 13 Mar 2023 22:39:51 +0100
refactor ldap user authentication, use new filter config
--- a/src/server/daemon/acl.c Mon Mar 13 20:53:46 2023 +0100 +++ b/src/server/daemon/acl.c Mon Mar 13 22:39:51 2023 +0100 @@ -179,18 +179,9 @@ ACLList *acl = acl_evallist(list, user, access_mask, NULL); if(acl) { acl_set_error_status(sn, rq, acl, user); - // TODO: don't free the user here #51 - if(user) { - user->free(user); - } return REQ_ABORTED; } - // access allowed, we can free the user - if(user) { - user->free(user); - } - return REQ_PROCEED; }
--- a/src/server/daemon/ldap_auth.c Mon Mar 13 20:53:46 2023 +0100 +++ b/src/server/daemon/ldap_auth.c Mon Mar 13 22:39:51 2023 +0100 @@ -57,7 +57,7 @@ NULL, // basedn NULL, // binddn NULL, // bindpw - "(&(objectclass=inetorgperson)(!(cn=%s)(uid=%s)))", // userSearchFilter + "(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))", // userSearchFilter ws_ldap_default_uid_attr, // uidAttributes 1, // numUidAttributes "(&(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames))(cn=%s))", // groupSearchFilter @@ -83,7 +83,7 @@ NULL, // basedn NULL, // binddn NULL, // bindpw - "(&(objectclass=inetorgperson)(!(cn=%s)(uid=%s)))", // userSearchFilter + "(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))", // userSearchFilter ws_ad_default_uid_attr, // uidAttributes 1, // numUidAttributes "", // groupSearchFilter @@ -157,19 +157,21 @@ cxstring memberAttributes = serverconfig_object_directive_value(node, cx_str("MemberAttributes")); cxstring memberType = serverconfig_object_directive_value(node, cx_str("MemberType")); cxstring enableGroups = serverconfig_object_directive_value(node, cx_str("EnableGroups")); - cxstring userNameIsDn = serverconfig_object_directive_value(node, cx_str("UserNameIsDn")); + cxstring userNameIsDn = serverconfig_object_directive_value(node, cx_str("UserNameIsDn")); if(!resource.ptr) { // TODO: create resource pool } else { - authdb->config.resource = resource.ptr; + authdb->config.resource = cx_strdup_a(cfg->a, resource).ptr; + if(!authdb->config.resource) return NULL; } if(!basedn.ptr) { log_ereport(LOG_FAILURE, "ldap authdb %s: basedn is required", name); return NULL; } - authdb->config.basedn = basedn.ptr; + authdb->config.basedn = cx_strdup_a(cfg->a, basedn).ptr; + if(!authdb->config.basedn) return NULL; // optional config if(binddn.ptr) { @@ -178,36 +180,47 @@ return NULL; } - authdb->config.binddn = binddn.ptr; - authdb->config.bindpw = bindpw.ptr; + authdb->config.binddn = cx_strdup_a(cfg->a, binddn).ptr; + authdb->config.bindpw = cx_strdup_a(cfg->a, bindpw).ptr; + + if(!authdb->config.binddn || !authdb->config.bindpw) { + return NULL; + } } + if(userSearchFilter.ptr) { - authdb->config.userSearchFilter = userSearchFilter.ptr; + authdb->config.userSearchFilter = cx_strdup_a(cfg->a, userSearchFilter).ptr; } if(uidAttributes.ptr) { - authdb->config.numUidAttributes = cx_strsplit_a( - cfg->a, - uidAttributes, - cx_str(","), - 1024, - &authdb->config.uidAttributes); + cxmutstr uidAttributesCopy = cx_strdup_a(cfg->a, uidAttributes); + if(uidAttributesCopy.ptr) { + authdb->config.numUidAttributes = cx_strsplit_a( + cfg->a, + cx_strcast(uidAttributesCopy), + cx_str(","), + 1024, + &authdb->config.uidAttributes); + } } if(groupSearchFilter.ptr) { authdb->config.groupSearchFilter = groupSearchFilter.ptr; } if(memberAttributes.ptr) { - authdb->config.numMemberAttributes = cx_strsplit_a( - cfg->a, - memberAttributes, - cx_str(","), - 1024, - &authdb->config.memberAttributes); + cxmutstr memberAttributesCopy = cx_strdup_a(cfg->a, memberAttributes); + if(memberAttributesCopy.ptr) { + authdb->config.numMemberAttributes = cx_strsplit_a( + cfg->a, + cx_strcast(memberAttributesCopy), + cx_str(","), + 1024, + &authdb->config.memberAttributes); + } } if(memberType.ptr) { if(!cx_strcmp(memberType, cx_str("dn"))) { authdb->config.groupMemberType = WS_LDAP_GROUP_MEMBER_DN; - } else if(cx_strcmp(memberType, cx_str("uid"))) { + } else if(!cx_strcmp(memberType, cx_str("uid"))) { authdb->config.groupMemberType = WS_LDAP_GROUP_MEMBER_UID; } else { log_ereport(LOG_FAILURE, "ldap authdb %s: unknown MemberType %s", name, memberType.ptr); @@ -257,72 +270,177 @@ return ldap; } +static LDAPUser* ldap_msg_to_user( + Session *sn, + Request *rq, + LDAPAuthDB *authdb, + LDAP *ldap, + LDAPMessage *msg) +{ + CxAllocator *a = pool_allocator(sn->pool); + + LDAPUser *user = pool_malloc(sn->pool, sizeof(LDAPUser)); + if(!user) { + return NULL; + } + + // get dn + char *ldap_dn = ldap_get_dn(ldap, msg); + if(!ldap_dn) { + return NULL; + } + char *dn = pool_strdup(sn->pool, ldap_dn); + ldap_memfree(ldap_dn); + if(!dn) { + return NULL; + } + + // get uid + char *uid = NULL; + + // values of configured UidAttributes + size_t numUidAttributes = authdb->config.numUidAttributes; + cxmutstr *uid_values = pool_calloc(sn->pool, authdb->config.numUidAttributes, sizeof(cxmutstr)); + if(!uid_values) { + return NULL; + } + + + BerElement *ber = NULL; + char *attribute = ldap_first_attribute(ldap, msg, &ber); + while(attribute) { + cxstring attr = cx_str(attribute); + for(int i=0;i<numUidAttributes;i++) { + // check if the attribute is one of the uid attributes + if(!uid_values[i].ptr && !cx_strcmp(attr, authdb->config.uidAttributes[i])) { + // copy value to uid_values + struct berval **values = ldap_get_values_len(ldap, msg, attribute); + if(values) { + int count = ldap_count_values_len(values); + if(count > 0) { + cxstring attr_val = cx_strn(values[0]->bv_val, values[0]->bv_len); + uid_values[i] = cx_strdup_a(a, attr_val); + } else { + log_ereport(LOG_FAILURE, "ldap user: dn: %s attribute %s: no values", dn, attribute); + } + ldap_value_free_len(values); + } + } + } + if(uid_values[0].ptr) { + // if we found a value for the first attribute, we can use that + break; + } + + ldap_memfree(attribute); + attribute = ldap_next_attribute(ldap, msg, ber); + } + + + // use first value as uid + for(int i=0;i<numUidAttributes;i++) { + if(uid_values[i].ptr) { + if(!uid) { + uid = uid_values[i].ptr; + } else { + cxFree(a, uid_values[i].ptr); + } + } + } + pool_free(sn->pool, uid_values); + + // get user name + char *username; + if(authdb->config.userNameIsDN) { + username = dn; + } else { + username = uid; + } + + if(!username) { + return NULL; + } + + user->authdb = authdb; + user->user.verify_password = ldap_user_verify_password; + user->user.check_group = ldap_user_check_group; + user->user.free = ldap_user_free; + user->user.name = username; + user->sn = sn; + user->rq = rq; + + // TODO: get uid/gid from ldap + user->user.uid = -1; + user->user.gid = -1; + + user->ldap = ldap; + user->userdn = dn; + user->uid_attr = uid; + + return user; +} + User* ldap_get_user(AuthDB *db, Session *sn, Request *rq, const char *username) { LDAPAuthDB *authdb = (LDAPAuthDB*) db; LDAPConfig *config = &authdb->config; + CxAllocator *a = pool_allocator(sn->pool); LDAP *ld = get_ldap_session(sn, rq, authdb); if (ld == NULL) { - fprintf(stderr, "ldap_init failed\n"); return NULL; } // get the user dn - // TODO: use config for filter - // TODO: use asprintf - char filter[128]; - snprintf(filter, 128, "(uid=%s)", username); + cxstring userSearch = cx_str(config->userSearchFilter); + cxmutstr filter = cx_strreplace_a(a, userSearch, cx_str("%s"), cx_str(username)); + if(!filter.ptr) { + return NULL; + } + log_ereport(LOG_DEBUG, "ldap_get_user: filter: %s", filter.ptr); + LDAPMessage *result; struct timeval timeout; - timeout.tv_sec = 8; + timeout.tv_sec = 8; // TODO: add config parameter for timeout timeout.tv_usec = 0; int r = ldap_search_ext_s( ld, config->basedn, LDAP_SCOPE_SUBTREE, - filter, + filter.ptr, NULL, 0, NULL, // server controls NULL, // client controls &timeout, - 1, // size limit + 2, // size limit &result); - if (r != LDAP_SUCCESS) { - //ws_ldap_close(ld); - + cxFree(a, filter.ptr); + if(r != LDAP_SUCCESS) { + if(result) { + ldap_msgfree(result); + } log_ereport(LOG_FAILURE, "ldap_get_user: search failed: %s", ldap_err2string(r)); return NULL; } + if(!result) { + // not sure if this can happen + log_ereport(LOG_FAILURE, "ldap_get_user: search failed: no result"); + return NULL; + } LDAPMessage *msg = ldap_first_entry(ld, result); - if (msg) { - LDAPUser *user = pool_malloc(sn->pool, sizeof(LDAPUser)); - if (user != NULL) { - user->authdb = authdb; - user->user.verify_password = ldap_user_verify_password; - user->user.check_group = ldap_user_check_group; - user->user.free = ldap_user_free; - user->user.name = pool_strdup(sn->pool, username); - user->sn = sn; - user->rq = rq; - - // TODO: get uid/gid from ldap - user->user.uid = -1; - user->user.gid = -1; - - user->ldap = ld; - user->userdn = ldap_get_dn(ld, msg); - - ldap_msgfree(result); - - return (User*)user; + LDAPUser *user = NULL; + if(msg) { + if(ldap_count_entries(ld, msg) > 1) { + log_ereport(LOG_FAILURE, "ldap_get_user: more than one search result"); + } else { + user = ldap_msg_to_user(sn, rq, authdb, ld, msg); } } + ldap_msgfree(result); - //ws_ldap_close(ld); - return NULL; + return (User*)user; } LDAPGroup* ldap_get_group(Session *sn, Request *rq, LDAPAuthDB *authdb, const char *group) { @@ -433,10 +551,10 @@ NULL, &server_cred); if(r == LDAP_SUCCESS) { - printf("ldap password ok\n"); + log_ereport(LOG_VERBOSE, "ldap user %s password ok", user->userdn); return 1; } else { - printf("ldap password not ok\n"); + log_ereport(LOG_VERBOSE, "ldap user %s password not ok", user->userdn); return 0; } }