Wed, 27 Nov 2024 23:00:07 +0100
add TODO to use a future ucx feature
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2013 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #ifdef __gnu_linux__ #define _GNU_SOURCE #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <cx/utils.h> #include <cx/hash_map.h> #include <cx/printf.h> #include "../util/util.h" #include "ldap_auth.h" #include "ldap_resource.h" static cxstring ws_ldap_default_uid_attr[] = { CX_STR("uid") }; static cxstring ws_ldap_default_member_attr[] = { CX_STR("member"), CX_STR("uniqueMember") }; static LDAPConfig ws_ldap_default_config = { NULL, // resource NULL, // basedn NULL, // binddn NULL, // bindpw "(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))", // userSearchFilter ws_ldap_default_uid_attr, // uidAttributes 1, // numUidAttributes "(&(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames))(cn=%s))", // groupSearchFilter ws_ldap_default_member_attr, // memberAttributes 2, // numMemberAttributes WS_LDAP_GROUP_MEMBER_DN, // groupMemberType TRUE, // enableGroups FALSE // userNameIsDN }; // TODO: AD static cxstring ws_ad_default_uid_attr[] = { CX_STR("uid") }; static cxstring ws_ad_default_member_attr[] = { CX_STR("member"), CX_STR("uniqueMember") }; static LDAPConfig ws_ldap_ad_config = { NULL, // resource NULL, // basedn NULL, // binddn NULL, // bindpw "(&(objectclass=inetorgperson)(|(cn=%s)(uid=%s)))", // userSearchFilter ws_ad_default_uid_attr, // uidAttributes 1, // numUidAttributes "", // groupSearchFilter ws_ad_default_member_attr, // memberAttributes 2, // numMemberAttributes WS_LDAP_GROUP_MEMBER_DN, // groupMemberType TRUE, // enableGroups FALSE // userNameIsDN }; static cxstring ws_posix_default_uid_attr[] = { CX_STR("uid") }; static cxstring ws_posix_default_member_attr[] = { CX_STR("memberUid") }; static LDAPConfig ws_ldap_posix_config = { NULL, // resource NULL, // basedn NULL, // binddn NULL, // bindpw "(&(objectclass=posixAccount)(uid=%s))", // userSearchFilter ws_posix_default_uid_attr, // uidAttributes 1, // numUidAttributes "(&(objectclass=posixGroup)(cn=%s))", // groupSearchFilter ws_posix_default_member_attr, // memberAttributes 1, // numMemberAttributes WS_LDAP_GROUP_MEMBER_UID, // groupMemberType TRUE, // enableGroups FALSE // userNameIsDN }; AuthDB* create_ldap_authdb(ServerConfiguration *cfg, const char *name, ConfigNode *node) { LDAPAuthDB *authdb = cxMalloc(cfg->a, sizeof(LDAPAuthDB)); if(!authdb) { return NULL; } authdb->authdb.name = pool_strdup(cfg->pool, name); if(!authdb->authdb.name) { return NULL; } authdb->authdb.get_user = ldap_get_user; authdb->authdb.use_cache = 0; // TODO: enable caching when cache actually works // initialize default ldap config cxstring dirtype = serverconfig_object_directive_value(node, cx_str("DirectoryType")); LDAPConfig *default_config; if(!dirtype.ptr) { default_config = &ws_ldap_default_config; } else if(!cx_strcmp(dirtype, cx_str("ldap"))) { default_config = &ws_ldap_default_config; } else if(!cx_strcmp(dirtype, cx_str("posix"))) { default_config = &ws_ldap_posix_config; } else if(!cx_strcmp(dirtype, cx_str("ad"))) { default_config = &ws_ldap_ad_config; } else { log_ereport(LOG_FAILURE, "cannot create ldap authdb %s: unknown directory type %s", name, dirtype.ptr); } memcpy(&authdb->config, default_config, sizeof(LDAPConfig)); // custom config cxstring resource = serverconfig_object_directive_value(node, cx_str("Resource")); cxstring basedn = serverconfig_object_directive_value(node, cx_str("Basedn")); cxstring binddn = serverconfig_object_directive_value(node, cx_str("Binddn")); cxstring bindpw = serverconfig_object_directive_value(node, cx_str("Bindpw")); cxstring userSearchFilter = serverconfig_object_directive_value(node, cx_str("UserSearchFilter")); cxstring uidAttributes = serverconfig_object_directive_value(node, cx_str("UidAttributes")); cxstring groupSearchFilter = serverconfig_object_directive_value(node, cx_str("GroupSearchFilter")); 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")); if(!resource.ptr) { // implicitly create a resource pool for this authdb cxmutstr respool_name = cx_asprintf_a(cfg->a, "_authdb_%s", name); if(!respool_name.ptr) { return NULL; } log_ereport( LOG_INFORM, "ldap authdb %s: no resource specified: create resource pool %s", name, respool_name.ptr); if(resourcepool_new(cfg, cx_str("ldap"), cx_strcast(respool_name), node)) { log_ereport( LOG_FAILURE, "ldap authdb %s: cannot create ldap resource pool", name); return NULL; } authdb->config.resource = respool_name.ptr; } else { 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 = cx_strdup_a(cfg->a, basedn).ptr; if(!authdb->config.basedn) return NULL; // optional config if(binddn.ptr) { if(!bindpw.ptr) { log_ereport(LOG_FAILURE, "ldap authdb %s: binddn specified, but no bindpw", name); return NULL; } 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 = cx_strdup_a(cfg->a, userSearchFilter).ptr; } if(uidAttributes.ptr) { 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) { 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"))) { authdb->config.groupMemberType = WS_LDAP_GROUP_MEMBER_UID; } else { log_ereport(LOG_FAILURE, "ldap authdb %s: unknown MemberType %s", name, memberType.ptr); return NULL; } } if(enableGroups.ptr) { authdb->config.enableGroups = util_getboolean_s(enableGroups, FALSE); } if(userNameIsDn.ptr) { authdb->config.userNameIsDN = util_getboolean_s(userNameIsDn, FALSE); } // initialize group cache authdb->groups.first = NULL; authdb->groups.last = NULL; authdb->groups.map = cxHashMapCreate(cfg->a, CX_STORE_POINTERS, 32); if(!authdb->groups.map) { return NULL; } log_ereport(LOG_INFORM, "create authdb name=%s type=ldap resource=%s", name, resource.ptr); return (AuthDB*) authdb; } LDAP* get_ldap_session(Session *sn, Request *rq, LDAPAuthDB *authdb) { ResourceData *res = resourcepool_lookup(sn, rq, authdb->config.resource, 0); if(!res) { log_ereport(LOG_FAILURE, "AuthDB %s: cannot get resource %s", authdb->authdb.name, authdb->config.resource); return NULL; } LDAP *ldap = res->data; if(authdb->config.binddn) { struct berval *server_cred; int r = ws_ldap_bind(ldap, authdb->config.binddn, authdb->config.bindpw, &server_cred); if(r != LDAP_SUCCESS) { log_ereport(LOG_FAILURE, "AuthDB %s: bind to %s failed: %s", authdb->config.binddn, ldap_err2string(r)); resourcepool_free(sn, rq, res); return NULL; } } 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); } if(ber) { ber_free(ber, 0); } // 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) { return NULL; } // get the user dn 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; // TODO: add config parameter for timeout timeout.tv_usec = 0; int r = ldap_search_ext_s( ld, config->basedn, LDAP_SCOPE_SUBTREE, filter.ptr, NULL, 0, NULL, // server controls NULL, // client controls &timeout, 2, // size limit &result); 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); 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); return (User*)user; } static int is_member_attribute(LDAPAuthDB *auth, const char *attribute) { LDAPConfig *config = &auth->config; cxstring attr = cx_str(attribute); for(int i=0;i<config->numMemberAttributes;i++) { if(!cx_strcmp(config->memberAttributes[i], attr)) { return 1; } } return 0; } static int group_add_member(LDAPGroup *group, LDAP *ldap, LDAPMessage *msg, char *attribute) { struct berval **values = ldap_get_values_len(ldap, msg, attribute); int ret = 0; if(values) { int count = ldap_count_values_len(values); for(int i=0;i<count;i++) { cxstring memberValue = cx_strn(values[i]->bv_val, values[i]->bv_len); CxHashKey key = cx_hash_key(memberValue.ptr, memberValue.length); char *g_member = cxMapGet(group->members, key); if(!g_member) { cxmutstr member = cx_strdup_a(group->members->allocator, memberValue); if(!member.ptr) { ret = 1; break; } if(cxMapPut(group->members, key, member.ptr)) { ret = 1; break; } } } ldap_value_free_len(values); } return ret; } static LDAPGroup* ldap_msg_to_group( Session *sn, Request *rq, LDAPAuthDB *authdb, LDAP *ldap, LDAPMessage *msg, const char *group_name) { CxAllocator *a = pool_allocator(sn->pool); LDAPGroup *group = pool_malloc(sn->pool, sizeof(LDAPGroup)); if(!group) { return NULL; } group->members = cxHashMapCreate(a, CX_STORE_POINTERS, 32); if(!group->members) { pool_free(sn->pool, group); return NULL; } group->name = pool_strdup(sn->pool, group_name); BerElement *ber = NULL; char *attribute = ldap_first_attribute(ldap, msg, &ber); while(attribute) { if(is_member_attribute(authdb, attribute)) { if(group_add_member(group, ldap, msg, attribute)) { // OOM ldap_memfree(attribute); // free at least some memory cxMapDestroy(group->members); pool_free(sn->pool, group); group = NULL; break; } } ldap_memfree(attribute); attribute = ldap_next_attribute(ldap, msg, ber); } if(ber) { ber_free(ber, 0); } return group; } LDAPGroup* ldap_get_group(Session *sn, Request *rq, LDAPAuthDB *authdb, const char *group) { LDAPConfig *config = &authdb->config; CxAllocator *a = pool_allocator(sn->pool); LDAP *ld = get_ldap_session(sn, rq, authdb); if (ld == NULL) { return NULL; } // if userNameIsDN is true, group will be the full group dn and we // don't need to search with a filter, to get the entry char *filterStr; const char *basedn; int scope; if(config->userNameIsDN) { filterStr = NULL; basedn = group; scope = LDAP_SCOPE_BASE; } else { cxstring groupSearch = cx_str(config->groupSearchFilter); cxmutstr filter = cx_strreplace_a(a, groupSearch, cx_str("%s"), cx_str(group)); if(!filter.ptr) { return NULL; } filterStr = filter.ptr; basedn = config->basedn; scope = LDAP_SCOPE_SUBTREE; } log_ereport(LOG_DEBUG, "ldap_get_group: basedn: %s filter: %s", basedn, filterStr); LDAPMessage *result; struct timeval timeout; timeout.tv_sec = 8; timeout.tv_usec = 0; int r = ldap_search_ext_s( ld, basedn, scope, filterStr, NULL, 0, NULL, // server controls NULL, // client controls &timeout, 2, // size limit &result); if(filterStr) { cxFree(a, filterStr); } if (r != LDAP_SUCCESS) { if(result) { ldap_msgfree(result); } log_ereport(LOG_FAILURE, "ldap_get_group %s: search failed: %s", group, ldap_err2string(r)); return NULL; } LDAPMessage *msg = ldap_first_entry(ld, result); LDAPGroup *wsgroup = NULL; if(msg) { if(ldap_count_entries(ld, msg) > 1) { log_ereport(LOG_FAILURE, "ldap_get_user: more than one search result"); } else { wsgroup = ldap_msg_to_group(sn, rq, authdb, ld, msg, group); } } ldap_msgfree(result); return wsgroup; } int ldap_user_verify_password(User *u, const char *password) { LDAPUser *user = (LDAPUser*)u; struct berval cred; cred.bv_val = (char*)password; cred.bv_len = strlen(password); struct berval *server_cred; int r = ldap_sasl_bind_s( user->ldap, user->userdn, LDAP_SASL_SIMPLE, &cred, NULL, NULL, &server_cred); if(r == LDAP_SUCCESS) { log_ereport(LOG_VERBOSE, "ldap user %s password ok", user->userdn); return 1; } else { log_ereport(LOG_VERBOSE, "ldap user %s password not ok", user->userdn); return 0; } } int ldap_user_check_group(User *u, const char *group_str) { LDAPUser *user = (LDAPUser*)u; LDAPAuthDB *authdb = user->authdb; if(!authdb->config.enableGroups) { log_ereport( LOG_DEBUG, "ldap_user_check_group: authdb %s: groups disabled", authdb->authdb.name); return 0; } int ret = 0; LDAPGroup *group = ldap_get_group(user->sn, user->rq, authdb, group_str); if(group) { const char *usr = authdb->config.groupMemberType == WS_LDAP_GROUP_MEMBER_DN ? user->userdn : user->uid_attr; char *member = cxMapGet(group->members, cx_hash_key_str(usr)); if(member) { ret = 1; } } return ret; } void ldap_user_free(User *u) { LDAPUser *user = (LDAPUser*)u; pool_free(user->sn->pool, user->userdn); pool_free(user->sn->pool, user->uid_attr); pool_free(user->sn->pool, user); }