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