1 /*
2  *	aprsc
3  *
4  *	(c) Heikki Hannikainen, OH7LZB <hessu@hes.iki.fi>
5  *
6  *     This program is licensed under the BSD license, which can be found
7  *     in the file LICENSE.
8  *
9  */
10 
11  /*
12   *	Clientlist contains a list of connected clients, along with their
13   *	verification status. It is updated when clients connect or disconnect,
14   *	and lookups are made more often by the Q construct algorithm.
15   *
16   *	This list is maintained so that the login handler can find duplicate
17   *	verified logins, and that the Q construct handler can lookup
18   *	verified logins without locking and walking the other worker thread's
19   *	client list.
20   *
21   *	TODO: the clientlist should use a hash for quick lookups
22   *	TODO: the clientlist should use cellmalloc
23   */
24 
25 #include <string.h>
26 
27 #include "clientlist.h"
28 #include "hmalloc.h"
29 #include "rwlock.h"
30 #include "keyhash.h"
31 #include "hlog.h"
32 
33 //#define CLIENTLIST_DEBUG
34 
35 #ifdef CLIENTLIST_DEBUG
36 #define DLOG(fmt, ...) \
37             do { hlog(LOG_DEBUG, fmt, __VA_ARGS__); } while (0)
38 #else
39 #define DLOG(fmt, ...)
40 #endif
41 
42 
43 struct clientlist_t {
44 	struct clientlist_t *next;
45 	struct clientlist_t **prevp;
46 
47 	char  username[16];     /* The callsign */
48 	int   username_len;
49 	int   validated;	/* Valid passcode given? */
50 	int   fd;               /* File descriptor, can be used by another
51 	                           thread to shut down a socket */
52 
53 	uint32_t hash;		/* hash value */
54 	void *client_id;	/* DO NOT REFERENCE - just used for an ID */
55 };
56 
57 /* hash buckets - serious overkill, but great for 40k clients */
58 #define CLIENTLIST_BUCKETS 512
59 struct clientlist_t *clientlist[CLIENTLIST_BUCKETS];
60 rwlock_t clientlist_lock = RWL_INITIALIZER;
61 
62 /*
63  *	Find a client by clientlist id - must hold either a read or write lock
64  *	before calling!
65  */
66 
clientlist_find_id(char * username,int len,void * id)67 static struct clientlist_t *clientlist_find_id(char *username, int len, void *id)
68 {
69 	struct clientlist_t *cl;
70 	uint32_t hash, idx;
71 	int i;
72 
73 	hash = keyhash(username, len, 0);
74 	idx = hash;
75 	// "CLIENT_HEARD_BUCKETS" is 512..
76 	idx ^= (idx >> 16);
77 	idx ^= (idx >>  8);
78 	i = idx % CLIENTLIST_BUCKETS;
79 
80 	DLOG("clientlist_find_id '%.*s' id %p bucket %d", len, username, id, i);
81 	for (cl = clientlist[i]; cl; cl = cl->next)
82 		if (cl->client_id == id) {
83 			DLOG("clientlist_find_id '%.*s' found %p", len, username, cl);
84 			return cl;
85 		}
86 
87 	return NULL;
88 }
89 
90 /*
91  *	Check if given usename is logged in and validated.
92  *	Return fd if found, -1 if not.
93  *	Internal variant, no locking.
94  */
95 
check_if_validated_client(char * username,int len)96 static int check_if_validated_client(char *username, int len)
97 {
98 	struct clientlist_t *cl;
99 	uint32_t hash, idx;
100 	int i;
101 
102 	hash = keyhash(username, len, 0);
103 	idx = hash;
104 	// "CLIENT_HEARD_BUCKETS" is 512..
105 	idx ^= (idx >> 16);
106 	idx ^= (idx >>  8);
107 	i = idx % CLIENTLIST_BUCKETS;
108 
109 	DLOG("check_if_validated_client '%.*s': bucket %d", len, username, i);
110 	for (cl = clientlist[i]; cl; cl = cl->next) {
111 		if (cl->hash == hash && memcmp(username, cl->username, len) == 0
112 		  && cl->username_len == len && cl->validated) {
113 			DLOG("check_if_validated_client '%.*s' found validated, fd %d", cl->username_len, cl->username, cl->fd);
114 		  	return cl->fd;
115 		}
116 	}
117 
118 	return -1;
119 }
120 
121 /*
122  *	Check if given usename is logged in and validated.
123  *	Return fd if found, -1 if not.
124  *	This is the external variant, with locking.
125  */
126 
clientlist_check_if_validated_client(char * username,int len)127 int clientlist_check_if_validated_client(char *username, int len)
128 {
129 	int fd;
130 
131 	rwl_rdlock(&clientlist_lock);
132 
133 	fd = check_if_validated_client(username, len);
134 
135 	rwl_rdunlock(&clientlist_lock);
136 
137 	return fd;
138 }
139 
140 /*
141  *	Add a client to the client list
142  */
143 
clientlist_add(struct client_t * c)144 int clientlist_add(struct client_t *c)
145 {
146 	struct clientlist_t *cl;
147 	int old_fd;
148 	uint32_t hash, idx;
149 	int i;
150 
151 	hash = keyhash(c->username, c->username_len, 0);
152 	idx = hash;
153 	// "CLIENT_HEARD_BUCKETS" is 512..
154 	idx ^= (idx >> 16);
155 	idx ^= (idx >>  8);
156 	i = idx % CLIENTLIST_BUCKETS;
157 
158 	DLOG("clientlist_add '%s': bucket %d", c->username, i);
159 
160 	/* allocate and fill in */
161 	cl = hmalloc(sizeof(*cl));
162 	strncpy(cl->username, c->username, sizeof(cl->username));
163 	cl->username[sizeof(cl->username)-1] = 0;
164 	cl->username_len = c->username_len;
165 	cl->validated = c->validated;
166 	cl->fd = c->fd;
167 	cl->hash = hash;
168 
169 	/* get lock for list and insert */
170 	rwl_wrlock(&clientlist_lock);
171 
172 	old_fd = check_if_validated_client(c->username, c->username_len);
173 
174 	if (clientlist[i])
175 		clientlist[i]->prevp = &cl->next;
176 	cl->next = clientlist[i];
177 	cl->prevp = &clientlist[i];
178 	cl->client_id = (void *)c;
179 	clientlist[i] = cl;
180 	rwl_wrunlock(&clientlist_lock);
181 
182 	return old_fd;
183 }
184 
185 /*
186  *	Remove a client from the client list
187  */
188 
clientlist_remove(struct client_t * c)189 void clientlist_remove(struct client_t *c)
190 {
191 	struct clientlist_t *cl;
192 
193 	DLOG("clientlist_remove '%s'", c->username);
194 
195 	/* get lock for list, find, and remove */
196 	rwl_wrlock(&clientlist_lock);
197 
198 	cl = clientlist_find_id(c->username, c->username_len, (void *)c);
199 	if (cl) {
200 		DLOG("clientlist_remove found '%s'", c->username);
201 		if (cl->next)
202 			cl->next->prevp = cl->prevp;
203 		*cl->prevp = cl->next;
204 
205 		hfree(cl);
206 	}
207 
208 	rwl_wrunlock(&clientlist_lock);
209 }
210 
211