comparison freeDiameter/routing_dispatch.c @ 123:960fa8048805

Merged routing and dispatch files for similarities
author Sebastien Decugis <sdecugis@nict.go.jp>
date Wed, 09 Dec 2009 19:02:31 +0900
parents freeDiameter/routing.c@e66a82a739fa
children cc42d8607114
comparison
equal deleted inserted replaced
122:e66a82a739fa 123:960fa8048805
1 /*********************************************************************************************************
2 * Software License Agreement (BSD License) *
3 * Author: Sebastien Decugis <sdecugis@nict.go.jp> *
4 * *
5 * Copyright (c) 2009, 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 #include "fD.h"
37
38 /********************************************************************************/
39 /* First part : handling the extensions callbacks */
40 /********************************************************************************/
41
42 /* Lists of the callbacks, and locks to protect them */
43 static pthread_rwlock_t rt_fwd_lock = PTHREAD_RWLOCK_INITIALIZER;
44 static struct fd_list rt_fwd_list = FD_LIST_INITIALIZER_O(rt_fwd_list, &rt_fwd_lock);
45
46 static pthread_rwlock_t rt_out_lock = PTHREAD_RWLOCK_INITIALIZER;
47 static struct fd_list rt_out_list = FD_LIST_INITIALIZER_O(rt_out_list, &rt_out_lock);
48
49 /* Items in the lists are the same */
50 struct rt_hdl {
51 struct fd_list chain; /* link in the rt_fwd_list or rt_out_list */
52 void * cbdata; /* the registered data */
53 union {
54 int order; /* This value is used to sort the list */
55 int dir; /* It is the direction for FWD handlers */
56 int prio; /* and the priority for OUT handlers */
57 };
58 union {
59 int (*rt_fwd_cb)(void * cbdata, struct msg ** msg);
60 int (*rt_out_cb)(void * cbdata, struct msg * msg, struct fd_list * candidates);
61 };
62 };
63
64 /* Add a new entry in the list */
65 static int add_ordered(struct rt_hdl * new, struct fd_list * list)
66 {
67 /* The list is ordered by prio parameter */
68 struct fd_list * li;
69
70 CHECK_POSIX( pthread_rwlock_wrlock(list->o) );
71
72 for (li = list->next; li != list; li = li->next) {
73 struct rt_hdl * h = (struct rt_hdl *) li;
74 if (new->order <= h->order)
75 break;
76 }
77
78 fd_list_insert_before(li, &new->chain);
79
80 CHECK_POSIX( pthread_rwlock_unlock(list->o) );
81
82 return 0;
83 }
84
85 /* Register a new FWD callback */
86 int fd_rt_fwd_register ( int (*rt_fwd_cb)(void * cbdata, struct msg ** msg), void * cbdata, enum fd_rt_fwd_dir dir, struct fd_rt_fwd_hdl ** handler )
87 {
88 struct rt_hdl * new;
89
90 TRACE_ENTRY("%p %p %d %p", rt_fwd_cb, cbdata, dir, handler);
91 CHECK_PARAMS( rt_fwd_cb );
92 CHECK_PARAMS( (dir >= RT_FWD_REQ) && ( dir <= RT_FWD_ANS) );
93
94 /* Create a new container */
95 CHECK_MALLOC(new = malloc(sizeof(struct rt_hdl)));
96 memset(new, 0, sizeof(struct rt_hdl));
97
98 /* Write the content */
99 fd_list_init(&new->chain, NULL);
100 new->cbdata = cbdata;
101 new->dir = dir;
102 new->rt_fwd_cb = rt_fwd_cb;
103
104 /* Save this in the list */
105 CHECK_FCT( add_ordered(new, &rt_fwd_list) );
106
107 /* Give it back to the extension if needed */
108 if (handler)
109 *handler = (void *)new;
110
111 return 0;
112 }
113
114 /* Remove it */
115 int fd_rt_fwd_unregister ( struct fd_rt_fwd_hdl * handler, void ** cbdata )
116 {
117 struct rt_hdl * del;
118 TRACE_ENTRY( "%p %p", handler, cbdata);
119 CHECK_PARAMS( handler );
120
121 del = (struct rt_hdl *)handler;
122 CHECK_PARAMS( del->chain.head == &rt_fwd_list );
123
124 /* Unlink */
125 CHECK_POSIX( pthread_rwlock_wrlock(&rt_fwd_lock) );
126 fd_list_unlink(&del->chain);
127 CHECK_POSIX( pthread_rwlock_unlock(&rt_fwd_lock) );
128
129 if (cbdata)
130 *cbdata = del->cbdata;
131
132 free(del);
133 return 0;
134 }
135
136 /* Register a new OUT callback */
137 int fd_rt_out_register ( int (*rt_out_cb)(void * cbdata, struct msg * msg, struct fd_list * candidates), void * cbdata, int priority, struct fd_rt_out_hdl ** handler )
138 {
139 struct rt_hdl * new;
140
141 TRACE_ENTRY("%p %p %d %p", rt_out_cb, cbdata, priority, handler);
142 CHECK_PARAMS( rt_out_cb );
143
144 /* Create a new container */
145 CHECK_MALLOC(new = malloc(sizeof(struct rt_hdl)));
146 memset(new, 0, sizeof(struct rt_hdl));
147
148 /* Write the content */
149 fd_list_init(&new->chain, NULL);
150 new->cbdata = cbdata;
151 new->prio = priority;
152 new->rt_out_cb = rt_out_cb;
153
154 /* Save this in the list */
155 CHECK_FCT( add_ordered(new, &rt_out_list) );
156
157 /* Give it back to the extension if needed */
158 if (handler)
159 *handler = (void *)new;
160
161 return 0;
162 }
163
164 /* Remove it */
165 int fd_rt_out_unregister ( struct fd_rt_out_hdl * handler, void ** cbdata )
166 {
167 struct rt_hdl * del;
168 TRACE_ENTRY( "%p %p", handler, cbdata);
169 CHECK_PARAMS( handler );
170
171 del = (struct rt_hdl *)handler;
172 CHECK_PARAMS( del->chain.head == &rt_out_list );
173
174 /* Unlink */
175 CHECK_POSIX( pthread_rwlock_wrlock(&rt_out_lock) );
176 fd_list_unlink(&del->chain);
177 CHECK_POSIX( pthread_rwlock_unlock(&rt_out_lock) );
178
179 if (cbdata)
180 *cbdata = del->cbdata;
181
182 free(del);
183 return 0;
184 }
185
186 /********************************************************************************/
187 /* Helper functions */
188 /********************************************************************************/
189 /* Test if a User-Name AVP contains a Decorated NAI -- RFC4282, draft-ietf-dime-nai-routing-04 */
190 static int is_decorated_NAI(union avp_value * un)
191 {
192 int i;
193 TRACE_ENTRY("%p", un);
194
195 /* If there was no User-Name, we return false */
196 if (un == NULL)
197 return 0;
198
199 /* Search if there is a '!' before any '@' -- do we need to check it contains a '.' ? */
200 for (i = 0; i < un->os.len; i++) {
201 if ( un->os.data[i] == (unsigned char) '!' )
202 return 1;
203 if ( un->os.data[i] == (unsigned char) '@' )
204 break;
205 if ( un->os.data[i] == (unsigned char) '\\' )
206 i++; /* next one was escaped */
207 }
208
209 return 0;
210 }
211
212 /* Create new User-Name and Destination-Realm values */
213 static int process_decorated_NAI(union avp_value * un, union avp_value * dr)
214 {
215 int i, at_idx = 0, sep_idx = 0;
216 unsigned char * old_un;
217 TRACE_ENTRY("%p %p", un, dr);
218 CHECK_PARAMS(un && dr);
219
220 /* Save the decorated User-Name, for example 'homerealm.example.net!user@otherrealm.example.net' */
221 old_un = un->os.data;
222
223 /* Search the positions of the first '!' and the '@' in the string */
224 for (i = 0; i < un->os.len; i++) {
225 if ( (!sep_idx) && (old_un[i] == (unsigned char) '!') )
226 sep_idx = i;
227 if ( old_un[i] == (unsigned char) '@' ) {
228 at_idx = i;
229 break;
230 }
231 if ( un->os.data[i] == (unsigned char) '\\' )
232 i++; /* next one is escaped */
233 }
234
235 CHECK_PARAMS( 0 < sep_idx < at_idx < un->os.len);
236
237 /* Create the new User-Name value */
238 CHECK_MALLOC( un->os.data = malloc( at_idx ) );
239 memcpy( un->os.data, old_un + sep_idx + 1, at_idx - sep_idx ); /* user@ */
240 memcpy( un->os.data + at_idx - sep_idx, old_un, sep_idx ); /* homerealm.example.net */
241
242 /* Create the new Destination-Realm value */
243 CHECK_MALLOC( dr->os.data = realloc(dr->os.data, sep_idx) );
244 memcpy( dr->os.data, old_un, sep_idx );
245 dr->os.len = sep_idx;
246
247 TRACE_DEBUG(FULL, "Processed Decorated NAI '%.*s' into '%.*s' (%.*s)",
248 un->os.len, old_un,
249 at_idx, un->os.data,
250 dr->os.len, dr->os.data);
251
252 un->os.len = at_idx;
253 free(old_un);
254
255 return 0;
256 }
257
258 /* Function to return an error to an incoming request */
259 static int return_error(struct msg * msg, char * error_code, char * error_message, struct avp * failedavp)
260 {
261 struct fd_peer * peer;
262 int is_loc = 0;
263
264 /* Get the source of the message */
265 {
266 char * id;
267 CHECK_FCT( fd_msg_source_get( msg, &id ) );
268
269 if (id == NULL) {
270 is_loc = 1; /* The message was issued locally */
271 } else {
272
273 /* Search the peer with this id */
274 CHECK_FCT( fd_peer_getbyid( id, (void *)&peer ) );
275
276 if (!peer) {
277 TRACE_DEBUG(INFO, "Unable to send error '%s' to deleted peer '%s' in reply to:", error_code, id);
278 fd_msg_dump_walk(INFO, msg);
279 fd_msg_free(msg);
280 return 0;
281 }
282 }
283 }
284
285 /* Create the error message */
286 CHECK_FCT( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &msg, MSGFL_ANSW_ERROR ) );
287
288 /* Set the error code */
289 CHECK_FCT( fd_msg_rescode_set(msg, error_code, error_message, failedavp, 1 ) );
290
291 /* Send the answer */
292 if (is_loc) {
293 CHECK_FCT( fd_fifo_post(fd_g_incoming, &msg) );
294 } else {
295 CHECK_FCT( fd_out_send(&msg, NULL, peer) );
296 }
297
298 /* Done */
299 return 0;
300 }
301
302
303 /********************************************************************************/
304 /* Second part : the threads moving messages in the daemon */
305 /********************************************************************************/
306
307 /* Note: in the first version, we only create one thread of each kind.
308 We could improve the scalability by using the threshold feature of the queues
309 to create additional threads if a queue is filling up.
310 */
311
312 /* Control of the threads */
313 enum thread_state { INITIAL = 0, RUNNING = 1, TERMINATED = 2 };
314 static void cleanup_state(void * state_loc)
315 {
316 if (state_loc)
317 *(enum thread_state *)state_loc = TERMINATED;
318 }
319 static pthread_mutex_t order_lock = PTHREAD_MUTEX_INITIALIZER;
320 static enum { RUN = 0, STOP = 1 } order_val = RUN;;
321
322 /* The dispatching thread */
323 static void * dispatch_thr(void * arg)
324 {
325 TRACE_ENTRY("%p", arg);
326
327 /* Set the thread name */
328 {
329 char buf[48];
330 snprintf(buf, sizeof(buf), "Dispatch %p", arg);
331 fd_log_threadname ( buf );
332 }
333
334 pthread_cleanup_push( cleanup_state, arg );
335
336 /* Mark the thread running */
337 *(enum thread_state *)arg = RUNNING;
338
339 do {
340 struct msg * msg;
341 struct msg_hdr * hdr;
342 int is_req = 0;
343 struct session * sess;
344 enum disp_action action;
345 const char * ec = NULL;
346 const char * em = NULL;
347
348 /* Test the environment */
349 {
350 int must_stop;
351 CHECK_POSIX_DO( pthread_mutex_lock(&order_lock), goto end ); /* we lock to flush the caches */
352 must_stop = (order_val == STOP);
353 CHECK_POSIX_DO( pthread_mutex_unlock(&order_lock), goto end );
354 if (must_stop)
355 goto end;
356
357 pthread_testcancel();
358 }
359
360 /* Ok, we are allowed to run */
361
362 /* Get the next message from the queue */
363 {
364 int ret;
365 CHECK_FCT_DO( ret = fd_fifo_get ( fd_g_local, &msg ),
366 {
367 if (ret == EPIPE)
368 /* The queue was destroyed */
369 goto end;
370 goto fatal_error;
371 } );
372 }
373
374 if (TRACE_BOOL(FULL)) {
375 TRACE_DEBUG(FULL, "Picked next message");
376 fd_msg_dump_one(ANNOYING, msg);
377 }
378
379 /* Read the message header */
380 CHECK_FCT_DO( fd_msg_hdr(msg, &hdr), goto fatal_error );
381 is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
382
383 /* Note: if the message is for local delivery, we should test for duplicate
384 (draft-asveren-dime-dupcons-00). This may conflict with path validation decisions, no clear answer yet */
385
386 /* At this point, we need to understand the message content, so parse it */
387 {
388 int ret;
389 CHECK_FCT_DO( ret = fd_msg_parse_or_error( &msg ),
390 {
391 /* in case of error, the message is already dump'd */
392 if ((ret == EBADMSG) && (msg != NULL)) {
393 /* msg now contains the answer message to send back */
394 CHECK_FCT_DO( fd_fifo_post(fd_g_outgoing, &msg), goto fatal_error );
395 }
396 if (msg) { /* another error happen'd */
397 TRACE_DEBUG(INFO, "An unexpected error occurred (%s), discarding a message:", strerror(ret));
398 fd_msg_dump_walk(INFO, msg);
399 CHECK_FCT_DO( fd_msg_free(msg), /* continue */);
400 }
401 /* Go to the next message */
402 continue;
403 } );
404 }
405
406 /* First, if the original request was registered with a callback and we receive the answer, call it. */
407 if ( ! is_req ) {
408 struct msg * qry;
409 void (*anscb)(void *, struct msg **) = NULL;
410 void * data = NULL;
411
412 /* Retrieve the corresponding query */
413 CHECK_FCT_DO( fd_msg_answ_getq( msg, &qry ), goto fatal_error );
414
415 /* Retrieve any registered handler */
416 CHECK_FCT_DO( fd_msg_anscb_get( qry, &anscb, &data ), goto fatal_error );
417
418 /* If a callback was registered, pass the message to it */
419 if (anscb != NULL) {
420
421 TRACE_DEBUG(FULL, "Calling callback registered when query was sent (%p, %p)", anscb, data);
422 (*anscb)(data, &msg);
423
424 if (msg == NULL) {
425 /* Ok, the message is now handled, we can skip to the next one */
426 continue;
427 }
428 }
429 }
430
431 /* Retrieve the session of the message */
432 CHECK_FCT_DO( fd_msg_sess_get(fd_g_config->cnf_dict, msg, &sess, NULL), goto fatal_error );
433
434 /* Now, call any callback registered for the message */
435 CHECK_FCT_DO( fd_msg_dispatch ( &msg, sess, &action, &ec), goto fatal_error );
436
437 /* Now, act depending on msg and action and ec */
438 if (!msg)
439 continue;
440
441 switch ( action ) {
442 case DISP_ACT_CONT:
443 /* No callback has handled the message, let's reply with a generic error */
444 em = "The message was not handled by any extension callback";
445 ec = "DIAMETER_COMMAND_UNSUPPORTED";
446
447 case DISP_ACT_ERROR:
448 /* We have a problem with delivering the message */
449 if (ec == NULL) {
450 ec = "DIAMETER_UNABLE_TO_COMPLY";
451 }
452
453 if (!is_req) {
454 TRACE_DEBUG(INFO, "Received an answer to a localy issued query, but no handler processed this answer!");
455 fd_msg_dump_walk(INFO, msg);
456 fd_msg_free(msg);
457 msg = NULL;
458 break;
459 }
460
461 /* Create an answer with the error code and message */
462 CHECK_FCT_DO( fd_msg_new_answer_from_req ( fd_g_config->cnf_dict, &msg, 0 ), goto fatal_error );
463 CHECK_FCT_DO( fd_msg_rescode_set(msg, (char *)ec, (char *)em, NULL, 1 ), goto fatal_error );
464
465 case DISP_ACT_SEND:
466 /* Now, send the message */
467 CHECK_FCT_DO( fd_fifo_post(fd_g_outgoing, &msg), goto fatal_error );
468 }
469
470 /* We're done with this message */
471
472 } while (1);
473
474 fatal_error:
475 TRACE_DEBUG(INFO, "An error occurred in dispatch module! Thread is terminating...");
476 CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), );
477
478 end:
479 /* Mark the thread as terminated */
480 pthread_cleanup_pop(1);
481 return NULL;
482 }
483
484
485 /* The (routing-in) thread -- see description in freeDiameter.h */
486 static void * routing_in_thr(void * arg)
487 {
488 TRACE_ENTRY("%p", arg);
489
490 /* Set the thread name */
491 {
492 char buf[48];
493 snprintf(buf, sizeof(buf), "Routing-IN %p", arg);
494 fd_log_threadname ( buf );
495 }
496
497 pthread_cleanup_push( cleanup_state, arg );
498
499 /* Mark the thread running */
500 *(enum thread_state *)arg = RUNNING;
501
502 /* Main thread loop */
503 do {
504 struct msg * msg;
505 struct msg_hdr * hdr;
506 int is_req = 0;
507 int is_err = 0;
508 char * qry_src = NULL;
509
510 /* Test if we were told to stop */
511 {
512 int must_stop;
513 CHECK_POSIX_DO( pthread_mutex_lock(&order_lock), goto end ); /* we lock to flush the caches */
514 must_stop = (order_val == STOP);
515 CHECK_POSIX_DO( pthread_mutex_unlock(&order_lock), goto end );
516 if (must_stop)
517 goto end;
518
519 pthread_testcancel();
520 }
521
522 /* Get the next message from the incoming queue */
523 {
524 int ret;
525 CHECK_FCT_DO( ret = fd_fifo_get ( fd_g_incoming, &msg ),
526 {
527 if (ret == EPIPE)
528 /* The queue was destroyed */
529 goto end;
530 goto fatal_error;
531 } );
532 }
533
534 if (TRACE_BOOL(FULL)) {
535 TRACE_DEBUG(FULL, "Picked next message");
536 fd_msg_dump_one(ANNOYING, msg);
537 }
538
539 /* Read the message header */
540 CHECK_FCT_DO( fd_msg_hdr(msg, &hdr), goto fatal_error );
541 is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
542 is_err = hdr->msg_flags & CMD_FLAG_ERROR;
543
544 /* Handle incorrect bits */
545 if (is_req && is_err) {
546 CHECK_FCT_DO( return_error( msg, "DIAMETER_INVALID_HDR_BITS", "R & E bits were set", NULL), goto fatal_error );
547 continue;
548 }
549
550 /* If it is a request, we must analyze its content to decide what we do with it */
551 if (is_req) {
552 struct avp * avp, *un = NULL;
553 union avp_value * un_val = NULL, *dr_val = NULL;
554 enum status { UNKNOWN, YES, NO };
555 /* Are we Destination-Host? */
556 enum status is_dest_host = UNKNOWN;
557 /* Are we Destination-Realm? */
558 enum status is_dest_realm = UNKNOWN;
559 /* Do we support the application of the message? */
560 enum status is_local_app = UNKNOWN;
561
562 /* Check if we have local support for the message application */
563 if ( (hdr->msg_appl == 0) || (hdr->msg_appl == AI_RELAY) ) {
564 TRACE_DEBUG(INFO, "Received a routable message with application id 0, returning DIAMETER_APPLICATION_UNSUPPORTED");
565 CHECK_FCT_DO( return_error( msg, "DIAMETER_APPLICATION_UNSUPPORTED", "Routable message with application id 0 or relay", NULL), goto fatal_error );
566 continue;
567 } else {
568 struct fd_app * app;
569 CHECK_FCT_DO( fd_app_check(&fd_g_config->cnf_apps, hdr->msg_appl, &app), goto fatal_error );
570 is_local_app = (app ? YES : NO);
571 }
572
573 /* Parse the message for Dest-Host and Dest-Realm */
574 CHECK_FCT_DO( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL), goto fatal_error );
575 while (avp) {
576 struct avp_hdr * ahdr;
577 CHECK_FCT_DO( fd_msg_avp_hdr( avp, &ahdr ), goto fatal_error );
578
579 if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
580 switch (ahdr->avp_code) {
581 case AC_DESTINATION_HOST:
582 /* Parse this AVP */
583 CHECK_FCT_DO( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ), goto fatal_error );
584 ASSERT( ahdr->avp_value );
585 /* Compare the Destination-Host AVP of the message with our identity */
586 if (ahdr->avp_value->os.len != fd_g_config->cnf_diamid_len) {
587 is_dest_host = NO;
588 } else {
589 is_dest_host = (strncasecmp(fd_g_config->cnf_diamid, (char *)ahdr->avp_value->os.data, fd_g_config->cnf_diamid_len)
590 ? NO : YES);
591 }
592 break;
593
594 case AC_DESTINATION_REALM:
595 /* Parse this AVP */
596 CHECK_FCT_DO( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ), goto fatal_error );
597 ASSERT( ahdr->avp_value );
598 dr_val = ahdr->avp_value;
599 /* Compare the Destination-Realm AVP of the message with our identity */
600 if (ahdr->avp_value->os.len != fd_g_config->cnf_diamrlm_len) {
601 is_dest_realm = NO;
602 } else {
603 is_dest_realm = (strncasecmp(fd_g_config->cnf_diamrlm, (char *)ahdr->avp_value->os.data, fd_g_config->cnf_diamrlm_len)
604 ? NO : YES);
605 }
606 break;
607
608 case AC_USER_NAME:
609 /* Parse this AVP */
610 CHECK_FCT_DO( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ), goto fatal_error );
611 ASSERT( ahdr->avp_value );
612 un = avp;
613 un_val = ahdr->avp_value;
614 break;
615 }
616 }
617
618 if ((is_dest_host != UNKNOWN) && (is_dest_realm != UNKNOWN) && un)
619 break;
620
621 /* Go to next AVP */
622 CHECK_FCT_DO( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL), goto fatal_error );
623 }
624
625 /* OK, now decide what we do with the request */
626
627 /* Handle the missing routing AVPs first */
628 if ( is_dest_realm == UNKNOWN ) {
629 CHECK_FCT_DO( return_error( msg, "DIAMETER_COMMAND_UNSUPPORTED", "Non-routable message not supported (invalid bit ? missing Destination-Realm ?)", NULL), goto fatal_error );
630 continue;
631 }
632
633 /* If we are listed as Destination-Host */
634 if (is_dest_host == YES) {
635 if (is_local_app == YES) {
636 /* Ok, give the message to the dispatch thread */
637 CHECK_FCT_DO(fd_fifo_post(fd_g_local, &msg), goto fatal_error );
638 } else {
639 /* We don't support the application, reply an error */
640 CHECK_FCT_DO( return_error( msg, "DIAMETER_APPLICATION_UNSUPPORTED", NULL, NULL), goto fatal_error );
641 }
642 continue;
643 }
644
645 /* If the message is explicitely for someone else */
646 if ((is_dest_host == NO) || (is_dest_realm == NO)) {
647 if (fd_g_config->cnf_flags.no_fwd) {
648 CHECK_FCT_DO( return_error( msg, "DIAMETER_UNABLE_TO_DELIVER", "This peer is not an agent", NULL), goto fatal_error );
649 continue;
650 }
651 } else {
652 /* Destination-Host was not set, and Destination-Realm is matching : we may handle or pass to a fellow peer */
653
654 /* test for decorated NAI (draft-ietf-dime-nai-routing-04 section 4.4) */
655 if (is_decorated_NAI(un_val)) {
656 /* Handle the decorated NAI */
657 CHECK_FCT_DO( process_decorated_NAI(un_val, dr_val),
658 {
659 /* If the process failed, we assume it is because of the AVP format */
660 CHECK_FCT_DO( return_error( msg, "DIAMETER_INVALID_AVP_VALUE", "Failed to process decorated NAI", un), goto fatal_error );
661 continue;
662 } );
663
664 /* We have transformed the AVP, now submit it again in the queue */
665 CHECK_FCT_DO(fd_fifo_post(fd_g_incoming, &msg), goto fatal_error );
666 continue;
667 }
668
669 if (is_local_app == YES) {
670 /* Handle localy since we are able to */
671 CHECK_FCT_DO(fd_fifo_post(fd_g_local, &msg), goto fatal_error );
672 continue;
673 }
674
675 if (fd_g_config->cnf_flags.no_fwd) {
676 /* We return an error */
677 CHECK_FCT_DO( return_error( msg, "DIAMETER_APPLICATION_UNSUPPORTED", NULL, NULL), goto fatal_error );
678 continue;
679 }
680 }
681
682 /* From that point, for requests, we will call the registered callbacks, then forward to another peer */
683
684 } else {
685 /* The message is an answer */
686 struct msg * qry;
687
688 /* Retrieve the corresponding query and its origin */
689 CHECK_FCT_DO( fd_msg_answ_getq( msg, &qry ), goto fatal_error );
690 CHECK_FCT_DO( fd_msg_source_get( qry, &qry_src ), goto fatal_error );
691
692 if ((!qry_src) && (!is_err)) {
693 /* The message is a normal answer to a request issued localy, we do not call the callbacks chain on it. */
694 CHECK_FCT_DO(fd_fifo_post(fd_g_local, &msg), goto fatal_error );
695 continue;
696 }
697
698 /* From that point, for answers, we will call the registered callbacks, then pass it to the dispatch module or forward it */
699 }
700
701 /* Call all registered callbacks for this message */
702 {
703 struct fd_list * li;
704
705 CHECK_FCT_DO( pthread_rwlock_rdlock( &rt_fwd_lock ), goto fatal_error );
706 pthread_cleanup_push( fd_cleanup_rwlock, &rt_fwd_lock );
707
708 /* requests: dir = 1 & 2 => in order; answers = 3 & 2 => in reverse order */
709 for ( li = (is_req ? rt_fwd_list.next : rt_fwd_list.prev) ; msg && (li != &rt_fwd_list) ; li = (is_req ? li->next : li->prev) ) {
710 struct rt_hdl * rh = (struct rt_hdl *)li;
711
712 if (is_req && (rh->dir > RT_FWD_ALL))
713 break;
714 if ((!is_req) && (rh->dir < RT_FWD_ALL))
715 break;
716
717 /* Ok, call this cb */
718 TRACE_DEBUG(ANNOYING, "Calling next FWD callback on %p : %p", msg, rh->rt_fwd_cb);
719 CHECK_FCT_DO( (*rh->rt_fwd_cb)(rh->cbdata, &msg),
720 {
721 TRACE_DEBUG(INFO, "A FWD routing callback returned an error, message discarded.");
722 fd_msg_dump_walk(INFO, msg);
723 fd_msg_free(msg);
724 msg = NULL;
725 } );
726 }
727
728 pthread_cleanup_pop(0);
729 CHECK_FCT_DO( pthread_rwlock_unlock( &rt_fwd_lock ), goto fatal_error );
730
731 /* If a callback has handled the message, we stop now */
732 if (!msg)
733 continue;
734 }
735
736 /* Now handle the message to the next step: either forward to another peer, or for local delivery */
737 if (is_req || qry_src) {
738 CHECK_FCT_DO(fd_fifo_post(fd_g_outgoing, &msg), goto fatal_error );
739 } else {
740 CHECK_FCT_DO(fd_fifo_post(fd_g_local, &msg), goto fatal_error );
741 }
742
743 /* We're done with this message */
744 } while (1);
745
746 fatal_error:
747 TRACE_DEBUG(INFO, "An error occurred in routing module! IN thread is terminating...");
748 CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), );
749
750 end:
751 /* Mark the thread as terminated */
752 pthread_cleanup_pop(1);
753 return NULL;
754 }
755
756
757 /* The (routing-out) thread -- see description in freeDiameter.h */
758 static void * routing_out_thr(void * arg)
759 {
760 struct rt_data * rtd = NULL;
761 TRACE_ENTRY("%p", arg);
762
763 /* Set the thread name */
764 {
765 char buf[48];
766 snprintf(buf, sizeof(buf), "Routing OUT %p", arg);
767 fd_log_threadname ( buf );
768 }
769
770 pthread_cleanup_push( cleanup_state, arg );
771
772 /* Mark the thread running */
773 *(enum thread_state *)arg = RUNNING;
774
775
776 /* Main thread loop */
777 do {
778 struct msg * msg;
779 struct msg_hdr * hdr;
780 int is_req = 0;
781 struct fd_list * li, *candidates;
782 struct avp * avp;
783 struct rtd_candidate * c;
784
785 /* If we loop'd with some undeleted routing data, destroy it */
786 if (rtd != NULL)
787 fd_rtd_free(&rtd);
788
789 /* Test if we were told to stop */
790 {
791 int must_stop;
792 CHECK_POSIX_DO( pthread_mutex_lock(&order_lock), goto end ); /* we lock to flush the caches */
793 must_stop = (order_val == STOP);
794 CHECK_POSIX_DO( pthread_mutex_unlock(&order_lock), goto end );
795 if (must_stop)
796 goto end;
797
798 pthread_testcancel();
799 }
800
801 /* Get the next message from the ougoing queue */
802 {
803 int ret;
804 CHECK_FCT_DO( ret = fd_fifo_get ( fd_g_outgoing, &msg ),
805 {
806 if (ret == EPIPE)
807 /* The queue was destroyed */
808 goto end;
809 goto fatal_error;
810 } );
811 }
812
813 if (TRACE_BOOL(FULL)) {
814 TRACE_DEBUG(FULL, "Picked next message");
815 fd_msg_dump_one(ANNOYING, msg);
816 }
817
818 /* Read the message header */
819 CHECK_FCT_DO( fd_msg_hdr(msg, &hdr), goto fatal_error );
820 is_req = hdr->msg_flags & CMD_FLAG_REQUEST;
821
822 /* For answers, the routing is very easy */
823 if ( ! is_req ) {
824 struct msg * qry;
825 char * qry_src = NULL;
826 struct msg_hdr * qry_hdr;
827 struct fd_peer * peer = NULL;
828
829 /* Retrieve the corresponding query and its origin */
830 CHECK_FCT_DO( fd_msg_answ_getq( msg, &qry ), goto fatal_error );
831 CHECK_FCT_DO( fd_msg_source_get( qry, &qry_src ), goto fatal_error );
832
833 ASSERT( qry_src ); /* if it is NULL, the message should have been in the LOCAL queue! */
834
835 /* Find the peer corresponding to this name */
836 CHECK_FCT_DO( fd_peer_getbyid( qry_src, (void *) &peer ), goto fatal_error );
837 if ((!peer) || (peer->p_hdr.info.runtime.pir_state != STATE_OPEN)) {
838 TRACE_DEBUG(INFO, "Unable to forward answer message to peer '%s', deleted or not in OPEN state.", qry_src);
839 fd_msg_dump_walk(INFO, msg);
840 fd_msg_free(msg);
841 continue;
842 }
843
844 /* We must restore the hop-by-hop id */
845 CHECK_FCT_DO( fd_msg_hdr(qry, &qry_hdr), goto fatal_error );
846 hdr->msg_hbhid = qry_hdr->msg_hbhid;
847
848 /* Push the message into this peer */
849 CHECK_FCT_DO( fd_out_send(&msg, NULL, peer), goto fatal_error );
850
851 /* We're done with this answer */
852 continue;
853 }
854
855 /* From that point, the message is a request */
856
857 /* Get the routing data out of the message if any (in case of re-transmit) */
858 CHECK_FCT_DO( fd_msg_rt_get ( msg, &rtd ), goto fatal_error );
859
860 /* If there is no routing data already, let's create it */
861 if (rtd == NULL) {
862 CHECK_FCT_DO( fd_rtd_init(&rtd), goto fatal_error );
863
864 /* Add all peers in OPEN state */
865 CHECK_FCT_DO( pthread_rwlock_wrlock(&fd_g_activ_peers_rw), goto fatal_error );
866 for (li = fd_g_activ_peers.next; li != &fd_g_activ_peers; li = li->next) {
867 struct fd_peer * p = (struct fd_peer *)li->o;
868 CHECK_FCT_DO( fd_rtd_candidate_add(rtd, p->p_hdr.info.pi_diamid), goto fatal_error);
869 }
870 CHECK_FCT_DO( pthread_rwlock_unlock(&fd_g_activ_peers_rw), goto fatal_error );
871
872 /* Now let's remove all peers from the Route-Records */
873 CHECK_FCT_DO( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL), goto fatal_error );
874 while (avp) {
875 struct avp_hdr * ahdr;
876 CHECK_FCT_DO( fd_msg_avp_hdr( avp, &ahdr ), goto fatal_error );
877
878 if ((ahdr->avp_code == AC_ROUTE_RECORD) && (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) ) {
879 /* Parse this AVP */
880 CHECK_FCT_DO( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ), goto fatal_error );
881 ASSERT( ahdr->avp_value );
882 /* Remove this value from the list */
883 fd_rtd_candidate_del(rtd, (char *)ahdr->avp_value->os.data, ahdr->avp_value->os.len);
884 }
885
886 /* Go to next AVP */
887 CHECK_FCT_DO( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL), goto fatal_error );
888 }
889 }
890
891 /* Note: we reset the scores and pass the message to the callbacks, maybe we could re-use the saved scores when we have received an error ? */
892
893 /* Ok, we have our list in rtd now, let's (re)initialize the scores */
894 fd_rtd_candidate_extract(rtd, &candidates);
895
896 /* Pass the list to registered callbacks (even if it is empty) */
897 {
898 CHECK_FCT_DO( pthread_rwlock_rdlock( &rt_out_lock ), goto fatal_error );
899 pthread_cleanup_push( fd_cleanup_rwlock, &rt_out_lock );
900
901 /* We call the cb by reverse priority order */
902 for ( li = rt_out_list.prev ; li != &rt_out_list ; li = li->prev ) {
903 struct rt_hdl * rh = (struct rt_hdl *)li;
904
905 TRACE_DEBUG(ANNOYING, "Calling next OUT callback on %p : %p (prio %d)", msg, rh->rt_out_cb, rh->prio);
906 CHECK_FCT_DO( (*rh->rt_out_cb)(rh->cbdata, msg, candidates),
907 {
908 TRACE_DEBUG(INFO, "An OUT routing callback returned an error ! Message discarded.");
909 fd_msg_dump_walk(INFO, msg);
910 fd_msg_free(msg);
911 msg = NULL;
912 break;
913 } );
914 }
915
916 pthread_cleanup_pop(0);
917 CHECK_FCT_DO( pthread_rwlock_unlock( &rt_out_lock ), goto fatal_error );
918
919 /* If an error occurred, skip to the next message */
920 if (!msg)
921 continue;
922 }
923
924 /* Order the candidate peers by score attributed by the callbacks */
925 CHECK_FCT_DO( fd_rtd_candidate_reorder(candidates), goto fatal_error );
926
927 /* Save the routing information in the message */
928 CHECK_FCT_DO( fd_msg_rt_associate ( msg, &rtd ), goto fatal_error );
929
930 /* Now try sending the message */
931 for (li = candidates->prev; li != candidates; li = li->prev) {
932 struct fd_peer * peer;
933
934 c = (struct rtd_candidate *) li;
935
936 /* Stop when we have reached the end of valid candidates */
937 if (c->score < 0)
938 break;
939
940 /* Search for the peer */
941 CHECK_FCT_DO( fd_peer_getbyid( c->diamid, (void *)&peer ), goto fatal_error );
942
943 if (peer && (peer->p_hdr.info.runtime.pir_state == STATE_OPEN)) {
944 /* Send to this one */
945 CHECK_FCT_DO( fd_out_send(&msg, NULL, peer), continue );
946 /* If the sending was successful */
947 break;
948 }
949 }
950
951 /* If the message has not been sent, return an error */
952 if (msg) {
953 TRACE_DEBUG(INFO, "Could not send the following message, replying with UNABLE_TO_DELIVER");
954 fd_msg_dump_walk(INFO, msg);
955 return_error( msg, "DIAMETER_UNABLE_TO_DELIVER", "No suitable candidate to route the message to", NULL);
956 }
957
958 /* We're done with this message */
959
960 } while (1);
961
962 fatal_error:
963 TRACE_DEBUG(INFO, "An error occurred in routing module! OUT thread is terminating...");
964 CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), );
965
966 end:
967 /* Mark the thread as terminated */
968 pthread_cleanup_pop(1);
969 return NULL;
970 }
971
972
973 /********************************************************************************/
974 /* Some default routing callbacks */
975 /********************************************************************************/
976
977 /* First OUT callback: prevent sending to peers that do not support the message application */
978 static int dont_send_if_no_common_app(void * cbdata, struct msg * msg, struct fd_list * candidates)
979 {
980 struct fd_list * li;
981 struct msg_hdr * hdr;
982
983 TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
984 CHECK_PARAMS(msg && candidates);
985
986 CHECK_FCT( fd_msg_hdr(msg, &hdr) );
987
988 /* For Base Diameter Protocol, every peer is supposed to support it, so skip */
989 if (hdr->msg_appl == 0)
990 return 0;
991
992 /* Otherwise, check that the peers support the application */
993 for (li = candidates->next; li != candidates; li = li->next) {
994 struct rtd_candidate *c = (struct rtd_candidate *) li;
995 struct fd_peer * peer;
996 struct fd_app *found;
997 CHECK_FCT( fd_peer_getbyid( c->diamid, (void *)&peer ) );
998 if (peer && (peer->p_hdr.info.runtime.pir_relay == 0)) {
999 /* Check if the remote peer advertised the message's appli */
1000 CHECK_FCT( fd_app_check(&peer->p_hdr.info.runtime.pir_apps, hdr->msg_appl, &found) );
1001 if (!found)
1002 c->score += FD_SCORE_NO_DELIVERY;
1003 }
1004 }
1005
1006 return 0;
1007 }
1008
1009 /* Second OUT callback: Detect if the Destination-Host and Destination-Realm match the peer */
1010 static int score_destination_avp(void * cbdata, struct msg * msg, struct fd_list * candidates)
1011 {
1012 struct fd_list * li;
1013 struct avp * avp;
1014 union avp_value *dh = NULL, *dr = NULL;
1015
1016 TRACE_ENTRY("%p %p %p", cbdata, msg, candidates);
1017 CHECK_PARAMS(msg && candidates);
1018
1019 /* Search the Destination-Host and Destination-Realm AVPs -- we could also use fd_msg_search_avp here, but this one is slightly more efficient */
1020 CHECK_FCT( fd_msg_browse(msg, MSG_BRW_FIRST_CHILD, &avp, NULL) );
1021 while (avp) {
1022 struct avp_hdr * ahdr;
1023 CHECK_FCT( fd_msg_avp_hdr( avp, &ahdr ) );
1024
1025 if (! (ahdr->avp_flags & AVP_FLAG_VENDOR)) {
1026 switch (ahdr->avp_code) {
1027 case AC_DESTINATION_HOST:
1028 /* Parse this AVP */
1029 CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
1030 ASSERT( ahdr->avp_value );
1031 dh = ahdr->avp_value;
1032 break;
1033
1034 case AC_DESTINATION_REALM:
1035 /* Parse this AVP */
1036 CHECK_FCT( fd_msg_parse_dict ( avp, fd_g_config->cnf_dict, NULL ) );
1037 ASSERT( ahdr->avp_value );
1038 dr = ahdr->avp_value;
1039 break;
1040 }
1041 }
1042
1043 if (dh && dr)
1044 break;
1045
1046 /* Go to next AVP */
1047 CHECK_FCT( fd_msg_browse(avp, MSG_BRW_NEXT, &avp, NULL) );
1048 }
1049
1050 /* Now, check each candidate against these AVP values */
1051 for (li = candidates->next; li != candidates; li = li->next) {
1052 struct rtd_candidate *c = (struct rtd_candidate *) li;
1053 struct fd_peer * peer;
1054 CHECK_FCT( fd_peer_getbyid( c->diamid, (void *)&peer ) );
1055 if (peer) {
1056 if (dh
1057 && (dh->os.len == strlen(peer->p_hdr.info.pi_diamid))
1058 && (strncasecmp(peer->p_hdr.info.pi_diamid, dh->os.data, dh->os.len) == 0)) {
1059 /* The candidate is the Destination-Host */
1060 c->score += FD_SCORE_FINALDEST;
1061 } else {
1062 if (dr && peer->p_hdr.info.runtime.pir_realm
1063 && (dr->os.len == strlen(peer->p_hdr.info.runtime.pir_realm))
1064 && (strncasecmp(peer->p_hdr.info.runtime.pir_realm, dr->os.data, dr->os.len) == 0)) {
1065 /* The candidate's realm matchs the Destination-Realm */
1066 c->score += FD_SCORE_REALM;
1067 }
1068 }
1069 }
1070 }
1071
1072 return 0;
1073 }
1074
1075 /********************************************************************************/
1076 /* The functions for the other files */
1077 /********************************************************************************/
1078
1079 /* Later: TODO("Set thresholds on queues"); */
1080 static pthread_t dispatch = (pthread_t)NULL;
1081 static enum thread_state disp_state = INITIAL;
1082
1083 static pthread_t rt_out = (pthread_t)NULL;
1084 static enum thread_state out_state = INITIAL;
1085
1086 static pthread_t rt_in = (pthread_t)NULL;
1087 static enum thread_state in_state = INITIAL;
1088
1089 /* Initialize the routing and dispatch threads */
1090 int fd_rtdisp_init(void)
1091 {
1092 CHECK_POSIX( pthread_create( &dispatch, NULL, dispatch_thr, &disp_state ) );
1093 CHECK_POSIX( pthread_create( &rt_out, NULL, routing_out_thr, &out_state) );
1094 CHECK_POSIX( pthread_create( &rt_in, NULL, routing_in_thr, &in_state) );
1095
1096 /* Later: TODO("Set the thresholds for the queues to create more threads as needed"); */
1097
1098 /* Register the built-in callbacks */
1099 CHECK_FCT( fd_rt_out_register( dont_send_if_no_common_app, NULL, 10, NULL ) );
1100 CHECK_FCT( fd_rt_out_register( score_destination_avp, NULL, 10, NULL ) );
1101 return 0;
1102 }
1103
1104 /* Ask the thread to terminate after next iteration */
1105 int fd_rtdisp_cleanstop(void)
1106 {
1107 CHECK_POSIX( pthread_mutex_lock(&order_lock) );
1108 order_val = STOP;
1109 CHECK_POSIX( pthread_mutex_unlock(&order_lock) );
1110
1111 return 0;
1112 }
1113
1114 /* Stop the thread after up to one second of wait */
1115 int fd_rtdisp_fini(void)
1116 {
1117 /* Destroy the local queue */
1118 CHECK_FCT_DO( fd_queues_fini_disp(), /* ignore */);
1119
1120 /* Wait for a second for the thread to complete, by monitoring my_state */
1121 if (disp_state != TERMINATED) {
1122 TRACE_DEBUG(INFO, "Waiting for the dispatch thread to have a chance to terminate");
1123 do {
1124 struct timespec ts, ts_final;
1125
1126 CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), break );
1127
1128 ts_final.tv_sec = ts.tv_sec + 1;
1129 ts_final.tv_nsec = ts.tv_nsec;
1130
1131 while (TS_IS_INFERIOR( &ts, &ts_final )) {
1132 if (disp_state == TERMINATED)
1133 break;
1134
1135 usleep(100000);
1136 CHECK_SYS_DO( clock_gettime(CLOCK_REALTIME, &ts), break );
1137 }
1138 } while (0);
1139 }
1140
1141 /* Now stop the thread and reclaim its resources */
1142 CHECK_FCT_DO( fd_thr_term(&dispatch ), /* continue */);
1143
1144
1145 TODO("Add terminating the routing threads");
1146 return 0;
1147 }
1148
1149 /* Cleanup handlers */
1150 int fd_rtdisp_cleanup(void)
1151 {
1152 /* Cleanup all remaining handlers */
1153 while (!FD_IS_LIST_EMPTY(&rt_fwd_list)) {
1154 CHECK_FCT_DO( fd_rt_fwd_unregister ( (void *)rt_fwd_list.next, NULL ), /* continue */ );
1155 }
1156 while (!FD_IS_LIST_EMPTY(&rt_out_list)) {
1157 CHECK_FCT_DO( fd_rt_out_unregister ( (void *)rt_out_list.next, NULL ), /* continue */ );
1158 }
1159
1160 fd_disp_unregister_all(); /* destroy remaining handlers */
1161
1162 return 0;
1163 }
1164
1165
1166 /* Add an application into the peer's supported apps */
1167 int fd_disp_app_support ( struct dict_object * app, struct dict_object * vendor, int auth, int acct )
1168 {
1169 application_id_t aid = 0;
1170 vendor_id_t vid = 0;
1171
1172 TRACE_ENTRY("%p %p %d %d", app, vendor, auth, acct);
1173 CHECK_PARAMS( app && (auth || acct) );
1174
1175 {
1176 enum dict_object_type type = 0;
1177 struct dict_application_data data;
1178 CHECK_FCT( fd_dict_gettype(app, &type) );
1179 CHECK_PARAMS( type == DICT_APPLICATION );
1180 CHECK_FCT( fd_dict_getval(app, &data) );
1181 aid = data.application_id;
1182 }
1183
1184 if (vendor) {
1185 enum dict_object_type type = 0;
1186 struct dict_vendor_data data;
1187 CHECK_FCT( fd_dict_gettype(vendor, &type) );
1188 CHECK_PARAMS( type == DICT_VENDOR );
1189 CHECK_FCT( fd_dict_getval(vendor, &data) );
1190 vid = data.vendor_id;
1191 }
1192
1193 return fd_app_merge(&fd_g_config->cnf_apps, aid, vid, auth, acct);
1194 }
1195
1196
1197
"Welcome to our mercurial repository"