src/server/daemon/ldap_auth.c

Sun, 12 Mar 2023 20:27:29 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sun, 12 Mar 2023 20:27:29 +0100
changeset 468
73e80eb953f5
parent 467
4d038bc6f86e
child 469
9a36a6b52e4c
permissions
-rw-r--r--

make ldap auth minimally working again and disable auth caching, because it is currently broken

/*
 * 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 "ldap_auth.h"
#include "ldap_resource.h"


static LDAPConfig ws_ldap_default_config = {
    NULL, // resource
    NULL, // basedn
    NULL, // binddn
    NULL, // bindpw
    "(&(objectclass=inetorgperson)(!(cn=%s)(uid=%s)))", // userSearchFilter
    {"uid"}, // uidAttributes
    1, // numUidAttributes
    "(&(|(objectclass=groupOfNames)(objectclass=groupOfUniqueNames))(cn=%s))", // groupSearchFilter
    {"member", "uniqueMember"}, // memberAttributes
    2, // numMemberAttributes
    WS_LDAP_GROUP_MEMBER_DN, // groupMemberType
    TRUE, // enableGroups
    FALSE // userNameIsDN
};

// TODO
static LDAPConfig ws_ldap_ad_config = {
    NULL, // resource
    NULL, // basedn
    NULL, // binddn
    NULL, // bindpw
    "(&(objectclass=inetorgperson)(!(cn=%s)(uid=%s)))", // userSearchFilter
    {"uid"}, // uidAttributes
    1, // numUidAttributes
    "", // groupSearchFilter
    {"uniqueMember", "member"}, // memberAttributes
    2, // numMemberAttributes
    WS_LDAP_GROUP_MEMBER_DN, // groupMemberType
    TRUE, // enableGroups
    FALSE // userNameIsDN
};

static LDAPConfig ws_ldap_posix_config = {
    NULL, // resource
    NULL, // basedn
    NULL, // binddn
    NULL, // bindpw
    "(&(objectclass=posixAccount)(uid=%s))", // userSearchFilter
    {"uid"}, // uidAttributes
    1, // numUidAttributes
    "(&(objectclass=posixGroup)(cn=%s))", // groupSearchFilter
    {"memberUid"}, // 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"));
    // TODO ...
    
    if(!resource.ptr) {
        // TODO: create resource pool
    } else {
        authdb->config.resource = resource.ptr;
    }
    
    if(!basedn.ptr) {
        log_ereport(LOG_FAILURE, "ldap authdb %s: basedn is required", name);
        return NULL;
    }
    authdb->config.basedn = basedn.ptr;
    
    
    // initialize group cache
    authdb->groups.first = NULL;
    authdb->groups.last = NULL;
    authdb->groups.map = cxHashMapCreate(cfg->a, 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;
}

User* ldap_get_user(AuthDB *db, Session *sn, Request *rq, const char *username) {
    LDAPAuthDB *authdb = (LDAPAuthDB*) db;
    LDAPConfig *config = &authdb->config;

    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);

    LDAPMessage *result;
    struct timeval timeout;
    timeout.tv_sec = 8;
    timeout.tv_usec = 0;
    int r = ldap_search_ext_s(
            ld,
            config->basedn,
            LDAP_SCOPE_SUBTREE,
            filter,
            NULL,
            0,
            NULL,        // server controls
            NULL,        // client controls
            &timeout,
            1,           // size limit
            &result);
    if (r != LDAP_SUCCESS) {
        //ws_ldap_close(ld);
        
        log_ereport(LOG_FAILURE, "ldap_get_user: search failed: %s", ldap_err2string(r));
        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;
        }
    }

    //ws_ldap_close(ld);
    return NULL;
}

LDAPGroup* ldap_get_group(Session *sn, Request *rq, LDAPAuthDB *authdb, const char *group) {
    printf("ldap_get_group: %s\n", group);
    
    LDAPConfig *config = &authdb->config;
    
    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];
    int s = snprintf(filter, 127, "cn=%s", group);
    filter[s] = 0;

    LDAPMessage *result;
    struct timeval timeout;
    timeout.tv_sec = 8;
    timeout.tv_usec = 0;
    int r = ldap_search_ext_s(
            ld,
            config->basedn,
            LDAP_SCOPE_SUBTREE,
            filter,
            NULL,
            0,
            NULL,        // server controls
            NULL,        // client controls
            &timeout,
            1,           // size limit
            &result);
    if (r != LDAP_SUCCESS) {
        //ws_ldap_close(ld);
        
        fprintf(stderr, "ldap_search_ext_s failed\n");
        return NULL;
    }
    
    LDAPGroup *wsgroup = NULL;
    LDAPMessage *msg = ldap_first_entry(ld, result);
    if (msg) {
        // create group object
        wsgroup = malloc(sizeof(LDAPGroup));
        wsgroup->name = strdup(group);
        wsgroup->members = NULL;
        wsgroup->nmembers = 0;
        wsgroup->update = 0;
        wsgroup->next = NULL;
        
        // get attributes
        BerElement *ber = NULL;
        char *attribute = attribute=ldap_first_attribute(ld, msg, &ber);
        while(attribute != NULL) {
            printf("attribute: %s\n", attribute);
            if(!strcasecmp(attribute, "memberuid")) {
                // get all memberuid values and add the users to the group obj
                
                struct berval **values = ldap_get_values_len(ld, msg, attribute);
                if(values) {
                    int count = ldap_count_values_len(values);
                    wsgroup->members = calloc(count, sizeof(LDAPMember));
                    wsgroup->nmembers = count;
                    for(int i=0;i<count;i++) {
                        cxstring member = cx_strn(
                                values[i]->bv_val,
                                values[i]->bv_len);
                        wsgroup->members[i].name = cx_strdup(member).ptr;
                        // TODO: uid?
                        printf("added member: %.*s\n", (int)member.length, member.ptr);
                    }
                }
            }
            
            attribute = ldap_next_attribute(ld, msg, ber); 
        }
        
        if(ber) {
            //ldap_ber_free(ber, 0);
        }
        if(attribute) {
            ldap_memfree(attribute);
        }
    }
    
    //ws_ldap_close(ld);
    return wsgroup;
}

int ldap_user_verify_password(User *u, const char *password) {
    LDAPUser *user = (LDAPUser*)u;
    
    //int r = ldap_simple_bind_s(user->ldap, user->userdn, password);
    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) {
        printf("ldap password ok\n");
        return 1;
    } else {
        printf("ldap password not ok\n");
        return 0;
    }
}

int ldap_user_check_group(User *u, const char *group_str) {
    LDAPUser *user = (LDAPUser*)u;
    
    int ret = 0;
    
    LDAPGroup *group = ldap_get_group(user->sn, user->rq, user->authdb, group_str);
    for(int i=0;i<group->nmembers;i++) {
        char *member = group->members[i].name;
        if(!strcmp(member, u->name)) {
            printf("is member\n");
            ret = 1;
        }
    }
    
    // TODO: free or cache group
    
    return ret;
}

void ldap_user_free(User *u) {
    LDAPUser *user = (LDAPUser*)u;
    ldap_memfree(user->userdn);
    // TODO: use connection pool
    //ws_ldap_close(user->ldap);
    free(user);
}

mercurial