blob: 3bd45260d23be3b474a4cae7466481f3c68b6068 [file] [log] [blame]
Brian Waters13d96012017-12-08 16:53:31 -06001/*********************************************************************************************************
2* Software License Agreement (BSD License) *
3* Author: Sebastien Decugis <sdecugis@freediameter.net> *
4* *
5* Copyright (c) 2013, 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 plugins that provide handlers for RADIUS messages and attributes */
37
38#include "rgw.h"
39#include <dlfcn.h>
40#include <libgen.h>
41
42/* List of plugins, in the order they appear in the configuration file. */
43static struct fd_list plg_list = FD_LIST_INITIALIZER(plg_list);
44
45/* A plugin entry */
46struct plg_descr {
47 struct fd_list chain; /* chaining in plg_list */
48
49 void *dlo; /* pointer returned by dlopen for the extension, to use with dlclose later */
50 struct rgw_api *descriptor; /* Points to the resolved plugin's rgwp_descriptor */
51 struct rgwp_config *cs; /* the (private) state returned by rgwp_conf_parse */
52
53 int type; /* this extension is called for messages received on this(these) server port(s) only */
54 unsigned char *cc; /* array of command codes, or NULL for all cc */
55 size_t cc_len; /* size of the previous array */
56};
57
58/* Accelerators for each command code (one for each port). These accelerators are built on-demand, as a cache, after start_cache function has been called. */
59static struct fd_list plg_accel_auth = FD_LIST_INITIALIZER(plg_accel_auth);
60static struct fd_list plg_accel_acct = FD_LIST_INITIALIZER(plg_accel_acct);
61
62/* Accelerator list item, one per command code value (only the ones actually used) */
63struct plg_accel {
64 struct fd_list chain; /* link in the plg_accel_* list. List ordered by ccode. */
65 unsigned char ccode; /* the command code for this accelerator. We don't handle extended CC yet */
66 struct fd_list plugins;/* head for the list of plg_accel_item, corresponding to the extensions to be called for this command code. */
67};
68
69/* Accelerator item */
70struct plg_accel_item {
71 struct fd_list chain; /* link in the plg_accel "plugins" list */
72 struct plg_descr * plg; /* pointer to the plugin */
73 /* Note: we can further optimize by caching the location of plg->descriptor->rgwp_rad_req etc... at this level. */
74};
75
76/* RWlock to protect all the previous lists */
77static pthread_rwlock_t plg_lock = PTHREAD_RWLOCK_INITIALIZER;
78
79/* Has start_cache been called? */
80static int cache_started = 0;
81
82
83/* The read lock must be held before calling this function */
84static int get_accelerator(struct fd_list ** list, unsigned char ccode, int type)
85{
86 struct fd_list *refer, *search;
87 struct plg_accel * accel = NULL;
88 struct plg_accel_item * item = NULL;
89 int upgraded = 0;
90
91 TRACE_ENTRY("%p %hhu %i", list, ccode, type);
92
93 CHECK_PARAMS( cache_started && list && ((type == RGW_PLG_TYPE_AUTH) || (type == RGW_PLG_TYPE_ACCT)) );
94
95 if (type == RGW_PLG_TYPE_AUTH)
96 refer = &plg_accel_auth;
97 else
98 refer = &plg_accel_acct;
99restart:
100 /* Check if we have already an accelerator for this ccode */
101 for (search = refer->next; search != refer; search = search->next) {
102 struct plg_accel * loc = (struct plg_accel *)search;
103
104 if (loc->ccode < ccode)
105 continue;
106
107 if (loc->ccode > ccode)
108 break; /* we don't have an accelerator for this value yet */
109
110 /* We found the matching accelerator, just return this list */
111 *list = &loc->plugins;
112 return 0;
113 }
114
115 /* We must create the accelerator list, then save it just before "search" */
116
117 /* First , upgrade the lock to write lock, and restart the search. This is the only robust solution to avoid deadlocks */
118 if (! upgraded) {
119 CHECK_POSIX( pthread_rwlock_unlock(&plg_lock) );
120 CHECK_POSIX( pthread_rwlock_wrlock(&plg_lock) );
121 upgraded = 1;
122 goto restart;
123 }
124
125 /* Now create the new element */
126 CHECK_MALLOC( accel = malloc(sizeof(struct plg_accel)) );
127 memset(accel, 0, sizeof(struct plg_accel) );
128 fd_list_init(&accel->chain, NULL);
129 fd_list_init(&accel->plugins, accel);
130 accel->ccode = ccode;
131
132 /* Check each extension from the global list for this port and ccode */
133 for (refer = plg_list.next; refer != &plg_list; refer = refer->next) {
134 struct plg_descr * loc = (struct plg_descr *)refer;
135
136 /* Skip if this extension is not registered for this port */
137 if (! (loc->type & type) )
138 continue;
139
140 /* Check if the ccode is there */
141 if (loc->cc) {
142 int i;
143 int match = 0;
144 for (i=0; i< loc->cc_len; i++) {
145 if (loc->cc[i] < ccode)
146 continue;
147 if (loc->cc[i] == ccode)
148 match = 1;
149 break;
150 }
151 if (!match)
152 continue;
153 }
154
155 /* Ok, this extension must be called for this port / ccode, add to the accelerator */
156 CHECK_MALLOC( item = malloc(sizeof(struct plg_accel_item)) );
157 memset(item, 0, sizeof(struct plg_accel_item));
158 fd_list_init(&item->chain, NULL);
159 item->plg = loc;
160 /* Add as last element of the accelerator */
161 fd_list_insert_before(&accel->plugins, &item->chain);
162 }
163
164 /* Now, save this accelerator entry in the global list */
165 fd_list_insert_before(search, &accel->chain);
166 *list = &accel->plugins;
167
168 return 0;
169}
170
171
172int rgw_plg_add( char * plgfile, char * conffile, int type, unsigned char ** codes_array, size_t codes_sz )
173{
174 struct plg_descr * new;
175
176 TRACE_ENTRY("%p %p %d %p %zi", plgfile, conffile, type, codes_array, codes_sz);
177
178 CHECK_PARAMS( plgfile && type && codes_array && (cache_started == 0) );
179
180 CHECK_MALLOC( new = malloc(sizeof(struct plg_descr)) );
181 memset(new, 0, sizeof(struct plg_descr));
182
183 fd_list_init(&new->chain, new);
184
185 /* Try and load the plugin */
186 TRACE_DEBUG(FULL, "Loading plugin: %s", plgfile);
187 new->dlo = dlopen(plgfile, RTLD_NOW | RTLD_GLOBAL);
188 if (new->dlo == NULL) {
189 /* An error occured */
190 fd_log_debug("Loading of plugin '%s' failed: %s", plgfile, dlerror());
191 goto error;
192 }
193
194 /* Resolve the descriptor */
195 new->descriptor = dlsym( new->dlo, "rgwp_descriptor" );
196 if (new->descriptor == NULL) {
197 /* An error occured */
198 fd_log_debug("Unable to resolve 'rgwp_descriptor' in plugin '%s': %s", plgfile, dlerror());
199 goto error;
200 }
201
202 TRACE_DEBUG(FULL, "Plugin '%s' found in file '%s'", new->descriptor->rgwp_name, plgfile);
203
204 /* Now parse the configuration file, this will initialize all plugin states and store it in the "cs" pointer (the plugin must be re-entrant, so no global state) */
205 if (new->descriptor->rgwp_conf_parse) {
206 CHECK_FCT_DO( (*(new->descriptor->rgwp_conf_parse))(conffile, &new->cs),
207 {
208 fd_log_debug("An error occurred while parsing configuration file '%s' in plugin '%s', aborting...", conffile, plgfile);
209 goto error;
210 } );
211 }
212
213 /* Now sort the array (very simple algorithm, but this list is usually small) of command codes and save */
214 if (*codes_array && codes_sz) {
215 int i;
216
217 new->cc = *codes_array;
218 *codes_array = NULL;
219
220 for (i = 0; i < codes_sz - 1; i++) {
221 int j, idx = i, min = new->cc[i];
222
223 /* find the smallest remaining element */
224 for (j = i + 1; j < codes_sz; j++) {
225 if (min > new->cc[j]) {
226 min = new->cc[j];
227 idx = j;
228 }
229 }
230
231 /* swap if needed */
232 if (idx != i) {
233 int tmp = new->cc[i];
234 new->cc[i] = new->cc[idx];
235 new->cc[idx] = tmp;
236 }
237 }
238 new->cc_len = codes_sz;
239 }
240
241 new->type = type;
242
243 /* And save this new extension in the list. We don't need to lock at this point because we are single threaded. */
244 fd_list_insert_before(&plg_list, &new->chain);
245
246 return 0;
247
248
249error:
250 if (new && new->dlo)
251 dlclose(new->dlo);
252 if (new)
253 free(new);
254 return EINVAL;
255}
256
257void rgw_plg_dump(void)
258{
259 struct plg_descr * plg;
260 struct fd_list * ptr, *ptraccel;
261
262 if ( ! TRACE_BOOL(FULL) )
263 return;
264
265 CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
266
267 if ( ! FD_IS_LIST_EMPTY( &plg_list ) )
268 fd_log_debug("[app_radgw] --- List of registered plugins:");
269 for (ptr = plg_list.next; ptr != &plg_list; ptr = ptr->next) {
270 char buf[1024];
271 plg = (struct plg_descr *)ptr;
272
273 snprintf(buf, sizeof(buf), " %-25s ( %p ) - types: %s%s, codes: ",
274 plg->descriptor->rgwp_name,
275 plg->cs,
276 plg->type & RGW_PLG_TYPE_AUTH ? "Au" : " ",
277 plg->type & RGW_PLG_TYPE_ACCT ? "Ac" : " ");
278
279 if (plg->cc) {
280 int i;
281
282 for (i = 0; i < plg->cc_len; i++) {
283 snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%02hhx ", plg->cc[i]);
284 }
285 fd_log_debug("%s", buf);
286 } else {
287 fd_log_debug("%s*", buf);
288 }
289 }
290
291 CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
292
293 /* Dump the list of accelerators */
294 if ( ! TRACE_BOOL(FULL + 1) )
295 return;
296
297 CHECK_POSIX_DO( pthread_rwlock_rdlock(&plg_lock), );
298 if ( !FD_IS_LIST_EMPTY( &plg_accel_auth ) || !FD_IS_LIST_EMPTY( &plg_accel_acct ))
299 fd_log_debug(" --- Accelerators:");
300
301 for (ptraccel = plg_accel_auth.next; ptraccel != &plg_accel_auth; ptraccel = ptraccel->next) {
302 struct plg_accel * accel = (struct plg_accel *)ptraccel;
303 fd_log_debug(" auth, code %02hhu:", accel->ccode);
304
305 for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
306 struct plg_accel_item * item = (struct plg_accel_item *)ptr;
307 fd_log_debug(" %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
308 }
309 }
310 for (ptraccel = plg_accel_acct.next; ptraccel != &plg_accel_acct; ptraccel = ptraccel->next) {
311 struct plg_accel * accel = (struct plg_accel *)ptraccel;
312 fd_log_debug(" acct, code %02hhu:", accel->ccode);
313
314 for (ptr = accel->plugins.next; ptr != &accel->plugins; ptr = ptr->next) {
315 struct plg_accel_item * item = (struct plg_accel_item *)ptr;
316 fd_log_debug(" %-15s (%p)", item->plg->descriptor->rgwp_name, item->plg->cs);
317 }
318 }
319
320
321 CHECK_POSIX_DO( pthread_rwlock_unlock(&plg_lock), );
322
323}
324
325void rgw_plg_start_cache(void)
326{
327 cache_started++;
328}
329
330int rgw_plg_loop_req(struct rgw_radius_msg_meta **rad, struct msg **diam_msg, struct rgw_client * cli)
331{
332 int ret = 0;
333 struct fd_list * head = NULL, *li;
334 struct radius_msg * rad_ans = NULL;
335
336 TRACE_ENTRY("%p %p %p", rad, diam_msg, cli);
337 CHECK_PARAMS( rad && *rad && diam_msg && *diam_msg && cli);
338
339 /* First, get the list of extensions for this message */
340 CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
341 CHECK_FCT_DO( ret = get_accelerator(&head, (*rad)->radius.hdr->code, (*rad)->serv_type),
342 { CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
343
344 /* Loop in the list of extensions */
345 for (li = head->next; li != head; li = li->next) {
346 struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
347
348 if (plg->descriptor->rgwp_rad_req) {
349 TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
350 ret = (*plg->descriptor->rgwp_rad_req)(plg->cs, &(*rad)->radius, &rad_ans, diam_msg, cli);
351 if (ret)
352 break;
353 } else {
354 TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
355 }
356 }
357
358 CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
359
360 /* If no error encountered, we're done here */
361 if (ret == 0)
362 return 0;
363
364 /* Destroy the Diameter temp message, if any */
365 if (*diam_msg) {
366 CHECK_FCT_DO( fd_msg_free(*diam_msg), );
367 *diam_msg = NULL;
368 }
369
370 /* Send the radius message back if required */
371 if ((ret == -2) && rad_ans && rad) {
372 CHECK_FCT_DO( rgw_client_finish_send(&rad_ans, *rad, cli), /* It failed, it can't be helped... */);
373 }
374
375 if (ret > 0) {
376 /* Critical error, log and exit */
377 TRACE_DEBUG(NONE, "An error occurred while handling a RADIUS message from '%s': %s", rgw_clients_id(cli), strerror(ret));
378 return ret;
379 }
380
381 /* Now, discard the message and return */
382 rgw_msg_free(rad);
383 return 0;
384}
385
386/* Loop in the extension list (same as req) to convert data from diam_ans to rad_ans */
387int rgw_plg_loop_ans(struct rgw_radius_msg_meta *req, struct msg **diam_ans, struct radius_msg ** rad_ans, struct rgw_client * cli)
388{
389 int ret = 0;
390 struct fd_list * head = NULL, *li;
391
392 TRACE_ENTRY("%p %p %p %p", req, diam_ans, rad_ans, cli);
393 CHECK_PARAMS( req && diam_ans && *diam_ans && rad_ans && *rad_ans && cli);
394
395 /* Get the list of extensions of the RADIUS request */
396 CHECK_POSIX( pthread_rwlock_rdlock( &plg_lock) );
397 CHECK_FCT_DO( ret = get_accelerator(&head, req->radius.hdr->code, req->serv_type),
398 { CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) ); return ret; } );
399
400 /* Loop in the list of extensions */
401 for (li = head->next; li != head; li = li->next) {
402 struct plg_descr * plg = ((struct plg_accel_item *) li)->plg;
403
404 if (plg->descriptor->rgwp_diam_ans) {
405 TRACE_DEBUG(ANNOYING, "Calling next plugin: %s", plg->descriptor->rgwp_name);
406 ret = (*plg->descriptor->rgwp_diam_ans)(plg->cs, diam_ans, rad_ans, (void *)cli);
407 if (ret)
408 break;
409 } else {
410 TRACE_DEBUG(ANNOYING, "Skipping extension '%s' (NULL callback)", plg->descriptor->rgwp_name);
411 }
412 }
413
414 CHECK_POSIX( pthread_rwlock_unlock( &plg_lock) );
415
416 /* If no error encountered, we're done here */
417 if (ret == 0)
418 return 0;
419
420 /* Destroy the temporary RADIUS answer */
421 if (*rad_ans) {
422 radius_msg_free(*rad_ans);
423 free(*rad_ans);
424 *rad_ans = NULL;
425 }
426
427 if (ret > 0) {
428 /* Critical error, log and exit */
429 fd_log_debug("[app_radgw] An error occurred while handling a DIAMETER answer to a converted RADIUS request, turn on DEBUG for details: %s", strerror(ret));
430 return ret;
431 }
432
433 /* We might define other return values with special meaning here (ret == -1, ...) for example create a new Diameter request */
434
435 /* -1: just abord the translation with no more processing. */
436
437 return 0;
438}
439
440void rgw_plg_fini(void)
441{
442 struct fd_list * item, *subitem;
443
444 TRACE_ENTRY();
445
446 CHECK_POSIX_DO( pthread_rwlock_rdlock( &plg_lock), /* continue anyway */ );
447
448 /* Remove all elements from all accelerators */
449 while ( ! FD_IS_LIST_EMPTY(&plg_accel_auth) ) {
450 item = plg_accel_auth.next;
451 fd_list_unlink(item);
452 {
453 struct plg_accel * accel = (struct plg_accel *)item;
454 while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
455 subitem = accel->plugins.next;
456 fd_list_unlink(subitem);
457 free(subitem);
458 }
459 }
460 free(item);
461 }
462 while ( ! FD_IS_LIST_EMPTY(&plg_accel_acct) ) {
463 item = plg_accel_acct.next;
464 fd_list_unlink(item);
465 {
466 struct plg_accel * accel = (struct plg_accel *)item;
467 while ( ! FD_IS_LIST_EMPTY(&accel->plugins) ) {
468 subitem = accel->plugins.next;
469 fd_list_unlink(subitem);
470 free(subitem);
471 }
472 }
473 free(item);
474 }
475
476 /* Now destroy all plugins information */
477 while ( ! FD_IS_LIST_EMPTY(&plg_list) ) {
478 struct plg_descr * plg = (struct plg_descr *) plg_list.next;
479 fd_list_unlink(&plg->chain);
480 free(plg->cc);
481 if (plg->descriptor && plg->descriptor->rgwp_conf_free ) {
482 TRACE_DEBUG(INFO, "RADIUS/Diameter gateway plugin '%s' cleaning up...", plg->descriptor->rgwp_name);
483 (*plg->descriptor->rgwp_conf_free)(plg->cs);
484 }
485 if (plg->dlo)
486 dlclose(plg->dlo);
487 free(plg);
488 }
489
490 CHECK_POSIX_DO( pthread_rwlock_unlock( &plg_lock), );
491}