src/server/daemon/auth.c

Wed, 27 Nov 2024 23:00:07 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 27 Nov 2024 23:00:07 +0100
changeset 563
6ca97c99173e
parent 490
d218607f5a7e
permissions
-rw-r--r--

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.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <cx/map.h>

#include "../public/nsapi.h"
#include "../util/atomic.h"
#include "auth.h"

static pthread_mutex_t auth_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static UserCache cache;

void auth_cache_init() {
    log_ereport(LOG_VERBOSE, "auth_cache_init");
    // TODO: config parameters
    //pthread_mutex_init(&auth_cache_mutex, NULL);
    cache.map = calloc(80, sizeof(UserCacheElm));
    cache.size = 80;
    cache.count = 0;
    cache.max_users = 64;
    cache.head = NULL;
    cache.trail = NULL;
}

User* auth_cache_get(char *authdb, const char *user) {
    //printf("auth_cache_get: %s\n", user);
    /*
     * create the key to access the map
     * key: authdb\0user
     */
    size_t authdblen = strlen(authdb);
    size_t userlen = strlen(user);
    
    size_t keylen = authdblen + userlen + 1;
    unsigned char *key = malloc(keylen);
    memcpy(key, authdb, authdblen);
    key[authdblen] = 0;
    memcpy(key + authdblen + 1, user, userlen);
    
    CxHashKey mapkey = cx_hash_key_bytes(key, keylen);
    
    // get cached user from map
    time_t now = time(NULL);
    size_t slot = mapkey.hash%cache.size;
    
    User *u = NULL;
    pthread_mutex_lock(&auth_cache_mutex);
    
    UserCacheElm *elm = cache.map[slot];
    while(elm && elm->key.hash != mapkey.hash) {
        elm = elm->next_elm;
    }
    // if we have an elm, the hash is correct
    if(elm) {
        // compare the key data to be sure it is the correct user
        int n = (mapkey.len > elm->key.len) ? elm->key.len : mapkey.len;
        if (!memcmp(elm->key.data, mapkey.data, n)) {
            // elm is now the correct UserCacheElm
            // TODO: use configuration for expire time
            if(now - elm->created > 120) {
                // cached user expired
                // remove all users from the list from the first to this one
                UserCacheElm *e = cache.head;
                while(e) {
                    if(e == elm) {
                        break;
                    }
                    UserCacheElm *nu = e->next_user;
                    auth_cache_remove_from_map(e);
                    e = nu;
                }
                cache.head = elm->next_user;
                if(cache.trail == elm) {
                    cache.trail = NULL;
                }
                auth_cache_remove_from_map(elm);
                u = NULL;
            } else {
                u = (User*)elm->user;
            }
        }
    }
    
    pthread_mutex_unlock(&auth_cache_mutex);
    free(key);
    return u;
}

void auth_cache_add(
        char *authdb,
        User *user,
        const char *password,
        const char **groups,
        size_t numgroups)
{
    //printf("auth_cache_add: %s\n", user->name);
    /*
     * this function does not check, if the user is already in the map
     * use it only after auth_cache_get
     */
    
    CachedUser *cusr = malloc(sizeof(CachedUser));
    cusr->user.name = strdup(user->name);
    cusr->user.uid = user->uid;
    cusr->user.gid = user->gid;
    cusr->user.verify_password = 
            (user_verify_passwd_f)cached_user_verify_password;
    cusr->user.check_group = (user_check_group_f)cached_user_check_group;
    cusr->user.free = (user_free_f)cached_user_unref;
    
    cusr->authdb = strdup(authdb);
    cusr->password = strdup(password);
    cusr->groups = numgroups ? calloc(numgroups, sizeof(cxmutstr)) : NULL;
    cusr->numgroups = numgroups;
    for(int i=0;i<numgroups;i++) {
        cusr->groups[i] = cx_strdup(cx_str(groups[i]));
    }
    cusr->ref = 1;
    
    /*
     * add the user to the auth cache
     * the auth cache is a list of all cached users
     */
    
    // create list element
    time_t now = time(NULL);
    UserCacheElm *elm = malloc(sizeof(UserCacheElm));
    elm->user = cusr;
    elm->created = now;
    elm->next_elm = NULL;
    elm->next_user = NULL;
    
    // create map key
    size_t authdblen = strlen(authdb);
    size_t userlen = strlen(user->name);
    size_t keylen = authdblen + userlen + 1;
    unsigned char *key = malloc(keylen);
    memcpy(key, authdb, authdblen);
    key[authdblen] = 0;
    memcpy(key + authdblen + 1, user->name, userlen);
    CxHashKey mapkey = cx_hash_key_bytes(key, keylen);
    
    elm->key.data = key;
    elm->key.len = mapkey.len;
    elm->key.hash = mapkey.hash;
    elm->slot = mapkey.hash%cache.size;
    
    // add user to list and map
    pthread_mutex_lock(&auth_cache_mutex);
    
    // remove the first cached user if expired or the cache is full 
    if(cache.head && 
            (cache.count >= cache.max_users || now-cache.head->created > 120))
    {
        UserCacheElm *first = cache.head;
        cache.head = first->next_user;
        if(!cache.head) {
            cache.trail = NULL;
        }
        auth_cache_remove_from_map(first);
    }
    
    // add to map
    UserCacheElm *prevelm = cache.map[elm->slot];
    if(prevelm) {
        for(;;) {
            if(!prevelm->next_elm) {
                break;
            }
            prevelm = prevelm->next_elm;
        }
    }
    if(prevelm) {
        prevelm->next_elm = elm;
    } else {
        cache.map[elm->slot] = elm;
    }
    
    // add to list
    if(cache.head) {
        cache.trail->next_user = elm;
        cache.trail = elm;
    } else {
        cache.head = elm;
        cache.trail = elm;
    }
    
    cache.count++;
    
    pthread_mutex_unlock(&auth_cache_mutex);
}

void auth_cache_remove_from_map(UserCacheElm *elm) {
    UserCacheElm *prevelm = NULL;
    UserCacheElm *e = cache.map[elm->slot];
    while(e) {
        if(e == elm) {
            break;
        } else {
            prevelm = e;
        }
        e = e->next_elm;
    }
    if(prevelm) {
        prevelm->next_elm = elm->next_elm;
    } else {
        cache.map[elm->slot] = elm->next_elm;
    }
    
    free((void*)elm->key.data);
    cached_user_unref(elm->user);
    free(elm);
    
    cache.count--;
}

int cached_user_verify_password(CachedUser *user, const char *password) {
    if(!strcmp(user->password, password)) {
        return 1;
    } else {
        return 0;
    }
}

int cached_user_check_group(CachedUser *user, const char *group) {
    cxstring grp = cx_str(group);
    for(int i=0;i<user->numgroups;i++) {
        if(!cx_strcmp(cx_strcast(user->groups[i]), grp)) {
            return 1;
        }
    }
    return 0;
}

void cached_user_unref(CachedUser *user) {
    uint32_t ref = ws_atomic_dec32(&user->ref);
    if(ref == 0) {
        cached_user_delete(user);
    }
}

void cached_user_delete(CachedUser *user) {
    free(user->user.name);
    free(user->authdb);
    free(user->password);
    free(user->groups);
    free(user);
}


/*
 * public API
 * from public/auth.h
 */

User* authdb_get_user(AuthDB *db, Session *sn, Request *rq, const char *user) {
    if(db->use_cache) {
        User *u = auth_cache_get(db->name, user);
        if(u) {
            return u;
        }
    }
    return db->get_user(db, sn, rq, user);
}

User* authdb_get_and_verify(AuthDB *db, Session *sn, Request *rq, const char *user, const char *password, int *pw) {
    User *u = authdb_get_user(db, sn, rq, user);
    if(u) {
        if(u->verify_password(u, password)) {
            if(db->use_cache) {
                auth_cache_add(db->name, u, password, NULL, 0);
            }
            *pw = 1;
        } else {
            *pw = 0;
            u->free(u);
            u = NULL;
        }
    }
    return u;
}

mercurial