211 lines
4.9 KiB
C
211 lines
4.9 KiB
C
/*
|
|
* aprsc
|
|
*
|
|
* (c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
|
|
*
|
|
* This program is licensed under the BSD license, which can be found
|
|
* in the file LICENSE.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Clientlist contains a list of connected clients, along with their
|
|
* verification status. It is updated when clients connect or disconnect,
|
|
* and lookups are made more often by the Q construct algorithm.
|
|
*
|
|
* This list is maintained so that the login handler can find duplicate
|
|
* verified logins, and that the Q construct handler can lookup
|
|
* verified logins without locking and walking the other worker thread's
|
|
* client list.
|
|
*
|
|
* TODO: the clientlist should use a hash for quick lookups
|
|
* TODO: the clientlist should use cellmalloc
|
|
*/
|
|
|
|
#include <string.h>
|
|
|
|
#include "clientlist.h"
|
|
#include "hmalloc.h"
|
|
#include "rwlock.h"
|
|
#include "keyhash.h"
|
|
#include "hlog.h"
|
|
|
|
//#define CLIENTLIST_DEBUG
|
|
|
|
#ifdef CLIENTLIST_DEBUG
|
|
#define DLOG(fmt, ...) \
|
|
do { hlog(LOG_DEBUG, fmt, __VA_ARGS__); } while (0)
|
|
#else
|
|
#define DLOG(fmt, ...)
|
|
#endif
|
|
|
|
|
|
struct clientlist_t {
|
|
struct clientlist_t *next;
|
|
struct clientlist_t **prevp;
|
|
|
|
char username[16]; /* The callsign */
|
|
int username_len;
|
|
int validated; /* Valid passcode given? */
|
|
int fd; /* File descriptor, can be used by another
|
|
thread to shut down a socket */
|
|
|
|
uint32_t hash; /* hash value */
|
|
void *client_id; /* DO NOT REFERENCE - just used for an ID */
|
|
};
|
|
|
|
/* hash buckets - serious overkill, but great for 40k clients */
|
|
#define CLIENTLIST_BUCKETS 512
|
|
struct clientlist_t *clientlist[CLIENTLIST_BUCKETS];
|
|
rwlock_t clientlist_lock = RWL_INITIALIZER;
|
|
|
|
/*
|
|
* Find a client by clientlist id - must hold either a read or write lock
|
|
* before calling!
|
|
*/
|
|
|
|
static struct clientlist_t *clientlist_find_id(char *username, int len, void *id)
|
|
{
|
|
struct clientlist_t *cl;
|
|
uint32_t hash, idx;
|
|
int i;
|
|
|
|
hash = keyhash(username, len, 0);
|
|
idx = hash;
|
|
// "CLIENT_HEARD_BUCKETS" is 512..
|
|
idx ^= (idx >> 16);
|
|
idx ^= (idx >> 8);
|
|
i = idx % CLIENTLIST_BUCKETS;
|
|
|
|
DLOG("clientlist_find_id '%.*s' id %p bucket %d", len, username, id, i);
|
|
for (cl = clientlist[i]; cl; cl = cl->next)
|
|
if (cl->client_id == id) {
|
|
DLOG("clientlist_find_id '%.*s' found %p", len, username, cl);
|
|
return cl;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Check if given usename is logged in and validated.
|
|
* Return fd if found, -1 if not.
|
|
* Internal variant, no locking.
|
|
*/
|
|
|
|
static int check_if_validated_client(char *username, int len)
|
|
{
|
|
struct clientlist_t *cl;
|
|
uint32_t hash, idx;
|
|
int i;
|
|
|
|
hash = keyhash(username, len, 0);
|
|
idx = hash;
|
|
// "CLIENT_HEARD_BUCKETS" is 512..
|
|
idx ^= (idx >> 16);
|
|
idx ^= (idx >> 8);
|
|
i = idx % CLIENTLIST_BUCKETS;
|
|
|
|
DLOG("check_if_validated_client '%.*s': bucket %d", len, username, i);
|
|
for (cl = clientlist[i]; cl; cl = cl->next) {
|
|
if (cl->hash == hash && memcmp(username, cl->username, len) == 0
|
|
&& cl->username_len == len && cl->validated) {
|
|
DLOG("check_if_validated_client '%.*s' found validated, fd %d", cl->username_len, cl->username, cl->fd);
|
|
return cl->fd;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Check if given usename is logged in and validated.
|
|
* Return fd if found, -1 if not.
|
|
* This is the external variant, with locking.
|
|
*/
|
|
|
|
int clientlist_check_if_validated_client(char *username, int len)
|
|
{
|
|
int fd;
|
|
|
|
rwl_rdlock(&clientlist_lock);
|
|
|
|
fd = check_if_validated_client(username, len);
|
|
|
|
rwl_rdunlock(&clientlist_lock);
|
|
|
|
return fd;
|
|
}
|
|
|
|
/*
|
|
* Add a client to the client list
|
|
*/
|
|
|
|
int clientlist_add(struct client_t *c)
|
|
{
|
|
struct clientlist_t *cl;
|
|
int old_fd;
|
|
uint32_t hash, idx;
|
|
int i;
|
|
|
|
hash = keyhash(c->username, c->username_len, 0);
|
|
idx = hash;
|
|
// "CLIENT_HEARD_BUCKETS" is 512..
|
|
idx ^= (idx >> 16);
|
|
idx ^= (idx >> 8);
|
|
i = idx % CLIENTLIST_BUCKETS;
|
|
|
|
DLOG("clientlist_add '%s': bucket %d", c->username, i);
|
|
|
|
/* allocate and fill in */
|
|
cl = hmalloc(sizeof(*cl));
|
|
strncpy(cl->username, c->username, sizeof(cl->username));
|
|
cl->username[sizeof(cl->username)-1] = 0;
|
|
cl->username_len = c->username_len;
|
|
cl->validated = c->validated;
|
|
cl->fd = c->fd;
|
|
cl->hash = hash;
|
|
|
|
/* get lock for list and insert */
|
|
rwl_wrlock(&clientlist_lock);
|
|
|
|
old_fd = check_if_validated_client(c->username, c->username_len);
|
|
|
|
if (clientlist[i])
|
|
clientlist[i]->prevp = &cl->next;
|
|
cl->next = clientlist[i];
|
|
cl->prevp = &clientlist[i];
|
|
cl->client_id = (void *)c;
|
|
clientlist[i] = cl;
|
|
rwl_wrunlock(&clientlist_lock);
|
|
|
|
return old_fd;
|
|
}
|
|
|
|
/*
|
|
* Remove a client from the client list
|
|
*/
|
|
|
|
void clientlist_remove(struct client_t *c)
|
|
{
|
|
struct clientlist_t *cl;
|
|
|
|
DLOG("clientlist_remove '%s'", c->username);
|
|
|
|
/* get lock for list, find, and remove */
|
|
rwl_wrlock(&clientlist_lock);
|
|
|
|
cl = clientlist_find_id(c->username, c->username_len, (void *)c);
|
|
if (cl) {
|
|
DLOG("clientlist_remove found '%s'", c->username);
|
|
if (cl->next)
|
|
cl->next->prevp = cl->prevp;
|
|
*cl->prevp = cl->next;
|
|
|
|
hfree(cl);
|
|
}
|
|
|
|
rwl_wrunlock(&clientlist_lock);
|
|
}
|
|
|