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. */ #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; }