1 | /********************************************************************************************************* |
---|
2 | * Software License Agreement (BSD License) * |
---|
3 | * Author: Sebastien Decugis <sdecugis@nict.go.jp> * |
---|
4 | * * |
---|
5 | * Copyright (c) 2010, WIDE Project and NICT * |
---|
6 | * All rights reserved. * |
---|
7 | * * |
---|
8 | * Redistribution and use of this software in source and binary forms, with or without modification, are * |
---|
9 | * permitted provided that the following conditions are met: * |
---|
10 | * * |
---|
11 | * * Redistributions of source code must retain the above * |
---|
12 | * copyright notice, this list of conditions and the * |
---|
13 | * following disclaimer. * |
---|
14 | * * |
---|
15 | * * Redistributions in binary form must reproduce the above * |
---|
16 | * copyright notice, this list of conditions and the * |
---|
17 | * following disclaimer in the documentation and/or other * |
---|
18 | * materials provided with the distribution. * |
---|
19 | * * |
---|
20 | * * Neither the name of the WIDE Project or NICT nor the * |
---|
21 | * names of its contributors may be used to endorse or * |
---|
22 | * promote products derived from this software without * |
---|
23 | * specific prior written permission of WIDE Project and * |
---|
24 | * NICT. * |
---|
25 | * * |
---|
26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * |
---|
27 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * |
---|
28 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * |
---|
29 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * |
---|
30 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * |
---|
31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * |
---|
32 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * |
---|
33 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * |
---|
34 | *********************************************************************************************************/ |
---|
35 | |
---|
36 | /* Manage the list of RADIUS clients, along with their shared secrets. */ |
---|
37 | |
---|
38 | /* Probably some changes are needed to support RADIUS Proxies */ |
---|
39 | |
---|
40 | #include "rgw.h" |
---|
41 | |
---|
42 | #define REVERSE_DNS_SIZE_MAX 512 /* length of our buffer for reverse DNS */ |
---|
43 | #define DUPLICATE_CHECK_LIFETIME 60 /* number of seconds that the received RADIUS records are kept for duplicate checking . TODO: make it configurable if needed */ |
---|
44 | |
---|
45 | /* Ordered lists of clients. The order relationship is a memcmp on the address zone. |
---|
46 | For same addresses, the port is compared. |
---|
47 | The same address cannot be added twice, once with a 0-port and once with another port value. |
---|
48 | */ |
---|
49 | static struct fd_list cli_ip = FD_LIST_INITIALIZER(cli_ip); |
---|
50 | static struct fd_list cli_ip6 = FD_LIST_INITIALIZER(cli_ip6); |
---|
51 | |
---|
52 | /* Lock to protect the previous lists. We use a rwlock because this list is mostly static, to allow parallel reading */ |
---|
53 | static pthread_rwlock_t cli_rwl = PTHREAD_RWLOCK_INITIALIZER; |
---|
54 | |
---|
55 | /* Structure describing one received RADIUS message, for duplicate checks purpose. */ |
---|
56 | struct req_info { |
---|
57 | uint16_t port; /* UDP source port of the request */ |
---|
58 | uint8_t id; /* The identifier in the request header */ |
---|
59 | uint8_t auth[16]; /* Request authenticator, since some RADIUS clients do not implement the id mechanism properly. */ |
---|
60 | struct radius_msg *ans; /* The replied answer if any, in case the previous answer got lost. */ |
---|
61 | |
---|
62 | int nbdup; /* Number of times this request was received as a duplicate */ |
---|
63 | struct fd_list by_id; /* The list of requests ordered by their id, port, and auth */ |
---|
64 | time_t received; /* When was the last duplicate received? */ |
---|
65 | struct fd_list by_time; /* The list of requests ordered by the 'received' value . */ |
---|
66 | }; |
---|
67 | |
---|
68 | static pthread_t dbt_expire = (pthread_t)NULL; /* The thread that will remove old requests information from all clients (one thread for all) */ |
---|
69 | |
---|
70 | /* Structure describing one client */ |
---|
71 | struct rgw_client { |
---|
72 | /* Link information in global list (cli_ip or cli_ip6) */ |
---|
73 | struct fd_list chain; |
---|
74 | |
---|
75 | /* Reference count */ |
---|
76 | int refcount; |
---|
77 | |
---|
78 | /* The address and optional port (alloc'd during configuration file parsing). */ |
---|
79 | union { |
---|
80 | struct sockaddr *sa; /* generic pointer */ |
---|
81 | struct sockaddr_in *sin; |
---|
82 | struct sockaddr_in6 *sin6; |
---|
83 | }; |
---|
84 | |
---|
85 | /* The FQDN, realm, and optional aliases */ |
---|
86 | int is_local; /* true if the RADIUS client runs on the same host -- we use Diameter Identity in that case */ |
---|
87 | enum rgw_cli_type type; /* is it a proxy ? */ |
---|
88 | char *fqdn; |
---|
89 | size_t fqdn_len; |
---|
90 | char *realm; |
---|
91 | char **aliases; |
---|
92 | size_t aliases_nb; |
---|
93 | |
---|
94 | /* The secret key data. */ |
---|
95 | struct { |
---|
96 | unsigned char * data; |
---|
97 | size_t len; |
---|
98 | } key; |
---|
99 | |
---|
100 | /* information of previous msg received, for duplicate checks. */ |
---|
101 | struct { |
---|
102 | pthread_mutex_t dupl_lock; /* The mutex protecting the following lists */ |
---|
103 | struct fd_list dupl_by_id; /* The list of req_info structures ordered by their id, port, and auth */ |
---|
104 | struct fd_list dupl_by_time; /* The list of req_info structures ordered by their time (approximative) */ |
---|
105 | } dupl_info[2]; /*[0] for auth, [1] for acct. */ |
---|
106 | }; |
---|
107 | |
---|
108 | |
---|
109 | /* Create a new req_info structure and initialize its data from a RADIUS request message */ |
---|
110 | static struct req_info * dupl_new_req_info(struct rgw_radius_msg_meta *msg) { |
---|
111 | struct req_info * ret = NULL; |
---|
112 | CHECK_MALLOC_DO( ret = malloc(sizeof(struct req_info)), return NULL ); |
---|
113 | memset(ret, 0, sizeof(struct req_info)); |
---|
114 | ret->port = msg->port; |
---|
115 | ret->id = msg->radius.hdr->identifier; |
---|
116 | memcpy(&ret->auth[0], &msg->radius.hdr->authenticator[0], 16); |
---|
117 | fd_list_init(&ret->by_id, ret); |
---|
118 | fd_list_init(&ret->by_time, ret); |
---|
119 | ret->received = time(NULL); |
---|
120 | return ret; |
---|
121 | } |
---|
122 | |
---|
123 | /* Destroy a req_info structure, after it has been unlinked */ |
---|
124 | static void dupl_free_req_info(struct req_info * r) { |
---|
125 | CHECK_PARAMS_DO( r && FD_IS_LIST_EMPTY(&r->by_id) && FD_IS_LIST_EMPTY(&r->by_time), return ); |
---|
126 | if (r->ans) { |
---|
127 | /* Free this RADIUS message */ |
---|
128 | radius_msg_free(r->ans); |
---|
129 | free(r->ans); |
---|
130 | } |
---|
131 | |
---|
132 | /* Use r->nbdup for some purpose? */ |
---|
133 | |
---|
134 | free(r); |
---|
135 | } |
---|
136 | |
---|
137 | /* The core of the purge thread */ |
---|
138 | static int dupl_purge_list(struct fd_list * clients) { |
---|
139 | |
---|
140 | struct fd_list *li = NULL; |
---|
141 | |
---|
142 | for (li = clients->next; li != clients; li = li->next) { |
---|
143 | struct rgw_client * client = (struct rgw_client *)li; |
---|
144 | int p; |
---|
145 | |
---|
146 | for (p=0; p<=1; p++) { |
---|
147 | |
---|
148 | /* Lock this list */ |
---|
149 | time_t now; |
---|
150 | CHECK_POSIX( pthread_mutex_lock(&client->dupl_info[p].dupl_lock) ); |
---|
151 | |
---|
152 | now = time(NULL); |
---|
153 | |
---|
154 | while (!FD_IS_LIST_EMPTY(&client->dupl_info[p].dupl_by_time)) { |
---|
155 | |
---|
156 | /* Check the first item in the list */ |
---|
157 | struct req_info * r = (struct req_info *)(client->dupl_info[p].dupl_by_time.next->o); |
---|
158 | |
---|
159 | if (now - r->received > DUPLICATE_CHECK_LIFETIME) { |
---|
160 | |
---|
161 | TRACE_DEBUG(ANNOYING + 1, "Purging RADIUS request (id: %02hhx, port: %hu, dup #%d, age %d secs)", r->id, ntohs(r->port), r->nbdup, now - r->received); |
---|
162 | |
---|
163 | /* Remove this record */ |
---|
164 | fd_list_unlink(&r->by_time); |
---|
165 | fd_list_unlink(&r->by_id); |
---|
166 | dupl_free_req_info(r); |
---|
167 | } else { |
---|
168 | /* We are done for this list */ |
---|
169 | break; |
---|
170 | } |
---|
171 | } |
---|
172 | |
---|
173 | CHECK_POSIX( pthread_mutex_unlock(&client->dupl_info[p].dupl_lock) ); |
---|
174 | } |
---|
175 | } |
---|
176 | return 0; |
---|
177 | } |
---|
178 | |
---|
179 | /* Thread that purges old RADIUS requests */ |
---|
180 | static void * dupl_th(void * arg) { |
---|
181 | /* Set the thread name */ |
---|
182 | fd_log_threadname ( "app_radgw:duplicate_purge" ); |
---|
183 | |
---|
184 | /* The thread will be canceled */ |
---|
185 | while (1) { |
---|
186 | |
---|
187 | /* We don't use a cond var, we simply wake up every 5 seconds. If the size of the duplicate cache is critical, it might be changed */ |
---|
188 | sleep(5); |
---|
189 | |
---|
190 | /* When we wake up, we will check all clients duplicate lists one by one */ |
---|
191 | CHECK_POSIX_DO( pthread_rwlock_rdlock(&cli_rwl), break ); |
---|
192 | |
---|
193 | CHECK_FCT_DO( dupl_purge_list(&cli_ip), break ); |
---|
194 | CHECK_FCT_DO( dupl_purge_list(&cli_ip6), break ); |
---|
195 | |
---|
196 | CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), break ); |
---|
197 | |
---|
198 | /* Loop */ |
---|
199 | } |
---|
200 | |
---|
201 | /* If we reach this part, some fatal error was encountered */ |
---|
202 | CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); |
---|
203 | TRACE_DEBUG(FULL, "Thread terminated"); |
---|
204 | return NULL; |
---|
205 | } |
---|
206 | |
---|
207 | |
---|
208 | /* create a new rgw_client. the arguments are MOVED into the structure (to limit malloc & free calls). */ |
---|
209 | static int client_create(struct rgw_client ** res, struct sockaddr ** ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) |
---|
210 | { |
---|
211 | struct rgw_client *tmp = NULL; |
---|
212 | char buf[255]; |
---|
213 | int ret, i; |
---|
214 | int loc = 0; |
---|
215 | |
---|
216 | /* Check if the IP address is local */ |
---|
217 | if ( ( ((*ip_port)->sa_family == AF_INET ) && ( IN_IS_ADDR_LOOPBACK( &((struct sockaddr_in *)(*ip_port))->sin_addr ) ) ) |
---|
218 | ||( ((*ip_port)->sa_family == AF_INET6) && ( IN6_IS_ADDR_LOOPBACK( &((struct sockaddr_in6 *)(*ip_port))->sin6_addr) ) )) { |
---|
219 | /* The client is local */ |
---|
220 | loc = 1; |
---|
221 | } else { |
---|
222 | |
---|
223 | /* Search FQDN for the client */ |
---|
224 | ret = getnameinfo( *ip_port, sizeof(struct sockaddr_storage), &buf[0], sizeof(buf), NULL, 0, 0 ); |
---|
225 | if (ret) { |
---|
226 | TRACE_DEBUG(INFO, "Unable to resolve peer name: %s", gai_strerror(ret)); |
---|
227 | return EINVAL; |
---|
228 | } |
---|
229 | } |
---|
230 | |
---|
231 | /* Create the new object */ |
---|
232 | CHECK_MALLOC( tmp = malloc(sizeof (struct rgw_client)) ); |
---|
233 | memset(tmp, 0, sizeof(struct rgw_client)); |
---|
234 | fd_list_init(&tmp->chain, NULL); |
---|
235 | |
---|
236 | /* Initialize the duplicate list info */ |
---|
237 | for (i=0; i<=1; i++) { |
---|
238 | CHECK_POSIX( pthread_mutex_init(&tmp->dupl_info[i].dupl_lock, NULL) ); |
---|
239 | fd_list_init(&tmp->dupl_info[i].dupl_by_id, NULL); |
---|
240 | fd_list_init(&tmp->dupl_info[i].dupl_by_time, NULL); |
---|
241 | } |
---|
242 | tmp->type = type; |
---|
243 | |
---|
244 | if (loc) { |
---|
245 | tmp->is_local = 1; |
---|
246 | } else { |
---|
247 | /* Copy the fqdn */ |
---|
248 | CHECK_MALLOC( tmp->fqdn = strdup(buf) ); |
---|
249 | tmp->fqdn_len = strlen(tmp->fqdn); |
---|
250 | /* Find an appropriate realm */ |
---|
251 | tmp->realm = strchr(tmp->fqdn, '.'); |
---|
252 | if (tmp->realm) |
---|
253 | tmp->realm += 1; |
---|
254 | if ((!tmp->realm) || (*tmp->realm == '\0')) /* in case the fqdn was "localhost." for example, if it is possible... */ |
---|
255 | tmp->realm = fd_g_config->cnf_diamrlm; |
---|
256 | } |
---|
257 | |
---|
258 | /* move the sa info reference */ |
---|
259 | tmp->sa = *ip_port; |
---|
260 | *ip_port = NULL; |
---|
261 | |
---|
262 | /* move the key material */ |
---|
263 | tmp->key.data = *key; |
---|
264 | tmp->key.len = keylen; |
---|
265 | *key = NULL; |
---|
266 | |
---|
267 | /* Done! */ |
---|
268 | *res = tmp; |
---|
269 | return 0; |
---|
270 | } |
---|
271 | |
---|
272 | /* Decrease refcount on a client; the lock must be held when this function is called. */ |
---|
273 | static void client_unlink(struct rgw_client * client) |
---|
274 | { |
---|
275 | client->refcount -= 1; |
---|
276 | |
---|
277 | if (client->refcount <= 0) { |
---|
278 | int idx; |
---|
279 | /* to be sure: the refcount should be 0 only when client_fini is called */ |
---|
280 | ASSERT( FD_IS_LIST_EMPTY(&client->chain) ); |
---|
281 | |
---|
282 | /* Free the data */ |
---|
283 | for (idx = 0; idx < client->aliases_nb; idx++) |
---|
284 | free(client->aliases[idx]); |
---|
285 | free(client->aliases); |
---|
286 | free(client->fqdn); |
---|
287 | free(client->sa); |
---|
288 | free(client->key.data); |
---|
289 | |
---|
290 | /* Free the duplicate info */ |
---|
291 | for (idx=0; idx <= 1; idx++){ |
---|
292 | CHECK_POSIX_DO( pthread_mutex_lock( &client->dupl_info[idx].dupl_lock ), /* continue */ ); |
---|
293 | |
---|
294 | while (!FD_IS_LIST_EMPTY(&client->dupl_info[idx].dupl_by_id)) { |
---|
295 | struct req_info * r = (struct req_info *)(client->dupl_info[idx].dupl_by_id.next->o); |
---|
296 | fd_list_unlink( &r->by_id ); |
---|
297 | fd_list_unlink( &r->by_time ); |
---|
298 | dupl_free_req_info(r); |
---|
299 | } |
---|
300 | |
---|
301 | CHECK_POSIX_DO( pthread_mutex_unlock( &client->dupl_info[idx].dupl_lock ), /* continue */ ); |
---|
302 | |
---|
303 | } |
---|
304 | |
---|
305 | free(client); |
---|
306 | } |
---|
307 | } |
---|
308 | |
---|
309 | |
---|
310 | /* Macro to avoid duplicating the code in the next function */ |
---|
311 | #define client_search_family( _family_ ) \ |
---|
312 | case AF_INET##_family_: { \ |
---|
313 | struct sockaddr_in##_family_ * sin##_family_ = (struct sockaddr_in##_family_ *)ip_port; \ |
---|
314 | for (ref = cli_ip##_family_.next; ref != &cli_ip##_family_; ref = ref->next) { \ |
---|
315 | cmp = memcmp(&sin##_family_->sin##_family_##_addr, \ |
---|
316 | &((struct rgw_client *)ref)->sin##_family_->sin##_family_##_addr, \ |
---|
317 | sizeof(struct in##_family_##_addr)); \ |
---|
318 | if (cmp > 0) continue; /* search further in the list */ \ |
---|
319 | if (cmp < 0) break; /* this IP is not in the list */ \ |
---|
320 | /* Now compare the ports as follow: */ \ |
---|
321 | /* If the ip_port we are searching does not contain a port, just return the first match result */ \ |
---|
322 | if ( (sin##_family_->sin##_family_##_port == 0) \ |
---|
323 | /* If the entry in the list does not contain a port, return it as a match */ \ |
---|
324 | || (((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port == 0) \ |
---|
325 | /* If both ports are equal, it is a match */ \ |
---|
326 | || (sin##_family_->sin##_family_##_port == \ |
---|
327 | ((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port)) { \ |
---|
328 | *res = (struct rgw_client *)ref; \ |
---|
329 | return EEXIST; \ |
---|
330 | } \ |
---|
331 | /* Otherwise, the list is ordered by port value (byte order does not matter */ \ |
---|
332 | if (sin##_family_->sin##_family_##_port \ |
---|
333 | > ((struct rgw_client *)ref)->sin##_family_->sin##_family_##_port) continue; \ |
---|
334 | else break; \ |
---|
335 | } \ |
---|
336 | *res = (struct rgw_client *)(ref->prev); \ |
---|
337 | return ENOENT; \ |
---|
338 | } |
---|
339 | /* Function to look for an existing rgw_client, or the previous element. |
---|
340 | The cli_rwl must be held for reading (at least) when calling this function. |
---|
341 | Returns ENOENT if the matching client does not exist, and res points to the previous element in the list. |
---|
342 | Returns EEXIST if the matching client is found, and res points to this element. |
---|
343 | Returns other error code on other error. */ |
---|
344 | static int client_search(struct rgw_client ** res, struct sockaddr * ip_port ) |
---|
345 | { |
---|
346 | int cmp; |
---|
347 | struct fd_list *ref = NULL; |
---|
348 | |
---|
349 | CHECK_PARAMS(res && ip_port); |
---|
350 | |
---|
351 | switch (ip_port->sa_family) { |
---|
352 | client_search_family() |
---|
353 | break; |
---|
354 | |
---|
355 | client_search_family( 6 ) |
---|
356 | break; |
---|
357 | } |
---|
358 | |
---|
359 | /* We're never supposed to reach this point */ |
---|
360 | ASSERT(0); |
---|
361 | return EINVAL; |
---|
362 | } |
---|
363 | |
---|
364 | int rgw_clients_getkey(struct rgw_client * cli, unsigned char **key, size_t *key_len) |
---|
365 | { |
---|
366 | CHECK_PARAMS( cli && key && key_len ); |
---|
367 | *key = cli->key.data; |
---|
368 | *key_len = cli->key.len; |
---|
369 | return 0; |
---|
370 | } |
---|
371 | |
---|
372 | int rgw_clients_gettype(struct rgw_client * cli, enum rgw_cli_type *type) |
---|
373 | { |
---|
374 | CHECK_PARAMS( cli && type ); |
---|
375 | *type = cli->type; |
---|
376 | return 0; |
---|
377 | } |
---|
378 | |
---|
379 | |
---|
380 | int rgw_clients_search(struct sockaddr * ip_port, struct rgw_client ** ref) |
---|
381 | { |
---|
382 | int ret = 0; |
---|
383 | |
---|
384 | TRACE_ENTRY("%p %p", ip_port, ref); |
---|
385 | |
---|
386 | CHECK_PARAMS(ip_port && ref); |
---|
387 | |
---|
388 | CHECK_POSIX( pthread_rwlock_rdlock(&cli_rwl) ); |
---|
389 | |
---|
390 | ret = client_search(ref, ip_port); |
---|
391 | if (ret == EEXIST) { |
---|
392 | (*ref)->refcount ++; |
---|
393 | ret = 0; |
---|
394 | } else { |
---|
395 | *ref = NULL; |
---|
396 | } |
---|
397 | |
---|
398 | CHECK_POSIX( pthread_rwlock_unlock(&cli_rwl) ); |
---|
399 | |
---|
400 | return ret; |
---|
401 | } |
---|
402 | |
---|
403 | int rgw_clients_check_dup(struct rgw_radius_msg_meta **msg, struct rgw_client *cli) |
---|
404 | { |
---|
405 | int p, dup = 0; |
---|
406 | struct fd_list * li; |
---|
407 | struct req_info * r; |
---|
408 | |
---|
409 | TRACE_ENTRY("%p %p", msg, cli); |
---|
410 | |
---|
411 | CHECK_PARAMS( msg && cli ); |
---|
412 | |
---|
413 | if ((*msg)->serv_type == RGW_PLG_TYPE_AUTH) |
---|
414 | p = 0; |
---|
415 | else |
---|
416 | p = 1; |
---|
417 | |
---|
418 | CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) ); |
---|
419 | |
---|
420 | /* Search if we have this message in our list */ |
---|
421 | for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) { |
---|
422 | int cmp = 0; |
---|
423 | r = (struct req_info *)(li->o); |
---|
424 | if (r->id < (*msg)->radius.hdr->identifier) |
---|
425 | continue; |
---|
426 | if (r->id > (*msg)->radius.hdr->identifier) |
---|
427 | break; |
---|
428 | if (r->port < (*msg)->port) |
---|
429 | continue; |
---|
430 | if (r->port > (*msg)->port) |
---|
431 | break; |
---|
432 | cmp = memcmp(&r->auth[0], &(*msg)->radius.hdr->authenticator[0], 16); |
---|
433 | if (cmp < 0) |
---|
434 | continue; |
---|
435 | if (cmp > 0) |
---|
436 | break; |
---|
437 | dup = 1; |
---|
438 | break; |
---|
439 | } |
---|
440 | |
---|
441 | if (dup) { |
---|
442 | time_t now = time(NULL); |
---|
443 | r->nbdup += 1; |
---|
444 | TRACE_DEBUG(INFO, "Received duplicated RADIUS message (id: %02hhx, port: %hu, dup #%d, previously seen %d secs ago).", |
---|
445 | r->id, ntohs(r->port), r->nbdup, now - r->received); |
---|
446 | |
---|
447 | if (r->ans) { |
---|
448 | /* Resend the answer */ |
---|
449 | CHECK_FCT_DO( rgw_servers_send((*msg)->serv_type, r->ans->buf, r->ans->buf_used, cli->sa, r->port), ); |
---|
450 | |
---|
451 | /* Should we delete 'r' so that a further duplicate will again be converted to Diameter? */ |
---|
452 | } |
---|
453 | |
---|
454 | /* Update the timestamp */ |
---|
455 | r->received = now; |
---|
456 | fd_list_unlink(&r->by_time); |
---|
457 | fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time); /* Move as last entry, since it is the most recent */ |
---|
458 | |
---|
459 | /* Delete the request message */ |
---|
460 | rgw_msg_free(msg); |
---|
461 | |
---|
462 | } else { |
---|
463 | /* The message was not a duplicate, we save it */ |
---|
464 | /* li currently points the the next entry in list_by_id */ |
---|
465 | CHECK_MALLOC_DO( r= dupl_new_req_info(*msg), { CHECK_POSIX_DO(pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ), ); return ENOMEM; } ); |
---|
466 | fd_list_insert_before(li, &r->by_id); |
---|
467 | fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time); /* it is the most recent */ |
---|
468 | } |
---|
469 | |
---|
470 | CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) ); |
---|
471 | |
---|
472 | return 0; |
---|
473 | } |
---|
474 | |
---|
475 | /* Check if the message has a valid authenticator, and update the meta-data accordingly */ |
---|
476 | int rgw_clients_auth_check(struct rgw_radius_msg_meta * msg, struct rgw_client * cli, uint8_t * req_auth) |
---|
477 | { |
---|
478 | unsigned char * key; |
---|
479 | size_t keylen; |
---|
480 | int count; |
---|
481 | |
---|
482 | TRACE_ENTRY("%p %p %p", msg, cli, req_auth); |
---|
483 | |
---|
484 | CHECK_PARAMS(msg && cli); |
---|
485 | |
---|
486 | CHECK_FCT(rgw_clients_getkey(cli, &key, &keylen)); |
---|
487 | |
---|
488 | count = radius_msg_count_attr(&msg->radius, RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 0); |
---|
489 | if (count > 1) { |
---|
490 | TRACE_DEBUG(INFO, "Too many Message-Authenticator attributes (%d), discarding message.", count); |
---|
491 | return EINVAL; |
---|
492 | } |
---|
493 | if (count == 0) { |
---|
494 | TRACE_DEBUG(FULL, "Message does not contain a Message-Authenticator attributes."); |
---|
495 | msg->valid_mac = 0; |
---|
496 | } else { |
---|
497 | if (radius_msg_verify_msg_auth( &msg->radius, key, keylen, req_auth )) { |
---|
498 | TRACE_DEBUG(INFO, "Invalid Message-Authenticator received, discarding message."); |
---|
499 | return EINVAL; |
---|
500 | } |
---|
501 | msg->valid_mac = 1; |
---|
502 | } |
---|
503 | |
---|
504 | return 0; |
---|
505 | } |
---|
506 | |
---|
507 | static struct dict_object * cache_orig_host = NULL; |
---|
508 | static struct dict_object * cache_orig_realm = NULL; |
---|
509 | static struct dict_object * cache_route_record = NULL; |
---|
510 | |
---|
511 | int rgw_clients_init(void) |
---|
512 | { |
---|
513 | TRACE_ENTRY(); |
---|
514 | CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Host", &cache_orig_host, ENOENT) ); |
---|
515 | CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Origin-Realm", &cache_orig_realm, ENOENT) ); |
---|
516 | CHECK_FCT( fd_dict_search(fd_g_config->cnf_dict, DICT_AVP, AVP_BY_NAME, "Route-Record", &cache_route_record, ENOENT) ); |
---|
517 | |
---|
518 | /* Create the thread that will purge old RADIUS duplicates */ |
---|
519 | CHECK_POSIX( pthread_create( &dbt_expire, NULL, dupl_th, NULL) ); |
---|
520 | |
---|
521 | return 0; |
---|
522 | } |
---|
523 | |
---|
524 | |
---|
525 | /* The following function checks if a RADIUS message contains a valid NAS identifier, and initializes an empty Diameter |
---|
526 | message with the appropriate routing information */ |
---|
527 | /* Check that the NAS-IP-Adress or NAS-Identifier is coherent with the IP the packet was received from */ |
---|
528 | /* Also update the client list of aliases if needed */ |
---|
529 | int rgw_clients_create_origin(struct rgw_radius_msg_meta *msg, struct rgw_client * cli, struct msg ** diam) |
---|
530 | { |
---|
531 | int idx; |
---|
532 | int valid_nas_info = 0; |
---|
533 | struct radius_attr_hdr *nas_ip = NULL, *nas_ip6 = NULL, *nas_id = NULL; |
---|
534 | char * oh_str = NULL; |
---|
535 | char * or_str = NULL; |
---|
536 | char * rr_str = NULL; |
---|
537 | char buf[REVERSE_DNS_SIZE_MAX]; /* to store DNS lookups results */ |
---|
538 | |
---|
539 | struct avp *avp = NULL; |
---|
540 | union avp_value avp_val; |
---|
541 | |
---|
542 | TRACE_ENTRY("%p %p %p", msg, cli, diam); |
---|
543 | CHECK_PARAMS(msg && cli && diam && (*diam == NULL)); |
---|
544 | |
---|
545 | /* Find the relevant attributes, if any */ |
---|
546 | for (idx = 0; idx < msg->radius.attr_used; idx++) { |
---|
547 | struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(msg->radius.buf + msg->radius.attr_pos[idx]); |
---|
548 | size_t attr_len = attr->length - sizeof(struct radius_attr_hdr); |
---|
549 | |
---|
550 | if ((attr->type == RADIUS_ATTR_NAS_IP_ADDRESS) && (attr_len = 4)) { |
---|
551 | nas_ip = attr; |
---|
552 | continue; |
---|
553 | } |
---|
554 | |
---|
555 | if ((attr->type == RADIUS_ATTR_NAS_IDENTIFIER) && (attr_len > 0)) { |
---|
556 | nas_id = attr; |
---|
557 | continue; |
---|
558 | } |
---|
559 | |
---|
560 | if ((attr->type == RADIUS_ATTR_NAS_IPV6_ADDRESS) && (attr_len = 16)) { |
---|
561 | nas_ip6 = attr; |
---|
562 | continue; |
---|
563 | } |
---|
564 | } |
---|
565 | |
---|
566 | if (!nas_ip && !nas_ip6 && !nas_id) { |
---|
567 | TRACE_DEBUG(FULL, "The message does not contain any NAS identification attribute."); |
---|
568 | |
---|
569 | /* Get information on this peer */ |
---|
570 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
571 | |
---|
572 | goto diameter; |
---|
573 | } |
---|
574 | |
---|
575 | /* Check if the message was received from the IP in NAS-IP-Address attribute */ |
---|
576 | if (nas_ip && (cli->sa->sa_family == AF_INET) && !memcmp(nas_ip+1, &cli->sin->sin_addr, sizeof(struct in_addr))) { |
---|
577 | TRACE_DEBUG(FULL, "NAS-IP-Address contains the same address as the message was received from."); |
---|
578 | valid_nas_info |= 1; |
---|
579 | } |
---|
580 | if (nas_ip6 && (cli->sa->sa_family == AF_INET6) && !memcmp(nas_ip6+1, &cli->sin6->sin6_addr, sizeof(struct in6_addr))) { |
---|
581 | TRACE_DEBUG(FULL, "NAS-IPv6-Address contains the same address as the message was received from."); |
---|
582 | valid_nas_info |= 2; |
---|
583 | } |
---|
584 | |
---|
585 | |
---|
586 | /* |
---|
587 | In RADIUS it would be possible for a rogue NAS to forge the NAS-IP- |
---|
588 | Address attribute value. Diameter/RADIUS translation agents MUST |
---|
589 | check a received NAS-IP-Address or NAS-IPv6-Address attribute against |
---|
590 | the source address of the RADIUS packet. If they do not match and |
---|
591 | the Diameter/RADIUS translation agent does not know whether the |
---|
592 | packet was sent by a RADIUS proxy or NAS (e.g., no Proxy-State |
---|
593 | attribute), then by default it is assumed that the source address |
---|
594 | corresponds to a RADIUS proxy, and that the NAS Address is behind |
---|
595 | that proxy, potentially with some additional RADIUS proxies in |
---|
596 | between. The Diameter/RADIUS translation agent MUST insert entries |
---|
597 | in the Route-Record AVP corresponding to the apparent route. This |
---|
598 | implies doing a reverse lookup on the source address and NAS-IP- |
---|
599 | Address or NAS-IPv6-Address attributes to determine the corresponding |
---|
600 | FQDNs. |
---|
601 | |
---|
602 | If the source address and the NAS-IP-Address or NAS-IPv6-Address do |
---|
603 | not match, and the Diameter/RADIUS translation agent knows that it is |
---|
604 | talking directly to the NAS (e.g., there are no RADIUS proxies |
---|
605 | between it and the NAS), then the error should be logged, and the |
---|
606 | packet MUST be discarded. |
---|
607 | |
---|
608 | Diameter agents and servers MUST check whether the NAS-IP-Address AVP |
---|
609 | corresponds to an entry in the Route-Record AVP. This is done by |
---|
610 | doing a reverse lookup (PTR RR) for the NAS-IP-Address to retrieve |
---|
611 | the corresponding FQDN, and by checking for a match with the Route- |
---|
612 | Record AVP. If no match is found, then an error is logged, but no |
---|
613 | other action is taken. |
---|
614 | */ |
---|
615 | if (nas_ip || nas_ip6) { |
---|
616 | if (!valid_nas_info) { |
---|
617 | if ((!cli->is_local) && (cli->type == RGW_CLI_NAS)) { |
---|
618 | TRACE_DEBUG(INFO, "Message received with a NAS-IP-Address or NAS-IPv6-Address different \nfrom the sender's. Please configure as Proxy if this is expected.\n Message discarded."); |
---|
619 | return EINVAL; |
---|
620 | } else { |
---|
621 | /* the peer is configured as a proxy, or running on localhost, so accept the message */ |
---|
622 | sSS ss; |
---|
623 | |
---|
624 | /* In that case, the cli will be stored as Route-Record and the NAS-IP-Address as origin */ |
---|
625 | if (!cli->is_local) { |
---|
626 | rr_str = cli->fqdn; |
---|
627 | } |
---|
628 | |
---|
629 | /* We must DNS-reverse the NAS-IP*-Address */ |
---|
630 | memset(&ss, 0 , sizeof(sSS)); |
---|
631 | if (nas_ip) { |
---|
632 | sSA4 * sin = (sSA4 *)&ss; |
---|
633 | sin->sin_family = AF_INET; |
---|
634 | memcpy(&sin->sin_addr, nas_ip + 1, sizeof(struct in_addr)); |
---|
635 | } else { |
---|
636 | sSA6 * sin6 = (sSA6 *)&ss; |
---|
637 | sin6->sin6_family = AF_INET6; |
---|
638 | memcpy(&sin6->sin6_addr, nas_ip6 + 1, sizeof(struct in6_addr)); |
---|
639 | } |
---|
640 | CHECK_SYS_DO( getnameinfo( (sSA *)&ss, sSAlen(&ss), &buf[0], sizeof(buf), NULL, 0, NI_NAMEREQD), |
---|
641 | { |
---|
642 | if (cli->is_local) { |
---|
643 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
644 | goto diameter; |
---|
645 | } |
---|
646 | |
---|
647 | TRACE_DEBUG(INFO, "The NAS-IP*-Address cannot be DNS reversed in order to create the Origin-Host AVP; rejecting the message (translation is impossible)."); |
---|
648 | return EINVAL; |
---|
649 | } ); |
---|
650 | |
---|
651 | oh_str = &buf[0]; |
---|
652 | or_str = strchr(oh_str, '.'); |
---|
653 | if (or_str) { |
---|
654 | or_str ++; /* move after the first dot */ |
---|
655 | if (*or_str == '\0') |
---|
656 | or_str = NULL; /* Discard this realm, we will use the local realm later */ |
---|
657 | } |
---|
658 | } |
---|
659 | } else { |
---|
660 | /* The attribute matches the source address, just use this in origin-host */ |
---|
661 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
662 | } |
---|
663 | |
---|
664 | goto diameter; /* we ignore the nas_id in that case */ |
---|
665 | } |
---|
666 | |
---|
667 | /* We don't have a NAS-IP*-Address attribute if we are here */ |
---|
668 | if (cli->is_local) { |
---|
669 | /* Simple: we use our own configuration */ |
---|
670 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
671 | goto diameter; |
---|
672 | } |
---|
673 | |
---|
674 | /* At this point, we only have nas_id, and the client is not local */ |
---|
675 | ASSERT(nas_id); |
---|
676 | |
---|
677 | { |
---|
678 | int found, ret; |
---|
679 | struct addrinfo hint, *res, *ptr; |
---|
680 | |
---|
681 | /* |
---|
682 | In RADIUS it would be possible for a rogue NAS to forge the NAS- |
---|
683 | Identifier attribute. Diameter/RADIUS translation agents SHOULD |
---|
684 | attempt to check a received NAS-Identifier attribute against the |
---|
685 | source address of the RADIUS packet, by doing an A/AAAA RR query. If |
---|
686 | the NAS-Identifier attribute contains an FQDN, then such a query |
---|
687 | would resolve to an IP address matching the source address. However, |
---|
688 | the NAS-Identifier attribute is not required to contain an FQDN, so |
---|
689 | such a query could fail. If it fails, an error should be logged, but |
---|
690 | no action should be taken, other than a reverse lookup on the source |
---|
691 | address and insert the resulting FQDN into the Route-Record AVP. |
---|
692 | |
---|
693 | Diameter agents and servers SHOULD check whether a NAS-Identifier AVP |
---|
694 | corresponds to an entry in the Route-Record AVP. If no match is |
---|
695 | found, then an error is logged, but no other action is taken. |
---|
696 | */ |
---|
697 | |
---|
698 | /* first, check if the nas_id is the fqdn of the peer or a known alias */ |
---|
699 | if ((cli->fqdn_len == (nas_id->length - sizeof(struct radius_attr_hdr))) |
---|
700 | && (!strncasecmp((char *)(nas_id + 1), cli->fqdn, nas_id->length - sizeof(struct radius_attr_hdr)))) { |
---|
701 | TRACE_DEBUG(FULL, "NAS-Identifier contains the fqdn of the client"); |
---|
702 | found = 1; |
---|
703 | } else { |
---|
704 | for (idx = 0; idx < cli->aliases_nb; idx++) { |
---|
705 | if (((nas_id->length - sizeof(struct radius_attr_hdr)) == strlen(cli->aliases[idx])) |
---|
706 | && (!strncasecmp((char *)(nas_id + 1), cli->aliases[idx], nas_id->length - sizeof(struct radius_attr_hdr)))) { |
---|
707 | TRACE_DEBUG(FULL, "NAS-Identifier valid value found in the cache"); |
---|
708 | found = 1; |
---|
709 | break; |
---|
710 | } |
---|
711 | } |
---|
712 | } |
---|
713 | |
---|
714 | if (found) { |
---|
715 | /* The NAS-Identifier matches the source IP */ |
---|
716 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
717 | |
---|
718 | goto diameter; |
---|
719 | } |
---|
720 | |
---|
721 | /* Attempt DNS resolution of the identifier */ |
---|
722 | ASSERT( nas_id->length - sizeof(struct radius_attr_hdr) < sizeof(buf) ); |
---|
723 | memcpy(buf, nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); |
---|
724 | buf[nas_id->length - sizeof(struct radius_attr_hdr)] = '\0'; |
---|
725 | |
---|
726 | /* Now check if this alias is valid for this peer */ |
---|
727 | memset(&hint, 0, sizeof(hint)); |
---|
728 | hint.ai_flags = AI_CANONNAME; |
---|
729 | ret = getaddrinfo(buf, NULL, &hint, &res); |
---|
730 | if (ret == 0) { |
---|
731 | strncpy(buf, res->ai_canonname, sizeof(buf)); |
---|
732 | /* The name was resolved correctly, does it match the IP of the client? */ |
---|
733 | for (ptr = res; ptr != NULL; ptr = ptr->ai_next) { |
---|
734 | if (cli->sa->sa_family != ptr->ai_family) |
---|
735 | continue; |
---|
736 | if (memcmp(cli->sa, ptr->ai_addr, sSAlen(cli->sa))) |
---|
737 | continue; |
---|
738 | |
---|
739 | found = 1; |
---|
740 | break; |
---|
741 | } |
---|
742 | freeaddrinfo(res); |
---|
743 | |
---|
744 | if (!found) { |
---|
745 | if (cli->type == RGW_CLI_NAS) { |
---|
746 | TRACE_DEBUG(INFO, "The NAS-Identifier value '%.*s' resolves to a different IP than the client's, discarding the message. \nConfigure this client as a Proxy if this message should be valid.", |
---|
747 | nas_id->length - sizeof(struct radius_attr_hdr), nas_id + 1); |
---|
748 | return EINVAL; |
---|
749 | } else { |
---|
750 | /* This identifier matches a different IP, assume it is a proxied message */ |
---|
751 | if (!cli->is_local) { |
---|
752 | rr_str = cli->fqdn; |
---|
753 | } |
---|
754 | oh_str = &buf[0]; /* The canonname resolved */ |
---|
755 | or_str = strchr(oh_str, '.'); |
---|
756 | if (or_str) { |
---|
757 | or_str ++; /* move after the first dot */ |
---|
758 | if (*or_str == '\0') |
---|
759 | or_str = NULL; /* Discard this realm, we will use the local realm later */ |
---|
760 | } |
---|
761 | } |
---|
762 | } else { |
---|
763 | /* It is a valid alias, save it */ |
---|
764 | CHECK_MALLOC( cli->aliases = realloc(cli->aliases, (cli->aliases_nb + 1) * sizeof(char *)) ); |
---|
765 | CHECK_MALLOC( cli->aliases[cli->aliases_nb + 1] = malloc( 1 + nas_id->length - sizeof(struct radius_attr_hdr) )); |
---|
766 | memcpy( cli->aliases[cli->aliases_nb + 1], nas_id + 1, nas_id->length - sizeof(struct radius_attr_hdr)); |
---|
767 | *(cli->aliases[cli->aliases_nb + 1] + nas_id->length - sizeof(struct radius_attr_hdr)) = '\0'; |
---|
768 | cli->aliases_nb ++; |
---|
769 | TRACE_DEBUG(FULL, "Saved valid alias for client: '%s' -> '%s'", cli->aliases[cli->aliases_nb + 1], cli->fqdn); |
---|
770 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
771 | } |
---|
772 | } else { |
---|
773 | /* Error resolving the name */ |
---|
774 | TRACE_DEBUG(INFO, "NAS-Identifier '%s' cannot be resolved: %s. Ignoring...", buf, gai_strerror(ret)); |
---|
775 | /* Assume this is a valid identifier for the client */ |
---|
776 | CHECK_FCT( rgw_clients_get_origin(cli, &oh_str, &or_str) ); |
---|
777 | } |
---|
778 | } |
---|
779 | |
---|
780 | /* Now, let's create the empty Diameter message with Origin-Host, -Realm, and Route-Record if needed. */ |
---|
781 | diameter: |
---|
782 | ASSERT(oh_str); /* If it is not defined here, there is a bug... */ |
---|
783 | if (!or_str) |
---|
784 | or_str = fd_g_config->cnf_diamrlm; /* Use local realm in that case */ |
---|
785 | |
---|
786 | /* Create an empty Diameter message so that extensions can store their AVPs */ |
---|
787 | CHECK_FCT( fd_msg_new ( NULL, MSGFL_ALLOC_ETEID, diam ) ); |
---|
788 | |
---|
789 | /* Add the Origin-Host as next AVP */ |
---|
790 | CHECK_FCT( fd_msg_avp_new ( cache_orig_host, 0, &avp ) ); |
---|
791 | memset(&avp_val, 0, sizeof(avp_val)); |
---|
792 | avp_val.os.data = (unsigned char *)oh_str; |
---|
793 | avp_val.os.len = strlen(oh_str); |
---|
794 | CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); |
---|
795 | CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); |
---|
796 | |
---|
797 | /* Add the Origin-Realm as next AVP */ |
---|
798 | CHECK_FCT( fd_msg_avp_new ( cache_orig_realm, 0, &avp ) ); |
---|
799 | memset(&avp_val, 0, sizeof(avp_val)); |
---|
800 | avp_val.os.data = (unsigned char *)or_str; |
---|
801 | avp_val.os.len = strlen(or_str); |
---|
802 | CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); |
---|
803 | CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); |
---|
804 | |
---|
805 | if (rr_str) { |
---|
806 | CHECK_FCT( fd_msg_avp_new ( cache_route_record, 0, &avp ) ); |
---|
807 | memset(&avp_val, 0, sizeof(avp_val)); |
---|
808 | avp_val.os.data = (unsigned char *)rr_str; |
---|
809 | avp_val.os.len = strlen(rr_str); |
---|
810 | CHECK_FCT( fd_msg_avp_setvalue ( avp, &avp_val ) ); |
---|
811 | CHECK_FCT( fd_msg_avp_add ( *diam, MSG_BRW_LAST_CHILD, avp) ); |
---|
812 | } |
---|
813 | |
---|
814 | /* Done! */ |
---|
815 | return 0; |
---|
816 | } |
---|
817 | |
---|
818 | int rgw_clients_get_origin(struct rgw_client *cli, char **fqdn, char **realm) |
---|
819 | { |
---|
820 | TRACE_ENTRY("%p %p %p", cli, fqdn, realm); |
---|
821 | CHECK_PARAMS(cli && fqdn); |
---|
822 | |
---|
823 | if (cli->is_local) { |
---|
824 | *fqdn = fd_g_config->cnf_diamid; |
---|
825 | if (realm) |
---|
826 | *realm= fd_g_config->cnf_diamrlm; |
---|
827 | } else { |
---|
828 | *fqdn = cli->fqdn; |
---|
829 | if (realm) |
---|
830 | *realm= cli->realm; |
---|
831 | } |
---|
832 | |
---|
833 | return 0; |
---|
834 | } |
---|
835 | |
---|
836 | char * rgw_clients_id(struct rgw_client *cli) |
---|
837 | { |
---|
838 | return cli->is_local ? "(local)" : cli->fqdn; |
---|
839 | } |
---|
840 | |
---|
841 | |
---|
842 | void rgw_clients_dispose(struct rgw_client ** ref) |
---|
843 | { |
---|
844 | TRACE_ENTRY("%p", ref); |
---|
845 | CHECK_PARAMS_DO(ref, return); |
---|
846 | |
---|
847 | CHECK_POSIX_DO( pthread_rwlock_wrlock(&cli_rwl), ); |
---|
848 | client_unlink(*ref); |
---|
849 | *ref = NULL; |
---|
850 | CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), ); |
---|
851 | } |
---|
852 | |
---|
853 | int rgw_clients_add( struct sockaddr * ip_port, unsigned char ** key, size_t keylen, enum rgw_cli_type type ) |
---|
854 | { |
---|
855 | struct rgw_client * prev = NULL, *new = NULL; |
---|
856 | int ret; |
---|
857 | |
---|
858 | TRACE_ENTRY("%p %p %lu", ip_port, key, keylen); |
---|
859 | |
---|
860 | CHECK_PARAMS( ip_port && key && *key && keylen ); |
---|
861 | CHECK_PARAMS( (ip_port->sa_family == AF_INET) || (ip_port->sa_family == AF_INET6) ); |
---|
862 | CHECK_PARAMS( (type == RGW_CLI_NAS) || (type == RGW_CLI_PXY) ); |
---|
863 | |
---|
864 | /* Dump the entry in debug mode */ |
---|
865 | if (TRACE_BOOL(FULL + 1 )) { |
---|
866 | TRACE_DEBUG(FULL, "Adding %s:", (type == RGW_CLI_NAS) ? "NAS" : "PROXY" ); |
---|
867 | TRACE_DEBUG_sSA(FULL, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); |
---|
868 | TRACE_DEBUG_BUFFER(FULL, "\tKey: [", *key, keylen, "]" ); |
---|
869 | } |
---|
870 | |
---|
871 | /* Lock the lists */ |
---|
872 | CHECK_POSIX( pthread_rwlock_wrlock(&cli_rwl) ); |
---|
873 | |
---|
874 | /* Check if the same entry does not already exist */ |
---|
875 | ret = client_search(&prev, ip_port ); |
---|
876 | if (ret == ENOENT) { |
---|
877 | /* No duplicate found, Ok to add */ |
---|
878 | CHECK_FCT_DO( ret = client_create( &new, &ip_port, key, keylen, type ), goto end ); |
---|
879 | fd_list_insert_after(&prev->chain, &new->chain); |
---|
880 | new->refcount++; |
---|
881 | ret = 0; |
---|
882 | goto end; |
---|
883 | } |
---|
884 | |
---|
885 | if (ret == EEXIST) { |
---|
886 | /* Check if the key is the same, then skip or return an error */ |
---|
887 | if ((keylen == prev->key.len ) && ( ! memcmp(*key, prev->key.data, keylen) ) && (type == prev->type)) { |
---|
888 | TRACE_DEBUG(INFO, "Skipping duplicate client description"); |
---|
889 | ret = 0; |
---|
890 | goto end; |
---|
891 | } |
---|
892 | |
---|
893 | fd_log_debug("ERROR: Conflicting RADIUS clients descriptions!\n"); |
---|
894 | TRACE_DEBUG(NONE, "Previous entry: %s", (prev->type == RGW_CLI_NAS) ? "NAS" : "PROXY"); |
---|
895 | TRACE_DEBUG_sSA(NONE, "\tIP : ", prev->sa, NI_NUMERICHOST | NI_NUMERICSERV, "" ); |
---|
896 | TRACE_DEBUG_BUFFER(NONE, "\tKey: [", prev->key.data, prev->key.len, "]" ); |
---|
897 | TRACE_DEBUG(NONE, "Conflicting entry: %s", (type == RGW_CLI_NAS) ? "NAS" : "PROXY"); |
---|
898 | TRACE_DEBUG_sSA(NONE, "\tIP : ", ip_port, NI_NUMERICHOST | NI_NUMERICSERV, "" ); |
---|
899 | TRACE_DEBUG_BUFFER(NONE, "\tKey: [", *key, keylen, "]" ); |
---|
900 | } |
---|
901 | end: |
---|
902 | /* release the lists */ |
---|
903 | CHECK_POSIX( pthread_rwlock_unlock(&cli_rwl) ); |
---|
904 | |
---|
905 | return ret; |
---|
906 | } |
---|
907 | |
---|
908 | static void dump_cli_list(struct fd_list *senti) |
---|
909 | { |
---|
910 | struct rgw_client * client = NULL; |
---|
911 | struct fd_list *ref = NULL; |
---|
912 | |
---|
913 | for (ref = senti->next; ref != senti; ref = ref->next) { |
---|
914 | client = (struct rgw_client *)ref; |
---|
915 | TRACE_DEBUG_sSA(NONE, " - ", client->sa, NI_NUMERICHOST | NI_NUMERICSERV, (client->type == RGW_CLI_NAS) ? "" : " [PROXY]" ); |
---|
916 | } |
---|
917 | } |
---|
918 | |
---|
919 | void rgw_clients_dump(void) |
---|
920 | { |
---|
921 | if ( ! TRACE_BOOL(FULL) ) |
---|
922 | return; |
---|
923 | |
---|
924 | CHECK_POSIX_DO( pthread_rwlock_rdlock(&cli_rwl), /* ignore error */ ); |
---|
925 | |
---|
926 | if (!FD_IS_LIST_EMPTY(&cli_ip)) |
---|
927 | fd_log_debug(" RADIUS IP clients list:\n"); |
---|
928 | dump_cli_list(&cli_ip); |
---|
929 | |
---|
930 | if (!FD_IS_LIST_EMPTY(&cli_ip6)) |
---|
931 | fd_log_debug(" RADIUS IPv6 clients list:\n"); |
---|
932 | dump_cli_list(&cli_ip6); |
---|
933 | |
---|
934 | CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), /* ignore error */ ); |
---|
935 | } |
---|
936 | |
---|
937 | void rgw_clients_fini(void) |
---|
938 | { |
---|
939 | struct fd_list * client; |
---|
940 | |
---|
941 | TRACE_ENTRY(); |
---|
942 | |
---|
943 | CHECK_POSIX_DO( pthread_rwlock_wrlock(&cli_rwl), /* ignore error */ ); |
---|
944 | |
---|
945 | CHECK_FCT_DO( fd_thr_term(&dbt_expire), /* continue */ ); |
---|
946 | |
---|
947 | /* empty the lists */ |
---|
948 | while ( ! FD_IS_LIST_EMPTY(&cli_ip) ) { |
---|
949 | client = cli_ip.next; |
---|
950 | fd_list_unlink(client); |
---|
951 | client_unlink((struct rgw_client *)client); |
---|
952 | } |
---|
953 | while (! FD_IS_LIST_EMPTY(&cli_ip6)) { |
---|
954 | client = cli_ip6.next; |
---|
955 | fd_list_unlink(client); |
---|
956 | client_unlink((struct rgw_client *)client); |
---|
957 | } |
---|
958 | |
---|
959 | CHECK_POSIX_DO( pthread_rwlock_unlock(&cli_rwl), /* ignore error */ ); |
---|
960 | |
---|
961 | } |
---|
962 | |
---|
963 | int rgw_client_finish_send(struct radius_msg ** msg, struct rgw_radius_msg_meta * req, struct rgw_client * cli) |
---|
964 | { |
---|
965 | int p; |
---|
966 | struct fd_list * li; |
---|
967 | |
---|
968 | TRACE_ENTRY("%p %p %p", msg, req, cli); |
---|
969 | CHECK_PARAMS( msg && *msg && cli ); |
---|
970 | |
---|
971 | if (!req) { |
---|
972 | /* We don't support this case yet */ |
---|
973 | ASSERT(0); |
---|
974 | return ENOTSUP; |
---|
975 | } |
---|
976 | |
---|
977 | /* Add all the Proxy-States back in the message */ |
---|
978 | for (p = 0; p < req->ps_nb; p++) { |
---|
979 | struct radius_attr_hdr * attr = (struct radius_attr_hdr *)(req->radius.buf + req->radius.attr_pos[req->ps_first + p]); |
---|
980 | |
---|
981 | if (radius_msg_add_attr_to_array(*msg, attr)) { |
---|
982 | TRACE_DEBUG(INFO, "Error in radius_msg_add_attr_to_array, ENOMEM"); |
---|
983 | radius_msg_free(*msg); |
---|
984 | free(*msg); |
---|
985 | *msg = NULL; |
---|
986 | return ENOMEM; |
---|
987 | } |
---|
988 | } |
---|
989 | |
---|
990 | /* Add the Message-Authenticator if needed, and other final tasks */ |
---|
991 | if (radius_msg_finish_srv(*msg, cli->key.data, cli->key.len, req->radius.hdr->authenticator)) { |
---|
992 | TRACE_DEBUG(INFO, "An error occurred while preparing the RADIUS answer"); |
---|
993 | radius_msg_free(*msg); |
---|
994 | free(*msg); |
---|
995 | *msg = NULL; |
---|
996 | return EINVAL; |
---|
997 | } |
---|
998 | |
---|
999 | /* Debug */ |
---|
1000 | TRACE_DEBUG(FULL, "RADIUS message ready for sending:"); |
---|
1001 | rgw_msg_dump((struct rgw_radius_msg_meta *)*msg, 0); |
---|
1002 | |
---|
1003 | /* Send the message */ |
---|
1004 | CHECK_FCT( rgw_servers_send(req->serv_type, (*msg)->buf, (*msg)->buf_used, cli->sa, req->port) ); |
---|
1005 | |
---|
1006 | /* update the duplicate cache */ |
---|
1007 | if (req->serv_type == RGW_PLG_TYPE_AUTH) |
---|
1008 | p = 0; |
---|
1009 | else |
---|
1010 | p = 1; |
---|
1011 | |
---|
1012 | CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) ); |
---|
1013 | |
---|
1014 | /* Search this message in our list */ |
---|
1015 | for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) { |
---|
1016 | int cmp = 0; |
---|
1017 | struct req_info * r = (struct req_info *)(li->o); |
---|
1018 | if (r->id < req->radius.hdr->identifier) |
---|
1019 | continue; |
---|
1020 | if (r->id > req->radius.hdr->identifier) |
---|
1021 | break; |
---|
1022 | if (r->port < req->port) |
---|
1023 | continue; |
---|
1024 | if (r->port > req->port) |
---|
1025 | break; |
---|
1026 | cmp = memcmp(&r->auth[0], &req->radius.hdr->authenticator[0], 16); |
---|
1027 | if (cmp < 0) |
---|
1028 | continue; |
---|
1029 | if (cmp > 0) |
---|
1030 | break; |
---|
1031 | |
---|
1032 | /* We have the request in our duplicate cache */ |
---|
1033 | /* This should not happen, but just in case... */ |
---|
1034 | if (r->ans) { |
---|
1035 | radius_msg_free(r->ans); |
---|
1036 | free(r->ans); |
---|
1037 | } |
---|
1038 | |
---|
1039 | /* Now save the message */ |
---|
1040 | r->ans = *msg; |
---|
1041 | *msg = NULL; |
---|
1042 | |
---|
1043 | /* Update the timestamp */ |
---|
1044 | { |
---|
1045 | time_t now = time(NULL); |
---|
1046 | r->received = now; |
---|
1047 | fd_list_unlink(&r->by_time); /* Move as last entry, since it is the most recent */ |
---|
1048 | fd_list_insert_before(&cli->dupl_info[p].dupl_by_time, &r->by_time); |
---|
1049 | } |
---|
1050 | break; |
---|
1051 | } |
---|
1052 | |
---|
1053 | CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) ); |
---|
1054 | |
---|
1055 | /* If we have not found the request in our list, the purge time is probably too small */ |
---|
1056 | if (*msg) { |
---|
1057 | TODO("Augment the purge time..."); |
---|
1058 | /* If we receive the duplicate request again, it will be converted to Diameter... */ |
---|
1059 | radius_msg_free(*msg); |
---|
1060 | free(*msg); |
---|
1061 | *msg = NULL; |
---|
1062 | } |
---|
1063 | |
---|
1064 | /* Finished */ |
---|
1065 | return 0; |
---|
1066 | } |
---|
1067 | |
---|
1068 | /* Call this function when a RADIUS request has explicitely no answer (mainly accounting) so |
---|
1069 | that we purge the duplicate cache and allow further message to be translated again. |
---|
1070 | This is useful for example when a temporary error occurred in Diameter (like UNABLE_TO_DELIVER) */ |
---|
1071 | int rgw_client_finish_nosend(struct rgw_radius_msg_meta * req, struct rgw_client * cli) |
---|
1072 | { |
---|
1073 | int p; |
---|
1074 | struct fd_list * li; |
---|
1075 | |
---|
1076 | TRACE_ENTRY("%p %p", req, cli); |
---|
1077 | CHECK_PARAMS( req && cli ); |
---|
1078 | |
---|
1079 | /* update the duplicate cache */ |
---|
1080 | if (req->serv_type == RGW_PLG_TYPE_AUTH) |
---|
1081 | p = 0; |
---|
1082 | else |
---|
1083 | p = 1; |
---|
1084 | |
---|
1085 | CHECK_POSIX( pthread_mutex_lock( &cli->dupl_info[p].dupl_lock ) ); |
---|
1086 | |
---|
1087 | /* Search this message in our list */ |
---|
1088 | for (li = cli->dupl_info[p].dupl_by_id.next; li != &cli->dupl_info[p].dupl_by_id; li = li->next) { |
---|
1089 | int cmp = 0; |
---|
1090 | struct req_info * r = (struct req_info *)(li->o); |
---|
1091 | if (r->id < req->radius.hdr->identifier) |
---|
1092 | continue; |
---|
1093 | if (r->id > req->radius.hdr->identifier) |
---|
1094 | break; |
---|
1095 | if (r->port < req->port) |
---|
1096 | continue; |
---|
1097 | if (r->port > req->port) |
---|
1098 | break; |
---|
1099 | cmp = memcmp(&r->auth[0], &req->radius.hdr->authenticator[0], 16); |
---|
1100 | if (cmp < 0) |
---|
1101 | continue; |
---|
1102 | if (cmp > 0) |
---|
1103 | break; |
---|
1104 | |
---|
1105 | /* We have the request in our duplicate cache, remove it */ |
---|
1106 | fd_list_unlink(&r->by_id); |
---|
1107 | fd_list_unlink(&r->by_time); |
---|
1108 | dupl_free_req_info(r); |
---|
1109 | break; |
---|
1110 | } |
---|
1111 | |
---|
1112 | CHECK_POSIX( pthread_mutex_unlock( &cli->dupl_info[p].dupl_lock ) ); |
---|
1113 | |
---|
1114 | /* Finished */ |
---|
1115 | return 0; |
---|
1116 | } |
---|
1117 | |
---|