1 /*********************************************************************************************************
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. */
43 static struct fd_list plg_list = FD_LIST_INITIALIZER(plg_list);
44 
45 /* A plugin entry */
46 struct 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.  */
59 static struct fd_list plg_accel_auth = FD_LIST_INITIALIZER(plg_accel_auth);
60 static 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) */
63 struct 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 */
70 struct 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 */
77 static pthread_rwlock_t plg_lock = PTHREAD_RWLOCK_INITIALIZER;
78 
79 /* Has start_cache been called? */
80 static int cache_started = 0;
81 
82 
83 /* The read lock must be held before calling this function */
get_accelerator(struct fd_list ** list,unsigned char ccode,int type)84 static 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;
99 restart:
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 
rgw_plg_add(char * plgfile,char * conffile,int type,unsigned char ** codes_array,size_t codes_sz)172 int 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 
249 error:
250 	if (new && new->dlo)
251 		dlclose(new->dlo);
252 	if (new)
253 		free(new);
254 	return EINVAL;
255 }
256 
rgw_plg_dump(void)257 void 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 
rgw_plg_start_cache(void)325 void rgw_plg_start_cache(void)
326 {
327 	cache_started++;
328 }
329 
rgw_plg_loop_req(struct rgw_radius_msg_meta ** rad,struct msg ** diam_msg,struct rgw_client * cli)330 int 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 */
rgw_plg_loop_ans(struct rgw_radius_msg_meta * req,struct msg ** diam_ans,struct radius_msg ** rad_ans,struct rgw_client * cli)387 int 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 
rgw_plg_fini(void)440 void 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 }
492