another dependency
[asterisk-bristuff.git] / pbx / pbx_dundi.c
blob007c65d13de82e4ae93c7a89293b129b5f567671
1 /*
2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
19 /*! \file
21 * \brief Distributed Universal Number Discovery (DUNDi)
25 /*** MODULEINFO
26 <depend>zlib</depend>
27 ***/
29 #include "asterisk.h"
31 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <unistd.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <sys/socket.h>
39 #include <string.h>
40 #include <errno.h>
41 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(SOLARIS) || defined(__Darwin__)
42 #include <sys/types.h>
43 #include <netinet/in_systm.h>
44 #endif
45 #include <netinet/ip.h>
46 #include <sys/ioctl.h>
47 #include <netinet/in.h>
48 #include <net/if.h>
49 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__Darwin__)
50 #include <net/if_dl.h>
51 #include <ifaddrs.h>
52 #endif
53 #include <zlib.h>
55 #include "asterisk/file.h"
56 #include "asterisk/logger.h"
57 #include "asterisk/channel.h"
58 #include "asterisk/config.h"
59 #include "asterisk/options.h"
60 #include "asterisk/pbx.h"
61 #include "asterisk/module.h"
62 #include "asterisk/frame.h"
63 #include "asterisk/file.h"
64 #include "asterisk/cli.h"
65 #include "asterisk/lock.h"
66 #include "asterisk/md5.h"
67 #include "asterisk/dundi.h"
68 #include "asterisk/sched.h"
69 #include "asterisk/io.h"
70 #include "asterisk/utils.h"
71 #include "asterisk/crypto.h"
72 #include "asterisk/astdb.h"
73 #include "asterisk/acl.h"
74 #include "asterisk/aes.h"
76 #include "dundi-parser.h"
78 #define MAX_RESULTS 64
80 #define MAX_PACKET_SIZE 8192
82 #define DUNDI_MODEL_INBOUND (1 << 0)
83 #define DUNDI_MODEL_OUTBOUND (1 << 1)
84 #define DUNDI_MODEL_SYMMETRIC (DUNDI_MODEL_INBOUND | DUNDI_MODEL_OUTBOUND)
86 /*! Keep times of last 10 lookups */
87 #define DUNDI_TIMING_HISTORY 10
89 #define FLAG_ISREG (1 << 0) /*!< Transaction is register request */
90 #define FLAG_DEAD (1 << 1) /*!< Transaction is dead */
91 #define FLAG_FINAL (1 << 2) /*!< Transaction has final message sent */
92 #define FLAG_ISQUAL (1 << 3) /*!< Transaction is a qualification */
93 #define FLAG_ENCRYPT (1 << 4) /*!< Transaction is encrypted wiht ECX/DCX */
94 #define FLAG_SENDFULLKEY (1 << 5) /*!< Send full key on transaction */
95 #define FLAG_STOREHIST (1 << 6) /*!< Record historic performance */
97 #define DUNDI_FLAG_INTERNAL_NOPARTIAL (1 << 17)
99 #if 0
100 #define DUNDI_SECRET_TIME 15 /* Testing only */
101 #else
102 #define DUNDI_SECRET_TIME DUNDI_DEFAULT_CACHE_TIME
103 #endif
105 #define KEY_OUT 0
106 #define KEY_IN 1
108 static struct io_context *io;
109 static struct sched_context *sched;
110 static int netsocket = -1;
111 static pthread_t netthreadid = AST_PTHREADT_NULL;
112 static pthread_t precachethreadid = AST_PTHREADT_NULL;
113 static int tos = 0;
114 static int dundidebug = 0;
115 static int authdebug = 0;
116 static int dundi_ttl = DUNDI_DEFAULT_TTL;
117 static int dundi_key_ttl = DUNDI_DEFAULT_KEY_EXPIRE;
118 static int dundi_cache_time = DUNDI_DEFAULT_CACHE_TIME;
119 static int global_autokilltimeout = 0;
120 static dundi_eid global_eid;
121 static int default_expiration = 60;
122 static int global_storehistory = 0;
123 static char dept[80];
124 static char org[80];
125 static char locality[80];
126 static char stateprov[80];
127 static char country[80];
128 static char email[80];
129 static char phone[80];
130 static char secretpath[80];
131 static char cursecret[80];
132 static char ipaddr[80];
133 static time_t rotatetime;
134 static dundi_eid empty_eid = { { 0, 0, 0, 0, 0, 0 } };
136 struct permission {
137 AST_LIST_ENTRY(permission) list;
138 int allow;
139 char name[0];
142 struct dundi_packet {
143 AST_LIST_ENTRY(dundi_packet) list;
144 struct dundi_hdr *h;
145 int datalen;
146 struct dundi_transaction *parent;
147 int retransid;
148 int retrans;
149 unsigned char data[0];
152 struct dundi_hint_metadata {
153 unsigned short flags;
154 char exten[AST_MAX_EXTENSION];
157 struct dundi_precache_queue {
158 AST_LIST_ENTRY(dundi_precache_queue) list;
159 char *context;
160 time_t expiration;
161 char number[0];
164 struct dundi_request;
166 struct dundi_transaction {
167 struct sockaddr_in addr; /*!< Other end of transaction */
168 struct timeval start; /*!< When this transaction was created */
169 dundi_eid eids[DUNDI_MAX_STACK + 1];
170 int eidcount; /*!< Number of eids in eids */
171 dundi_eid us_eid; /*!< Our EID, to them */
172 dundi_eid them_eid; /*!< Their EID, to us */
173 aes_encrypt_ctx ecx; /*!< AES 128 Encryption context */
174 aes_decrypt_ctx dcx; /*!< AES 128 Decryption context */
175 unsigned int flags; /*!< Has final packet been sent */
176 int ttl; /*!< Remaining TTL for queries on this one */
177 int thread; /*!< We have a calling thread */
178 int retranstimer; /*!< How long to wait before retransmissions */
179 int autokillid; /*!< ID to kill connection if answer doesn't come back fast enough */
180 int autokilltimeout; /*!< Recommended timeout for autokill */
181 unsigned short strans; /*!< Our transaction identifier */
182 unsigned short dtrans; /*!< Their transaction identifer */
183 unsigned char iseqno; /*!< Next expected received seqno */
184 unsigned char oiseqno; /*!< Last received incoming seqno */
185 unsigned char oseqno; /*!< Next transmitted seqno */
186 unsigned char aseqno; /*!< Last acknowledge seqno */
187 AST_LIST_HEAD_NOLOCK(packetlist, dundi_packet) packets; /*!< Packets to be retransmitted */
188 struct packetlist lasttrans; /*!< Last transmitted / ACK'd packet */
189 struct dundi_request *parent; /*!< Parent request (if there is one) */
190 AST_LIST_ENTRY(dundi_transaction) parentlist; /*!< Next with respect to the parent */
191 AST_LIST_ENTRY(dundi_transaction) all; /*!< Next with respect to all DUNDi transactions */
194 struct dundi_request {
195 char dcontext[AST_MAX_EXTENSION];
196 char number[AST_MAX_EXTENSION];
197 dundi_eid query_eid;
198 dundi_eid root_eid;
199 struct dundi_result *dr;
200 struct dundi_entity_info *dei;
201 struct dundi_hint_metadata *hmd;
202 int maxcount;
203 int respcount;
204 int expiration;
205 int cbypass;
206 int pfds[2];
207 unsigned long crc32; /*!< CRC-32 of all but root EID's in avoid list */
208 AST_LIST_HEAD_NOLOCK(, dundi_transaction) trans; /*!< Transactions */
209 AST_LIST_ENTRY(dundi_request) list;
212 struct dundi_mapping {
213 char dcontext[AST_MAX_EXTENSION];
214 char lcontext[AST_MAX_EXTENSION];
215 int weight;
216 int options;
217 int tech;
218 int dead;
219 char dest[AST_MAX_EXTENSION];
220 AST_LIST_ENTRY(dundi_mapping) list;
223 struct dundi_peer {
224 dundi_eid eid;
225 struct sockaddr_in addr; /*!< Address of DUNDi peer */
226 AST_LIST_HEAD_NOLOCK(permissionlist, permission) permit;
227 struct permissionlist include;
228 dundi_eid us_eid;
229 char inkey[80];
230 char outkey[80];
231 int dead;
232 int registerid;
233 int qualifyid;
234 int sentfullkey;
235 int order;
236 unsigned char txenckey[256]; /*!< Transmitted encrypted key + sig */
237 unsigned char rxenckey[256]; /*!< Cache received encrypted key + sig */
238 unsigned long us_keycrc32; /*!< CRC-32 of our key */
239 aes_encrypt_ctx us_ecx; /*!< Cached AES 128 Encryption context */
240 aes_decrypt_ctx us_dcx; /*!< Cached AES 128 Decryption context */
241 unsigned long them_keycrc32; /*!< CRC-32 of our key */
242 aes_encrypt_ctx them_ecx; /*!< Cached AES 128 Encryption context */
243 aes_decrypt_ctx them_dcx; /*!< Cached AES 128 Decryption context */
244 time_t keyexpire; /*!< When to expire/recreate key */
245 int registerexpire;
246 int lookuptimes[DUNDI_TIMING_HISTORY];
247 char *lookups[DUNDI_TIMING_HISTORY];
248 int avgms;
249 struct dundi_transaction *regtrans; /*!< Registration transaction */
250 struct dundi_transaction *qualtrans; /*!< Qualify transaction */
251 int model; /*!< Pull model */
252 int pcmodel; /*!< Push/precache model */
253 int dynamic; /*!< Are we dynamic? */
254 int lastms; /*!< Last measured latency */
255 int maxms; /*!< Max permissible latency */
256 struct timeval qualtx; /*!< Time of transmit */
257 AST_LIST_ENTRY(dundi_peer) list;
260 static AST_LIST_HEAD_STATIC(peers, dundi_peer);
261 static AST_LIST_HEAD_STATIC(pcq, dundi_precache_queue);
262 static AST_LIST_HEAD_NOLOCK_STATIC(mappings, dundi_mapping);
263 static AST_LIST_HEAD_NOLOCK_STATIC(requests, dundi_request);
264 static AST_LIST_HEAD_NOLOCK_STATIC(alltrans, dundi_transaction);
266 static int dundi_xmit(struct dundi_packet *pack);
268 static void dundi_debug_output(const char *data)
270 if (dundidebug)
271 ast_verbose("%s", data);
274 static void dundi_error_output(const char *data)
276 ast_log(LOG_WARNING, "%s", data);
279 static int has_permission(struct permissionlist *permlist, char *cont)
281 struct permission *perm;
282 int res = 0;
284 AST_LIST_TRAVERSE(permlist, perm, list) {
285 if (!strcasecmp(perm->name, "all") || !strcasecmp(perm->name, cont))
286 res = perm->allow;
289 return res;
292 static char *tech2str(int tech)
294 switch(tech) {
295 case DUNDI_PROTO_NONE:
296 return "None";
297 case DUNDI_PROTO_IAX:
298 return "IAX2";
299 case DUNDI_PROTO_SIP:
300 return "SIP";
301 case DUNDI_PROTO_H323:
302 return "H323";
303 default:
304 return "Unknown";
308 static int str2tech(char *str)
310 if (!strcasecmp(str, "IAX") || !strcasecmp(str, "IAX2"))
311 return DUNDI_PROTO_IAX;
312 else if (!strcasecmp(str, "SIP"))
313 return DUNDI_PROTO_SIP;
314 else if (!strcasecmp(str, "H323"))
315 return DUNDI_PROTO_H323;
316 else
317 return -1;
320 static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *md, int *expiration, int cybpass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[]);
321 static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[]);
322 static struct dundi_transaction *create_transaction(struct dundi_peer *p);
323 static struct dundi_transaction *find_transaction(struct dundi_hdr *hdr, struct sockaddr_in *sin)
325 struct dundi_transaction *trans;
327 /* Look for an exact match first */
328 AST_LIST_TRAVERSE(&alltrans, trans, all) {
329 if (!inaddrcmp(&trans->addr, sin) &&
330 ((trans->strans == (ntohs(hdr->dtrans) & 32767)) /* Matches our destination */ ||
331 ((trans->dtrans == (ntohs(hdr->strans) & 32767)) && (!hdr->dtrans))) /* We match their destination */) {
332 if (hdr->strans)
333 trans->dtrans = ntohs(hdr->strans) & 32767;
334 break;
337 if (!trans) {
338 switch(hdr->cmdresp & 0x7f) {
339 case DUNDI_COMMAND_DPDISCOVER:
340 case DUNDI_COMMAND_EIDQUERY:
341 case DUNDI_COMMAND_PRECACHERQ:
342 case DUNDI_COMMAND_REGREQ:
343 case DUNDI_COMMAND_NULL:
344 case DUNDI_COMMAND_ENCRYPT:
345 if (hdr->strans) {
346 /* Create new transaction */
347 trans = create_transaction(NULL);
348 if (trans) {
349 memcpy(&trans->addr, sin, sizeof(trans->addr));
350 trans->dtrans = ntohs(hdr->strans) & 32767;
351 } else
352 ast_log(LOG_WARNING, "Out of memory!\n");
354 break;
355 default:
356 break;
359 return trans;
362 static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied);
364 static int dundi_ack(struct dundi_transaction *trans, int final)
366 return dundi_send(trans, DUNDI_COMMAND_ACK, 0, final, NULL);
368 static void dundi_reject(struct dundi_hdr *h, struct sockaddr_in *sin)
370 struct {
371 struct dundi_packet pack;
372 struct dundi_hdr hdr;
373 } tmp;
374 struct dundi_transaction trans;
375 /* Never respond to an INVALID with another INVALID */
376 if (h->cmdresp == DUNDI_COMMAND_INVALID)
377 return;
378 memset(&tmp, 0, sizeof(tmp));
379 memset(&trans, 0, sizeof(trans));
380 memcpy(&trans.addr, sin, sizeof(trans.addr));
381 tmp.hdr.strans = h->dtrans;
382 tmp.hdr.dtrans = h->strans;
383 tmp.hdr.iseqno = h->oseqno;
384 tmp.hdr.oseqno = h->iseqno;
385 tmp.hdr.cmdresp = DUNDI_COMMAND_INVALID;
386 tmp.hdr.cmdflags = 0;
387 tmp.pack.h = (struct dundi_hdr *)tmp.pack.data;
388 tmp.pack.datalen = sizeof(struct dundi_hdr);
389 tmp.pack.parent = &trans;
390 dundi_xmit(&tmp.pack);
393 static void reset_global_eid(void)
395 #if defined(SIOCGIFHWADDR)
396 int x,s;
397 char eid_str[20];
398 struct ifreq ifr;
400 s = socket(AF_INET, SOCK_STREAM, 0);
401 if (s > 0) {
402 x = 0;
403 for(x=0;x<10;x++) {
404 memset(&ifr, 0, sizeof(ifr));
405 snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth%d", x);
406 if (!ioctl(s, SIOCGIFHWADDR, &ifr)) {
407 memcpy(&global_eid, ((unsigned char *)&ifr.ifr_hwaddr) + 2, sizeof(global_eid));
408 ast_log(LOG_DEBUG, "Seeding global EID '%s' from '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &global_eid), ifr.ifr_name);
409 close(s);
410 return;
413 close(s);
415 #else
416 #if defined(ifa_broadaddr) && !defined(SOLARIS)
417 char eid_str[20];
418 struct ifaddrs *ifap;
420 if (getifaddrs(&ifap) == 0) {
421 struct ifaddrs *p;
422 for (p = ifap; p; p = p->ifa_next) {
423 if (p->ifa_addr->sa_family == AF_LINK) {
424 struct sockaddr_dl* sdp = (struct sockaddr_dl*) p->ifa_addr;
425 memcpy(
426 &(global_eid.eid),
427 sdp->sdl_data + sdp->sdl_nlen, 6);
428 ast_log(LOG_DEBUG, "Seeding global EID '%s' from '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &global_eid), ifap->ifa_name);
429 freeifaddrs(ifap);
430 return;
433 freeifaddrs(ifap);
435 #endif
436 #endif
437 ast_log(LOG_NOTICE, "No ethernet interface found for seeding global EID You will have to set it manually.\n");
440 static int get_trans_id(void)
442 struct dundi_transaction *t;
443 int stid = (ast_random() % 32766) + 1;
444 int tid = stid;
446 do {
447 AST_LIST_TRAVERSE(&alltrans, t, all) {
448 if (t->strans == tid)
449 break;
451 if (!t)
452 return tid;
453 tid = (tid % 32766) + 1;
454 } while (tid != stid);
456 return 0;
459 static int reset_transaction(struct dundi_transaction *trans)
461 int tid;
462 tid = get_trans_id();
463 if (tid < 1)
464 return -1;
465 trans->strans = tid;
466 trans->dtrans = 0;
467 trans->iseqno = 0;
468 trans->oiseqno = 0;
469 trans->oseqno = 0;
470 trans->aseqno = 0;
471 ast_clear_flag(trans, FLAG_FINAL);
472 return 0;
475 static struct dundi_peer *find_peer(dundi_eid *eid)
477 struct dundi_peer *cur = NULL;
479 if (!eid)
480 eid = &empty_eid;
482 AST_LIST_TRAVERSE(&peers, cur, list) {
483 if (!dundi_eid_cmp(&cur->eid,eid))
484 break;
487 return cur;
490 static void build_iv(unsigned char *iv)
492 /* XXX Would be nice to be more random XXX */
493 unsigned int *fluffy;
494 int x;
495 fluffy = (unsigned int *)(iv);
496 for (x=0;x<4;x++)
497 fluffy[x] = ast_random();
500 struct dundi_query_state {
501 dundi_eid *eids[DUNDI_MAX_STACK + 1];
502 int directs[DUNDI_MAX_STACK + 1];
503 dundi_eid reqeid;
504 char called_context[AST_MAX_EXTENSION];
505 char called_number[AST_MAX_EXTENSION];
506 struct dundi_mapping *maps;
507 int nummaps;
508 int nocache;
509 struct dundi_transaction *trans;
510 void *chal;
511 int challen;
512 int ttl;
513 char fluffy[0];
516 static int dundi_lookup_local(struct dundi_result *dr, struct dundi_mapping *map, char *called_number, dundi_eid *us_eid, int anscnt, struct dundi_hint_metadata *hmd)
518 struct ast_flags flags = {0};
519 int x;
520 if (!ast_strlen_zero(map->lcontext)) {
521 if (ast_exists_extension(NULL, map->lcontext, called_number, 1, NULL))
522 ast_set_flag(&flags, DUNDI_FLAG_EXISTS);
523 if (ast_canmatch_extension(NULL, map->lcontext, called_number, 1, NULL))
524 ast_set_flag(&flags, DUNDI_FLAG_CANMATCH);
525 if (ast_matchmore_extension(NULL, map->lcontext, called_number, 1, NULL))
526 ast_set_flag(&flags, DUNDI_FLAG_MATCHMORE);
527 if (ast_ignore_pattern(map->lcontext, called_number))
528 ast_set_flag(&flags, DUNDI_FLAG_IGNOREPAT);
530 /* Clearly we can't say 'don't ask' anymore if we found anything... */
531 if (ast_test_flag(&flags, AST_FLAGS_ALL))
532 ast_clear_flag_nonstd(hmd, DUNDI_HINT_DONT_ASK);
534 if (map->options & DUNDI_FLAG_INTERNAL_NOPARTIAL) {
535 /* Skip partial answers */
536 ast_clear_flag(&flags, DUNDI_FLAG_MATCHMORE|DUNDI_FLAG_CANMATCH);
538 if (ast_test_flag(&flags, AST_FLAGS_ALL)) {
539 struct varshead headp;
540 struct ast_var_t *newvariable;
541 ast_set_flag(&flags, map->options & 0xffff);
542 ast_copy_flags(dr + anscnt, &flags, AST_FLAGS_ALL);
543 dr[anscnt].techint = map->tech;
544 dr[anscnt].weight = map->weight;
545 dr[anscnt].expiration = dundi_cache_time;
546 ast_copy_string(dr[anscnt].tech, tech2str(map->tech), sizeof(dr[anscnt].tech));
547 dr[anscnt].eid = *us_eid;
548 dundi_eid_to_str(dr[anscnt].eid_str, sizeof(dr[anscnt].eid_str), &dr[anscnt].eid);
549 if (ast_test_flag(&flags, DUNDI_FLAG_EXISTS)) {
550 AST_LIST_HEAD_INIT_NOLOCK(&headp);
551 newvariable = ast_var_assign("NUMBER", called_number);
552 AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
553 newvariable = ast_var_assign("EID", dr[anscnt].eid_str);
554 AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
555 newvariable = ast_var_assign("SECRET", cursecret);
556 AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
557 newvariable = ast_var_assign("IPADDR", ipaddr);
558 AST_LIST_INSERT_HEAD(&headp, newvariable, entries);
559 pbx_substitute_variables_varshead(&headp, map->dest, dr[anscnt].dest, sizeof(dr[anscnt].dest));
560 while ((newvariable = AST_LIST_REMOVE_HEAD(&headp, entries)))
561 ast_var_delete(newvariable);
562 } else
563 dr[anscnt].dest[0] = '\0';
564 anscnt++;
565 } else {
566 /* No answers... Find the fewest number of digits from the
567 number for which we have no answer. */
568 char tmp[AST_MAX_EXTENSION];
569 for (x=0;x<AST_MAX_EXTENSION;x++) {
570 tmp[x] = called_number[x];
571 if (!tmp[x])
572 break;
573 if (!ast_canmatch_extension(NULL, map->lcontext, tmp, 1, NULL)) {
574 /* Oops found something we can't match. If this is longer
575 than the running hint, we have to consider it */
576 if (strlen(tmp) > strlen(hmd->exten)) {
577 ast_copy_string(hmd->exten, tmp, sizeof(hmd->exten));
579 break;
584 return anscnt;
587 static void destroy_trans(struct dundi_transaction *trans, int fromtimeout);
589 static void *dundi_lookup_thread(void *data)
591 struct dundi_query_state *st = data;
592 struct dundi_result dr[MAX_RESULTS];
593 struct dundi_ie_data ied;
594 struct dundi_hint_metadata hmd;
595 char eid_str[20];
596 int res, x;
597 int ouranswers=0;
598 int max = 999999;
599 int expiration = dundi_cache_time;
601 ast_log(LOG_DEBUG, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
602 st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) : "ourselves");
603 memset(&ied, 0, sizeof(ied));
604 memset(&dr, 0, sizeof(dr));
605 memset(&hmd, 0, sizeof(hmd));
606 /* Assume 'don't ask for anything' and 'unaffected', no TTL expired */
607 hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
608 for (x=0;x<st->nummaps;x++)
609 ouranswers = dundi_lookup_local(dr, st->maps + x, st->called_number, &st->trans->us_eid, ouranswers, &hmd);
610 if (ouranswers < 0)
611 ouranswers = 0;
612 for (x=0;x<ouranswers;x++) {
613 if (dr[x].weight < max)
614 max = dr[x].weight;
617 if (max) {
618 /* If we do not have a canonical result, keep looking */
619 res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, st->called_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->nocache, 0, NULL, st->eids, st->directs);
620 if (res > 0) {
621 /* Append answer in result */
622 ouranswers += res;
623 } else {
624 if ((res < -1) && (!ouranswers))
625 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_DUPLICATE, "Duplicate Request Pending");
628 AST_LIST_LOCK(&peers);
629 /* Truncate if "don't ask" isn't present */
630 if (!ast_test_flag_nonstd(&hmd, DUNDI_HINT_DONT_ASK))
631 hmd.exten[0] = '\0';
632 if (ast_test_flag(st->trans, FLAG_DEAD)) {
633 ast_log(LOG_DEBUG, "Our transaction went away!\n");
634 st->trans->thread = 0;
635 destroy_trans(st->trans, 0);
636 } else {
637 for (x=0;x<ouranswers;x++) {
638 /* Add answers */
639 if (dr[x].expiration && (expiration > dr[x].expiration))
640 expiration = dr[x].expiration;
641 dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
643 dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
644 dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
645 dundi_send(st->trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
646 st->trans->thread = 0;
648 AST_LIST_UNLOCK(&peers);
649 free(st);
650 return NULL;
653 static void *dundi_precache_thread(void *data)
655 struct dundi_query_state *st = data;
656 struct dundi_ie_data ied;
657 struct dundi_hint_metadata hmd;
658 char eid_str[20];
660 ast_log(LOG_DEBUG, "Whee, precaching '%s@%s' for '%s'\n", st->called_number, st->called_context,
661 st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) : "ourselves");
662 memset(&ied, 0, sizeof(ied));
664 /* Now produce precache */
665 dundi_precache_internal(st->called_context, st->called_number, st->ttl, st->eids);
667 AST_LIST_LOCK(&peers);
668 /* Truncate if "don't ask" isn't present */
669 if (!ast_test_flag_nonstd(&hmd, DUNDI_HINT_DONT_ASK))
670 hmd.exten[0] = '\0';
671 if (ast_test_flag(st->trans, FLAG_DEAD)) {
672 ast_log(LOG_DEBUG, "Our transaction went away!\n");
673 st->trans->thread = 0;
674 destroy_trans(st->trans, 0);
675 } else {
676 dundi_send(st->trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
677 st->trans->thread = 0;
679 AST_LIST_UNLOCK(&peers);
680 free(st);
681 return NULL;
684 static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *dcontext, dundi_eid *eid, struct dundi_hint_metadata *hmd, int ttl, int blockempty, dundi_eid *avoid[]);
686 static void *dundi_query_thread(void *data)
688 struct dundi_query_state *st = data;
689 struct dundi_entity_info dei;
690 struct dundi_ie_data ied;
691 struct dundi_hint_metadata hmd;
692 char eid_str[20];
693 int res;
694 ast_log(LOG_DEBUG, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
695 st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) : "ourselves");
696 memset(&ied, 0, sizeof(ied));
697 memset(&dei, 0, sizeof(dei));
698 memset(&hmd, 0, sizeof(hmd));
699 if (!dundi_eid_cmp(&st->trans->us_eid, &st->reqeid)) {
700 /* Ooh, it's us! */
701 ast_log(LOG_DEBUG, "Neat, someone look for us!\n");
702 ast_copy_string(dei.orgunit, dept, sizeof(dei.orgunit));
703 ast_copy_string(dei.org, org, sizeof(dei.org));
704 ast_copy_string(dei.locality, locality, sizeof(dei.locality));
705 ast_copy_string(dei.stateprov, stateprov, sizeof(dei.stateprov));
706 ast_copy_string(dei.country, country, sizeof(dei.country));
707 ast_copy_string(dei.email, email, sizeof(dei.email));
708 ast_copy_string(dei.phone, phone, sizeof(dei.phone));
709 res = 1;
710 } else {
711 /* If we do not have a canonical result, keep looking */
712 res = dundi_query_eid_internal(&dei, st->called_context, &st->reqeid, &hmd, st->ttl, 1, st->eids);
714 AST_LIST_LOCK(&peers);
715 if (ast_test_flag(st->trans, FLAG_DEAD)) {
716 ast_log(LOG_DEBUG, "Our transaction went away!\n");
717 st->trans->thread = 0;
718 destroy_trans(st->trans, 0);
719 } else {
720 if (res) {
721 dundi_ie_append_str(&ied, DUNDI_IE_DEPARTMENT, dei.orgunit);
722 dundi_ie_append_str(&ied, DUNDI_IE_ORGANIZATION, dei.org);
723 dundi_ie_append_str(&ied, DUNDI_IE_LOCALITY, dei.locality);
724 dundi_ie_append_str(&ied, DUNDI_IE_STATE_PROV, dei.stateprov);
725 dundi_ie_append_str(&ied, DUNDI_IE_COUNTRY, dei.country);
726 dundi_ie_append_str(&ied, DUNDI_IE_EMAIL, dei.email);
727 dundi_ie_append_str(&ied, DUNDI_IE_PHONE, dei.phone);
728 if (!ast_strlen_zero(dei.ipaddr))
729 dundi_ie_append_str(&ied, DUNDI_IE_IPADDR, dei.ipaddr);
731 dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
732 dundi_send(st->trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
733 st->trans->thread = 0;
735 AST_LIST_UNLOCK(&peers);
736 free(st);
737 return NULL;
740 static int dundi_answer_entity(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
742 struct dundi_query_state *st;
743 int totallen;
744 int x;
745 int skipfirst=0;
746 struct dundi_ie_data ied;
747 char eid_str[20];
748 char *s;
749 pthread_t lookupthread;
750 pthread_attr_t attr;
751 if (ies->eidcount > 1) {
752 /* Since it is a requirement that the first EID is the authenticating host
753 and the last EID is the root, it is permissible that the first and last EID
754 could be the same. In that case, we should go ahead copy only the "root" section
755 since we will not need it for authentication. */
756 if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
757 skipfirst = 1;
759 totallen = sizeof(struct dundi_query_state);
760 totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
761 st = ast_calloc(1, totallen);
762 if (st) {
763 ast_copy_string(st->called_context, ies->called_context, sizeof(st->called_context));
764 memcpy(&st->reqeid, ies->reqeid, sizeof(st->reqeid));
765 st->trans = trans;
766 st->ttl = ies->ttl - 1;
767 if (st->ttl < 0)
768 st->ttl = 0;
769 s = st->fluffy;
770 for (x=skipfirst;ies->eids[x];x++) {
771 st->eids[x-skipfirst] = (dundi_eid *)s;
772 *st->eids[x-skipfirst] = *ies->eids[x];
773 s += sizeof(dundi_eid);
775 ast_log(LOG_DEBUG, "Answering EID query for '%s@%s'!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), ies->reqeid), ies->called_context);
776 pthread_attr_init(&attr);
777 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
778 trans->thread = 1;
779 if (ast_pthread_create(&lookupthread, &attr, dundi_query_thread, st)) {
780 trans->thread = 0;
781 ast_log(LOG_WARNING, "Unable to create thread!\n");
782 free(st);
783 memset(&ied, 0, sizeof(ied));
784 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
785 dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
786 pthread_attr_destroy(&attr);
787 return -1;
789 pthread_attr_destroy(&attr);
790 } else {
791 ast_log(LOG_WARNING, "Out of memory!\n");
792 memset(&ied, 0, sizeof(ied));
793 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
794 dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
795 return -1;
797 return 0;
800 static int cache_save_hint(dundi_eid *eidpeer, struct dundi_request *req, struct dundi_hint *hint, int expiration)
802 int unaffected;
803 char key1[256];
804 char key2[256];
805 char eidpeer_str[20];
806 char eidroot_str[20];
807 char data[80];
808 time_t timeout;
810 if (expiration < 0)
811 expiration = dundi_cache_time;
813 /* Only cache hint if "don't ask" is there... */
814 if (!ast_test_flag_nonstd(hint, htons(DUNDI_HINT_DONT_ASK)))
815 return 0;
817 unaffected = ast_test_flag_nonstd(hint, htons(DUNDI_HINT_UNAFFECTED));
819 dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
820 dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
821 snprintf(key1, sizeof(key1), "hint/%s/%s/%s/e%08lx", eidpeer_str, hint->data, req->dcontext, unaffected ? 0 : req->crc32);
822 snprintf(key2, sizeof(key2), "hint/%s/%s/%s/r%s", eidpeer_str, hint->data, req->dcontext, eidroot_str);
824 time(&timeout);
825 timeout += expiration;
826 snprintf(data, sizeof(data), "%ld|", (long)(timeout));
828 ast_db_put("dundi/cache", key1, data);
829 ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key1);
830 ast_db_put("dundi/cache", key2, data);
831 ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key2);
832 return 0;
835 static int cache_save(dundi_eid *eidpeer, struct dundi_request *req, int start, int unaffected, int expiration, int push)
837 int x;
838 char key1[256];
839 char key2[256];
840 char data[1024];
841 char eidpeer_str[20];
842 char eidroot_str[20];
843 time_t timeout;
845 if (expiration < 1)
846 expiration = dundi_cache_time;
848 /* Keep pushes a little longer, cut pulls a little short */
849 if (push)
850 expiration += 10;
851 else
852 expiration -= 10;
853 if (expiration < 1)
854 expiration = 1;
855 dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
856 dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
857 snprintf(key1, sizeof(key1), "%s/%s/%s/e%08lx", eidpeer_str, req->number, req->dcontext, unaffected ? 0 : req->crc32);
858 snprintf(key2, sizeof(key2), "%s/%s/%s/r%s", eidpeer_str, req->number, req->dcontext, eidroot_str);
859 /* Build request string */
860 time(&timeout);
861 timeout += expiration;
862 snprintf(data, sizeof(data), "%ld|", (long)(timeout));
863 for (x=start;x<req->respcount;x++) {
864 /* Skip anything with an illegal pipe in it */
865 if (strchr(req->dr[x].dest, '|'))
866 continue;
867 snprintf(data + strlen(data), sizeof(data) - strlen(data), "%d/%d/%d/%s/%s|",
868 req->dr[x].flags, req->dr[x].weight, req->dr[x].techint, req->dr[x].dest,
869 dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), &req->dr[x].eid));
871 ast_db_put("dundi/cache", key1, data);
872 ast_db_put("dundi/cache", key2, data);
873 return 0;
876 static int dundi_prop_precache(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
878 struct dundi_query_state *st;
879 int totallen;
880 int x,z;
881 struct dundi_ie_data ied;
882 char *s;
883 struct dundi_result dr2[MAX_RESULTS];
884 struct dundi_request dr;
885 struct dundi_hint_metadata hmd;
887 struct dundi_mapping *cur;
888 int mapcount;
889 int skipfirst = 0;
891 pthread_t lookupthread;
892 pthread_attr_t attr;
894 memset(&dr2, 0, sizeof(dr2));
895 memset(&dr, 0, sizeof(dr));
896 memset(&hmd, 0, sizeof(hmd));
898 /* Forge request structure to hold answers for cache */
899 hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
900 dr.dr = dr2;
901 dr.maxcount = MAX_RESULTS;
902 dr.expiration = dundi_cache_time;
903 dr.hmd = &hmd;
904 dr.pfds[0] = dr.pfds[1] = -1;
905 trans->parent = &dr;
906 ast_copy_string(dr.dcontext, ies->called_context ? ies->called_context : "e164", sizeof(dr.dcontext));
907 ast_copy_string(dr.number, ies->called_number, sizeof(dr.number));
909 for (x=0;x<ies->anscount;x++) {
910 if (trans->parent->respcount < trans->parent->maxcount) {
911 /* Make sure it's not already there */
912 for (z=0;z<trans->parent->respcount;z++) {
913 if ((trans->parent->dr[z].techint == ies->answers[x]->protocol) &&
914 !strcmp(trans->parent->dr[z].dest, (char *)ies->answers[x]->data))
915 break;
917 if (z == trans->parent->respcount) {
918 /* Copy into parent responses */
919 trans->parent->dr[trans->parent->respcount].flags = ntohs(ies->answers[x]->flags);
920 trans->parent->dr[trans->parent->respcount].techint = ies->answers[x]->protocol;
921 trans->parent->dr[trans->parent->respcount].weight = ntohs(ies->answers[x]->weight);
922 trans->parent->dr[trans->parent->respcount].eid = ies->answers[x]->eid;
923 if (ies->expiration > 0)
924 trans->parent->dr[trans->parent->respcount].expiration = ies->expiration;
925 else
926 trans->parent->dr[trans->parent->respcount].expiration = dundi_cache_time;
927 dundi_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str,
928 sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
929 &ies->answers[x]->eid);
930 ast_copy_string(trans->parent->dr[trans->parent->respcount].dest, (char *)ies->answers[x]->data,
931 sizeof(trans->parent->dr[trans->parent->respcount].dest));
932 ast_copy_string(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies->answers[x]->protocol),
933 sizeof(trans->parent->dr[trans->parent->respcount].tech));
934 trans->parent->respcount++;
935 ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
936 } else if (trans->parent->dr[z].weight > ies->answers[x]->weight) {
937 /* Update weight if appropriate */
938 trans->parent->dr[z].weight = ies->answers[x]->weight;
940 } else
941 ast_log(LOG_NOTICE, "Dropping excessive answers in precache for %s@%s\n",
942 trans->parent->number, trans->parent->dcontext);
945 /* Save all the results (if any) we had. Even if no results, still cache lookup. */
946 cache_save(&trans->them_eid, trans->parent, 0, 0, ies->expiration, 1);
947 if (ies->hint)
948 cache_save_hint(&trans->them_eid, trans->parent, ies->hint, ies->expiration);
950 totallen = sizeof(struct dundi_query_state);
951 /* Count matching map entries */
952 mapcount = 0;
953 AST_LIST_TRAVERSE(&mappings, cur, list) {
954 if (!strcasecmp(cur->dcontext, ccontext))
955 mapcount++;
958 /* If no maps, return -1 immediately */
959 if (!mapcount)
960 return -1;
962 if (ies->eidcount > 1) {
963 /* Since it is a requirement that the first EID is the authenticating host
964 and the last EID is the root, it is permissible that the first and last EID
965 could be the same. In that case, we should go ahead copy only the "root" section
966 since we will not need it for authentication. */
967 if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
968 skipfirst = 1;
971 /* Prepare to run a query and then propagate that as necessary */
972 totallen += mapcount * sizeof(struct dundi_mapping);
973 totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
974 st = ast_calloc(1, totallen);
975 if (st) {
976 ast_copy_string(st->called_context, ies->called_context, sizeof(st->called_context));
977 ast_copy_string(st->called_number, ies->called_number, sizeof(st->called_number));
978 st->trans = trans;
979 st->ttl = ies->ttl - 1;
980 st->nocache = ies->cbypass;
981 if (st->ttl < 0)
982 st->ttl = 0;
983 s = st->fluffy;
984 for (x=skipfirst;ies->eids[x];x++) {
985 st->eids[x-skipfirst] = (dundi_eid *)s;
986 *st->eids[x-skipfirst] = *ies->eids[x];
987 st->directs[x-skipfirst] = ies->eid_direct[x];
988 s += sizeof(dundi_eid);
990 /* Append mappings */
991 x = 0;
992 st->maps = (struct dundi_mapping *)s;
993 AST_LIST_TRAVERSE(&mappings, cur, list) {
994 if (!strcasecmp(cur->dcontext, ccontext)) {
995 if (x < mapcount) {
996 st->maps[x] = *cur;
997 st->maps[x].list.next = NULL;
998 x++;
1002 st->nummaps = mapcount;
1003 ast_log(LOG_DEBUG, "Forwarding precache for '%s@%s'!\n", ies->called_number, ies->called_context);
1004 pthread_attr_init(&attr);
1005 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1006 trans->thread = 1;
1007 if (ast_pthread_create(&lookupthread, &attr, dundi_precache_thread, st)) {
1008 trans->thread = 0;
1009 ast_log(LOG_WARNING, "Unable to create thread!\n");
1010 free(st);
1011 memset(&ied, 0, sizeof(ied));
1012 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
1013 dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
1014 pthread_attr_destroy(&attr);
1015 return -1;
1017 pthread_attr_destroy(&attr);
1018 } else {
1019 ast_log(LOG_WARNING, "Out of memory!\n");
1020 memset(&ied, 0, sizeof(ied));
1021 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
1022 dundi_send(trans, DUNDI_COMMAND_PRECACHERP, 0, 1, &ied);
1023 return -1;
1025 return 0;
1028 static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
1030 struct dundi_query_state *st;
1031 int totallen;
1032 int x;
1033 struct dundi_ie_data ied;
1034 char *s;
1035 struct dundi_mapping *cur;
1036 int mapcount = 0;
1037 int skipfirst = 0;
1039 pthread_t lookupthread;
1040 pthread_attr_t attr;
1041 totallen = sizeof(struct dundi_query_state);
1042 /* Count matching map entries */
1043 AST_LIST_TRAVERSE(&mappings, cur, list) {
1044 if (!strcasecmp(cur->dcontext, ccontext))
1045 mapcount++;
1047 /* If no maps, return -1 immediately */
1048 if (!mapcount)
1049 return -1;
1051 if (ies->eidcount > 1) {
1052 /* Since it is a requirement that the first EID is the authenticating host
1053 and the last EID is the root, it is permissible that the first and last EID
1054 could be the same. In that case, we should go ahead copy only the "root" section
1055 since we will not need it for authentication. */
1056 if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
1057 skipfirst = 1;
1060 totallen += mapcount * sizeof(struct dundi_mapping);
1061 totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
1062 st = ast_calloc(1, totallen);
1063 if (st) {
1064 ast_copy_string(st->called_context, ies->called_context, sizeof(st->called_context));
1065 ast_copy_string(st->called_number, ies->called_number, sizeof(st->called_number));
1066 st->trans = trans;
1067 st->ttl = ies->ttl - 1;
1068 st->nocache = ies->cbypass;
1069 if (st->ttl < 0)
1070 st->ttl = 0;
1071 s = st->fluffy;
1072 for (x=skipfirst;ies->eids[x];x++) {
1073 st->eids[x-skipfirst] = (dundi_eid *)s;
1074 *st->eids[x-skipfirst] = *ies->eids[x];
1075 st->directs[x-skipfirst] = ies->eid_direct[x];
1076 s += sizeof(dundi_eid);
1078 /* Append mappings */
1079 x = 0;
1080 st->maps = (struct dundi_mapping *)s;
1081 AST_LIST_TRAVERSE(&mappings, cur, list) {
1082 if (!strcasecmp(cur->dcontext, ccontext)) {
1083 if (x < mapcount) {
1084 st->maps[x] = *cur;
1085 st->maps[x].list.next = NULL;
1086 x++;
1090 st->nummaps = mapcount;
1091 ast_log(LOG_DEBUG, "Answering query for '%s@%s'!\n", ies->called_number, ies->called_context);
1092 pthread_attr_init(&attr);
1093 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
1094 trans->thread = 1;
1095 if (ast_pthread_create(&lookupthread, &attr, dundi_lookup_thread, st)) {
1096 trans->thread = 0;
1097 ast_log(LOG_WARNING, "Unable to create thread!\n");
1098 free(st);
1099 memset(&ied, 0, sizeof(ied));
1100 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
1101 dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
1102 pthread_attr_destroy(&attr);
1103 return -1;
1105 pthread_attr_destroy(&attr);
1106 } else {
1107 ast_log(LOG_WARNING, "Out of memory!\n");
1108 memset(&ied, 0, sizeof(ied));
1109 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
1110 dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
1111 return -1;
1113 return 0;
1116 static int cache_lookup_internal(time_t now, struct dundi_request *req, char *key, char *eid_str_full, int *lowexpiration)
1118 char data[1024];
1119 char *ptr, *term, *src;
1120 int tech;
1121 struct ast_flags flags;
1122 int weight;
1123 int length;
1124 int z;
1125 char fs[256];
1127 /* Build request string */
1128 if (!ast_db_get("dundi/cache", key, data, sizeof(data))) {
1129 time_t timeout;
1130 ptr = data;
1131 if (!ast_get_time_t(ptr, &timeout, 0, &length)) {
1132 int expiration = timeout - now;
1133 if (expiration > 0) {
1134 ast_log(LOG_DEBUG, "Found cache expiring in %d seconds!\n", expiration);
1135 ptr += length + 1;
1136 while((sscanf(ptr, "%d/%d/%d/%n", &(flags.flags), &weight, &tech, &length) == 3)) {
1137 ptr += length;
1138 term = strchr(ptr, '|');
1139 if (term) {
1140 *term = '\0';
1141 src = strrchr(ptr, '/');
1142 if (src) {
1143 *src = '\0';
1144 src++;
1145 } else
1146 src = "";
1147 ast_log(LOG_DEBUG, "Found cached answer '%s/%s' originally from '%s' with flags '%s' on behalf of '%s'\n",
1148 tech2str(tech), ptr, src, dundi_flags2str(fs, sizeof(fs), flags.flags), eid_str_full);
1149 /* Make sure it's not already there */
1150 for (z=0;z<req->respcount;z++) {
1151 if ((req->dr[z].techint == tech) &&
1152 !strcmp(req->dr[z].dest, ptr))
1153 break;
1155 if (z == req->respcount) {
1156 /* Copy into parent responses */
1157 ast_copy_flags(&(req->dr[req->respcount]), &flags, AST_FLAGS_ALL);
1158 req->dr[req->respcount].weight = weight;
1159 req->dr[req->respcount].techint = tech;
1160 req->dr[req->respcount].expiration = expiration;
1161 dundi_str_short_to_eid(&req->dr[req->respcount].eid, src);
1162 dundi_eid_to_str(req->dr[req->respcount].eid_str,
1163 sizeof(req->dr[req->respcount].eid_str), &req->dr[req->respcount].eid);
1164 ast_copy_string(req->dr[req->respcount].dest, ptr,
1165 sizeof(req->dr[req->respcount].dest));
1166 ast_copy_string(req->dr[req->respcount].tech, tech2str(tech),
1167 sizeof(req->dr[req->respcount].tech));
1168 req->respcount++;
1169 ast_clear_flag_nonstd(req->hmd, DUNDI_HINT_DONT_ASK);
1170 } else if (req->dr[z].weight > weight)
1171 req->dr[z].weight = weight;
1172 ptr = term + 1;
1175 /* We found *something* cached */
1176 if (expiration < *lowexpiration)
1177 *lowexpiration = expiration;
1178 return 1;
1179 } else
1180 ast_db_del("dundi/cache", key);
1181 } else
1182 ast_db_del("dundi/cache", key);
1185 return 0;
1188 static int cache_lookup(struct dundi_request *req, dundi_eid *peer_eid, unsigned long crc32, int *lowexpiration)
1190 char key[256];
1191 char eid_str[20];
1192 char eidroot_str[20];
1193 time_t now;
1194 int res=0;
1195 int res2=0;
1196 char eid_str_full[20];
1197 char tmp[256]="";
1198 int x;
1200 time(&now);
1201 dundi_eid_to_str_short(eid_str, sizeof(eid_str), peer_eid);
1202 dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
1203 dundi_eid_to_str(eid_str_full, sizeof(eid_str_full), peer_eid);
1204 snprintf(key, sizeof(key), "%s/%s/%s/e%08lx", eid_str, req->number, req->dcontext, crc32);
1205 res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1206 snprintf(key, sizeof(key), "%s/%s/%s/e%08lx", eid_str, req->number, req->dcontext, 0L);
1207 res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1208 snprintf(key, sizeof(key), "%s/%s/%s/r%s", eid_str, req->number, req->dcontext, eidroot_str);
1209 res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1210 x = 0;
1211 if (!req->respcount) {
1212 while(!res2) {
1213 /* Look and see if we have a hint that would preclude us from looking at this
1214 peer for this number. */
1215 if (!(tmp[x] = req->number[x]))
1216 break;
1217 x++;
1218 /* Check for hints */
1219 snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08lx", eid_str, tmp, req->dcontext, crc32);
1220 res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1221 snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08lx", eid_str, tmp, req->dcontext, 0L);
1222 res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1223 snprintf(key, sizeof(key), "hint/%s/%s/%s/r%s", eid_str, tmp, req->dcontext, eidroot_str);
1224 res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
1225 if (res2) {
1226 if (strlen(tmp) > strlen(req->hmd->exten)) {
1227 /* Update meta data if appropriate */
1228 ast_copy_string(req->hmd->exten, tmp, sizeof(req->hmd->exten));
1232 res |= res2;
1235 return res;
1238 static void qualify_peer(struct dundi_peer *peer, int schedonly);
1240 static void apply_peer(struct dundi_transaction *trans, struct dundi_peer *p)
1242 if (!trans->addr.sin_addr.s_addr)
1243 memcpy(&trans->addr, &p->addr, sizeof(trans->addr));
1244 trans->us_eid = p->us_eid;
1245 trans->them_eid = p->eid;
1246 /* Enable encryption if appropriate */
1247 if (!ast_strlen_zero(p->inkey))
1248 ast_set_flag(trans, FLAG_ENCRYPT);
1249 if (p->maxms) {
1250 trans->autokilltimeout = p->maxms;
1251 trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
1252 if (p->lastms > 1) {
1253 trans->retranstimer = p->lastms * 2;
1254 /* Keep it from being silly */
1255 if (trans->retranstimer < 150)
1256 trans->retranstimer = 150;
1258 if (trans->retranstimer > DUNDI_DEFAULT_RETRANS_TIMER)
1259 trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
1260 } else
1261 trans->autokilltimeout = global_autokilltimeout;
1264 /*! \note Called with the peers list already locked */
1265 static int do_register_expire(void *data)
1267 struct dundi_peer *peer = data;
1268 char eid_str[20];
1269 ast_log(LOG_DEBUG, "Register expired for '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
1270 peer->registerexpire = -1;
1271 peer->lastms = 0;
1272 memset(&peer->addr, 0, sizeof(peer->addr));
1273 return 0;
1276 static int update_key(struct dundi_peer *peer)
1278 unsigned char key[16];
1279 struct ast_key *ekey, *skey;
1280 char eid_str[20];
1281 int res;
1282 if (!peer->keyexpire || (peer->keyexpire < time(NULL))) {
1283 build_iv(key);
1284 aes_encrypt_key128(key, &peer->us_ecx);
1285 aes_decrypt_key128(key, &peer->us_dcx);
1286 ekey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
1287 if (!ekey) {
1288 ast_log(LOG_NOTICE, "No such key '%s' for creating RSA encrypted shared key for '%s'!\n",
1289 peer->inkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
1290 return -1;
1292 skey = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
1293 if (!skey) {
1294 ast_log(LOG_NOTICE, "No such key '%s' for signing RSA encrypted shared key for '%s'!\n",
1295 peer->outkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
1296 return -1;
1298 if ((res = ast_encrypt_bin(peer->txenckey, key, sizeof(key), ekey)) != 128) {
1299 ast_log(LOG_NOTICE, "Whoa, got a weird encrypt size (%d != %d)!\n", res, 128);
1300 return -1;
1302 if ((res = ast_sign_bin(skey, (char *)peer->txenckey, 128, peer->txenckey + 128))) {
1303 ast_log(LOG_NOTICE, "Failed to sign key (%d)!\n", res);
1304 return -1;
1306 peer->us_keycrc32 = crc32(0L, peer->txenckey, 128);
1307 peer->sentfullkey = 0;
1308 /* Looks good */
1309 time(&peer->keyexpire);
1310 peer->keyexpire += dundi_key_ttl;
1312 return 0;
1315 static int encrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, aes_encrypt_ctx *ecx)
1317 unsigned char curblock[16];
1318 int x;
1319 memcpy(curblock, iv, sizeof(curblock));
1320 while(len > 0) {
1321 for (x=0;x<16;x++)
1322 curblock[x] ^= src[x];
1323 aes_encrypt(curblock, dst, ecx);
1324 memcpy(curblock, dst, sizeof(curblock));
1325 dst += 16;
1326 src += 16;
1327 len -= 16;
1329 return 0;
1331 static int decrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, aes_decrypt_ctx *dcx)
1333 unsigned char lastblock[16];
1334 int x;
1335 memcpy(lastblock, iv, sizeof(lastblock));
1336 while(len > 0) {
1337 aes_decrypt(src, dst, dcx);
1338 for (x=0;x<16;x++)
1339 dst[x] ^= lastblock[x];
1340 memcpy(lastblock, src, sizeof(lastblock));
1341 dst += 16;
1342 src += 16;
1343 len -= 16;
1345 return 0;
1348 static struct dundi_hdr *dundi_decrypt(struct dundi_transaction *trans, unsigned char *dst, int *dstlen, struct dundi_hdr *ohdr, struct dundi_encblock *src, int srclen)
1350 int space = *dstlen;
1351 unsigned long bytes;
1352 struct dundi_hdr *h;
1353 unsigned char *decrypt_space;
1354 decrypt_space = alloca(srclen);
1355 if (!decrypt_space)
1356 return NULL;
1357 decrypt_memcpy(decrypt_space, src->encdata, srclen, src->iv, &trans->dcx);
1358 /* Setup header */
1359 h = (struct dundi_hdr *)dst;
1360 *h = *ohdr;
1361 bytes = space - 6;
1362 if (uncompress(dst + 6, &bytes, decrypt_space, srclen) != Z_OK) {
1363 ast_log(LOG_DEBUG, "Ouch, uncompress failed :(\n");
1364 return NULL;
1366 /* Update length */
1367 *dstlen = bytes + 6;
1368 /* Return new header */
1369 return h;
1372 static int dundi_encrypt(struct dundi_transaction *trans, struct dundi_packet *pack)
1374 unsigned char *compress_space;
1375 int len;
1376 int res;
1377 unsigned long bytes;
1378 struct dundi_ie_data ied;
1379 struct dundi_peer *peer;
1380 unsigned char iv[16];
1381 len = pack->datalen + pack->datalen / 100 + 42;
1382 compress_space = alloca(len);
1383 if (compress_space) {
1384 memset(compress_space, 0, len);
1385 /* We care about everthing save the first 6 bytes of header */
1386 bytes = len;
1387 res = compress(compress_space, &bytes, pack->data + 6, pack->datalen - 6);
1388 if (res != Z_OK) {
1389 ast_log(LOG_DEBUG, "Ouch, compression failed!\n");
1390 return -1;
1392 memset(&ied, 0, sizeof(ied));
1393 /* Say who we are */
1394 if (!pack->h->iseqno && !pack->h->oseqno) {
1395 /* Need the key in the first copy */
1396 if (!(peer = find_peer(&trans->them_eid)))
1397 return -1;
1398 if (update_key(peer))
1399 return -1;
1400 if (!peer->sentfullkey)
1401 ast_set_flag(trans, FLAG_SENDFULLKEY);
1402 /* Append key data */
1403 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
1404 if (ast_test_flag(trans, FLAG_SENDFULLKEY)) {
1405 dundi_ie_append_raw(&ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
1406 dundi_ie_append_raw(&ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
1407 } else {
1408 dundi_ie_append_int(&ied, DUNDI_IE_KEYCRC32, peer->us_keycrc32);
1410 /* Setup contexts */
1411 trans->ecx = peer->us_ecx;
1412 trans->dcx = peer->us_dcx;
1414 /* We've sent the full key */
1415 peer->sentfullkey = 1;
1417 /* Build initialization vector */
1418 build_iv(iv);
1419 /* Add the field, rounded up to 16 bytes */
1420 dundi_ie_append_encdata(&ied, DUNDI_IE_ENCDATA, iv, NULL, ((bytes + 15) / 16) * 16);
1421 /* Copy the data */
1422 if ((ied.pos + bytes) >= sizeof(ied.buf)) {
1423 ast_log(LOG_NOTICE, "Final packet too large!\n");
1424 return -1;
1426 encrypt_memcpy(ied.buf + ied.pos, compress_space, bytes, iv, &trans->ecx);
1427 ied.pos += ((bytes + 15) / 16) * 16;
1428 /* Reconstruct header */
1429 pack->datalen = sizeof(struct dundi_hdr);
1430 pack->h->cmdresp = DUNDI_COMMAND_ENCRYPT;
1431 pack->h->cmdflags = 0;
1432 memcpy(pack->h->ies, ied.buf, ied.pos);
1433 pack->datalen += ied.pos;
1434 return 0;
1436 return -1;
1439 static int check_key(struct dundi_peer *peer, unsigned char *newkey, unsigned char *newsig, unsigned long keycrc32)
1441 unsigned char dst[128];
1442 int res;
1443 struct ast_key *key, *skey;
1444 char eid_str[20];
1445 if (option_debug)
1446 ast_log(LOG_DEBUG, "Expected '%08lx' got '%08lx'\n", peer->them_keycrc32, keycrc32);
1447 if (peer->them_keycrc32 && (peer->them_keycrc32 == keycrc32)) {
1448 /* A match */
1449 return 1;
1450 } else if (!newkey || !newsig)
1451 return 0;
1452 if (!memcmp(peer->rxenckey, newkey, 128) &&
1453 !memcmp(peer->rxenckey + 128, newsig, 128)) {
1454 /* By definition, a match */
1455 return 1;
1457 /* Decrypt key */
1458 key = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
1459 if (!key) {
1460 ast_log(LOG_NOTICE, "Unable to find key '%s' to decode shared key from '%s'\n",
1461 peer->outkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
1462 return -1;
1465 skey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
1466 if (!skey) {
1467 ast_log(LOG_NOTICE, "Unable to find key '%s' to verify shared key from '%s'\n",
1468 peer->inkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
1469 return -1;
1472 /* First check signature */
1473 res = ast_check_signature_bin(skey, (char *)newkey, 128, newsig);
1474 if (res)
1475 return 0;
1477 res = ast_decrypt_bin(dst, newkey, sizeof(dst), key);
1478 if (res != 16) {
1479 if (res >= 0)
1480 ast_log(LOG_NOTICE, "Weird, key decoded to the wrong size (%d)\n", res);
1481 return 0;
1483 /* Decrypted, passes signature */
1484 ast_log(LOG_DEBUG, "Wow, new key combo passed signature and decrypt!\n");
1485 memcpy(peer->rxenckey, newkey, 128);
1486 memcpy(peer->rxenckey + 128, newsig, 128);
1487 peer->them_keycrc32 = crc32(0L, peer->rxenckey, 128);
1488 aes_decrypt_key128(dst, &peer->them_dcx);
1489 aes_encrypt_key128(dst, &peer->them_ecx);
1490 return 1;
1493 static int handle_command_response(struct dundi_transaction *trans, struct dundi_hdr *hdr, int datalen, int encrypted)
1495 /* Handle canonical command / response */
1496 int final = hdr->cmdresp & 0x80;
1497 int cmd = hdr->cmdresp & 0x7f;
1498 int x,y,z;
1499 int resp;
1500 int res;
1501 int authpass=0;
1502 unsigned char *bufcpy;
1503 struct dundi_ie_data ied;
1504 struct dundi_ies ies;
1505 struct dundi_peer *peer;
1506 char eid_str[20];
1507 char eid_str2[20];
1508 memset(&ied, 0, sizeof(ied));
1509 memset(&ies, 0, sizeof(ies));
1510 if (datalen) {
1511 bufcpy = alloca(datalen);
1512 if (!bufcpy)
1513 return -1;
1514 /* Make a copy for parsing */
1515 memcpy(bufcpy, hdr->ies, datalen);
1516 ast_log(LOG_DEBUG, "Got canonical message %d (%d), %d bytes data%s\n", cmd, hdr->oseqno, datalen, final ? " (Final)" : "");
1517 if (dundi_parse_ies(&ies, bufcpy, datalen) < 0) {
1518 ast_log(LOG_WARNING, "Failed to parse DUNDI information elements!\n");
1519 return -1;
1522 switch(cmd) {
1523 case DUNDI_COMMAND_DPDISCOVER:
1524 case DUNDI_COMMAND_EIDQUERY:
1525 case DUNDI_COMMAND_PRECACHERQ:
1526 if (cmd == DUNDI_COMMAND_EIDQUERY)
1527 resp = DUNDI_COMMAND_EIDRESPONSE;
1528 else if (cmd == DUNDI_COMMAND_PRECACHERQ)
1529 resp = DUNDI_COMMAND_PRECACHERP;
1530 else
1531 resp = DUNDI_COMMAND_DPRESPONSE;
1532 /* A dialplan or entity discover -- qualify by highest level entity */
1533 peer = find_peer(ies.eids[0]);
1534 if (!peer) {
1535 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
1536 dundi_send(trans, resp, 0, 1, &ied);
1537 } else {
1538 int hasauth = 0;
1539 trans->us_eid = peer->us_eid;
1540 if (strlen(peer->inkey)) {
1541 hasauth = encrypted;
1542 } else
1543 hasauth = 1;
1544 if (hasauth) {
1545 /* Okay we're authentiated and all, now we check if they're authorized */
1546 if (!ies.called_context)
1547 ies.called_context = "e164";
1548 if (cmd == DUNDI_COMMAND_EIDQUERY) {
1549 res = dundi_answer_entity(trans, &ies, ies.called_context);
1550 } else {
1551 if (ast_strlen_zero(ies.called_number)) {
1552 /* They're not permitted to access that context */
1553 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Invalid or missing number/entity");
1554 dundi_send(trans, resp, 0, 1, &ied);
1555 } else if ((cmd == DUNDI_COMMAND_DPDISCOVER) &&
1556 (peer->model & DUNDI_MODEL_INBOUND) &&
1557 has_permission(&peer->permit, ies.called_context)) {
1558 res = dundi_answer_query(trans, &ies, ies.called_context);
1559 if (res < 0) {
1560 /* There is no such dundi context */
1561 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
1562 dundi_send(trans, resp, 0, 1, &ied);
1564 } else if ((cmd = DUNDI_COMMAND_PRECACHERQ) &&
1565 (peer->pcmodel & DUNDI_MODEL_INBOUND) &&
1566 has_permission(&peer->include, ies.called_context)) {
1567 res = dundi_prop_precache(trans, &ies, ies.called_context);
1568 if (res < 0) {
1569 /* There is no such dundi context */
1570 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
1571 dundi_send(trans, resp, 0, 1, &ied);
1573 } else {
1574 /* They're not permitted to access that context */
1575 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Permission to context denied");
1576 dundi_send(trans, resp, 0, 1, &ied);
1579 } else {
1580 /* They're not permitted to access that context */
1581 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unencrypted responses not permitted");
1582 dundi_send(trans, resp, 0, 1, &ied);
1585 break;
1586 case DUNDI_COMMAND_REGREQ:
1587 /* A register request -- should only have one entity */
1588 peer = find_peer(ies.eids[0]);
1589 if (!peer || !peer->dynamic) {
1590 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
1591 dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, &ied);
1592 } else {
1593 int hasauth = 0;
1594 trans->us_eid = peer->us_eid;
1595 if (!ast_strlen_zero(peer->inkey)) {
1596 hasauth = encrypted;
1597 } else
1598 hasauth = 1;
1599 if (hasauth) {
1600 int expire = default_expiration;
1601 char data[256];
1602 int needqual = 0;
1603 if (peer->registerexpire > -1)
1604 ast_sched_del(sched, peer->registerexpire);
1605 peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
1606 snprintf(data, sizeof(data), "%s:%d:%d", ast_inet_ntoa(trans->addr.sin_addr),
1607 ntohs(trans->addr.sin_port), expire);
1608 ast_db_put("dundi/dpeers", dundi_eid_to_str_short(eid_str, sizeof(eid_str), &peer->eid), data);
1609 if (inaddrcmp(&peer->addr, &trans->addr)) {
1610 if (option_verbose > 2) {
1611 ast_verbose(VERBOSE_PREFIX_3 "Registered DUNDi peer '%s' at '%s:%d'\n",
1612 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
1613 ast_inet_ntoa(trans->addr.sin_addr), ntohs(trans->addr.sin_port));
1615 needqual = 1;
1618 memcpy(&peer->addr, &trans->addr, sizeof(peer->addr));
1619 dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, default_expiration);
1620 dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, &ied);
1621 if (needqual)
1622 qualify_peer(peer, 1);
1625 break;
1626 case DUNDI_COMMAND_DPRESPONSE:
1627 /* A dialplan response, lets see what we got... */
1628 if (ies.cause < 1) {
1629 /* Success of some sort */
1630 ast_log(LOG_DEBUG, "Looks like success of some sort (%d), %d answers\n", ies.cause, ies.anscount);
1631 if (ast_test_flag(trans, FLAG_ENCRYPT)) {
1632 authpass = encrypted;
1633 } else
1634 authpass = 1;
1635 if (authpass) {
1636 /* Pass back up answers */
1637 if (trans->parent && trans->parent->dr) {
1638 y = trans->parent->respcount;
1639 for (x=0;x<ies.anscount;x++) {
1640 if (trans->parent->respcount < trans->parent->maxcount) {
1641 /* Make sure it's not already there */
1642 for (z=0;z<trans->parent->respcount;z++) {
1643 if ((trans->parent->dr[z].techint == ies.answers[x]->protocol) &&
1644 !strcmp(trans->parent->dr[z].dest, (char *)ies.answers[x]->data))
1645 break;
1647 if (z == trans->parent->respcount) {
1648 /* Copy into parent responses */
1649 trans->parent->dr[trans->parent->respcount].flags = ntohs(ies.answers[x]->flags);
1650 trans->parent->dr[trans->parent->respcount].techint = ies.answers[x]->protocol;
1651 trans->parent->dr[trans->parent->respcount].weight = ntohs(ies.answers[x]->weight);
1652 trans->parent->dr[trans->parent->respcount].eid = ies.answers[x]->eid;
1653 if (ies.expiration > 0)
1654 trans->parent->dr[trans->parent->respcount].expiration = ies.expiration;
1655 else
1656 trans->parent->dr[trans->parent->respcount].expiration = dundi_cache_time;
1657 dundi_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str,
1658 sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
1659 &ies.answers[x]->eid);
1660 ast_copy_string(trans->parent->dr[trans->parent->respcount].dest, (char *)ies.answers[x]->data,
1661 sizeof(trans->parent->dr[trans->parent->respcount].dest));
1662 ast_copy_string(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies.answers[x]->protocol),
1663 sizeof(trans->parent->dr[trans->parent->respcount].tech));
1664 trans->parent->respcount++;
1665 ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
1666 } else if (trans->parent->dr[z].weight > ies.answers[x]->weight) {
1667 /* Update weight if appropriate */
1668 trans->parent->dr[z].weight = ies.answers[x]->weight;
1670 } else
1671 ast_log(LOG_NOTICE, "Dropping excessive answers to request for %s@%s\n",
1672 trans->parent->number, trans->parent->dcontext);
1674 /* Save all the results (if any) we had. Even if no results, still cache lookup. Let
1675 the cache know if this request was unaffected by our entity list. */
1676 cache_save(&trans->them_eid, trans->parent, y,
1677 ies.hint ? ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_UNAFFECTED)) : 0, ies.expiration, 0);
1678 if (ies.hint) {
1679 cache_save_hint(&trans->them_eid, trans->parent, ies.hint, ies.expiration);
1680 if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_TTL_EXPIRED)))
1681 ast_set_flag_nonstd(trans->parent->hmd, DUNDI_HINT_TTL_EXPIRED);
1682 if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_DONT_ASK))) {
1683 if (strlen((char *)ies.hint->data) > strlen(trans->parent->hmd->exten)) {
1684 ast_copy_string(trans->parent->hmd->exten, (char *)ies.hint->data,
1685 sizeof(trans->parent->hmd->exten));
1687 } else {
1688 ast_clear_flag_nonstd(trans->parent->hmd, DUNDI_HINT_DONT_ASK);
1691 if (ies.expiration > 0) {
1692 if (trans->parent->expiration > ies.expiration) {
1693 trans->parent->expiration = ies.expiration;
1697 /* Close connection if not final */
1698 if (!final)
1699 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1702 } else {
1703 /* Auth failure, check for data */
1704 if (!final) {
1705 /* Cancel if they didn't already */
1706 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1709 break;
1710 case DUNDI_COMMAND_EIDRESPONSE:
1711 /* A dialplan response, lets see what we got... */
1712 if (ies.cause < 1) {
1713 /* Success of some sort */
1714 ast_log(LOG_DEBUG, "Looks like success of some sort (%d)\n", ies.cause);
1715 if (ast_test_flag(trans, FLAG_ENCRYPT)) {
1716 authpass = encrypted;
1717 } else
1718 authpass = 1;
1719 if (authpass) {
1720 /* Pass back up answers */
1721 if (trans->parent && trans->parent->dei && ies.q_org) {
1722 if (!trans->parent->respcount) {
1723 trans->parent->respcount++;
1724 if (ies.q_dept)
1725 ast_copy_string(trans->parent->dei->orgunit, ies.q_dept, sizeof(trans->parent->dei->orgunit));
1726 if (ies.q_org)
1727 ast_copy_string(trans->parent->dei->org, ies.q_org, sizeof(trans->parent->dei->org));
1728 if (ies.q_locality)
1729 ast_copy_string(trans->parent->dei->locality, ies.q_locality, sizeof(trans->parent->dei->locality));
1730 if (ies.q_stateprov)
1731 ast_copy_string(trans->parent->dei->stateprov, ies.q_stateprov, sizeof(trans->parent->dei->stateprov));
1732 if (ies.q_country)
1733 ast_copy_string(trans->parent->dei->country, ies.q_country, sizeof(trans->parent->dei->country));
1734 if (ies.q_email)
1735 ast_copy_string(trans->parent->dei->email, ies.q_email, sizeof(trans->parent->dei->email));
1736 if (ies.q_phone)
1737 ast_copy_string(trans->parent->dei->phone, ies.q_phone, sizeof(trans->parent->dei->phone));
1738 if (ies.q_ipaddr)
1739 ast_copy_string(trans->parent->dei->ipaddr, ies.q_ipaddr, sizeof(trans->parent->dei->ipaddr));
1740 if (!dundi_eid_cmp(&trans->them_eid, &trans->parent->query_eid)) {
1741 /* If it's them, update our address */
1742 ast_copy_string(trans->parent->dei->ipaddr, ast_inet_ntoa(trans->addr.sin_addr), sizeof(trans->parent->dei->ipaddr));
1745 if (ies.hint) {
1746 if (ast_test_flag_nonstd(ies.hint, htons(DUNDI_HINT_TTL_EXPIRED)))
1747 ast_set_flag_nonstd(trans->parent->hmd, DUNDI_HINT_TTL_EXPIRED);
1750 /* Close connection if not final */
1751 if (!final)
1752 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1755 } else {
1756 /* Auth failure, check for data */
1757 if (!final) {
1758 /* Cancel if they didn't already */
1759 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1762 break;
1763 case DUNDI_COMMAND_REGRESPONSE:
1764 /* A dialplan response, lets see what we got... */
1765 if (ies.cause < 1) {
1766 int hasauth;
1767 /* Success of some sort */
1768 if (ast_test_flag(trans, FLAG_ENCRYPT)) {
1769 hasauth = encrypted;
1770 } else
1771 hasauth = 1;
1773 if (!hasauth) {
1774 ast_log(LOG_NOTICE, "Reponse to register not authorized!\n");
1775 if (!final) {
1776 dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Improper signature in answer");
1777 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, &ied);
1779 } else {
1780 ast_log(LOG_DEBUG, "Yay, we've registered as '%s' to '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &trans->us_eid),
1781 dundi_eid_to_str(eid_str2, sizeof(eid_str2), &trans->them_eid));
1782 /* Close connection if not final */
1783 if (!final)
1784 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1786 } else {
1787 /* Auth failure, cancel if they didn't for some reason */
1788 if (!final) {
1789 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1792 break;
1793 case DUNDI_COMMAND_INVALID:
1794 case DUNDI_COMMAND_NULL:
1795 case DUNDI_COMMAND_PRECACHERP:
1796 /* Do nothing special */
1797 if (!final)
1798 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1799 break;
1800 case DUNDI_COMMAND_ENCREJ:
1801 if ((ast_test_flag(trans, FLAG_SENDFULLKEY)) || AST_LIST_EMPTY(&trans->lasttrans) || !(peer = find_peer(&trans->them_eid))) {
1802 /* No really, it's over at this point */
1803 if (!final)
1804 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
1805 } else {
1806 /* Send with full key */
1807 ast_set_flag(trans, FLAG_SENDFULLKEY);
1808 if (final) {
1809 /* Ooops, we got a final message, start by sending ACK... */
1810 dundi_ack(trans, hdr->cmdresp & 0x80);
1811 trans->aseqno = trans->iseqno;
1812 /* Now, we gotta create a new transaction */
1813 if (!reset_transaction(trans)) {
1814 /* Make sure handle_frame doesn't destroy us */
1815 hdr->cmdresp &= 0x7f;
1816 /* Parse the message we transmitted */
1817 memset(&ies, 0, sizeof(ies));
1818 dundi_parse_ies(&ies, (AST_LIST_FIRST(&trans->lasttrans))->h->ies, (AST_LIST_FIRST(&trans->lasttrans))->datalen - sizeof(struct dundi_hdr));
1819 /* Reconstruct outgoing encrypted packet */
1820 memset(&ied, 0, sizeof(ied));
1821 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
1822 dundi_ie_append_raw(&ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
1823 dundi_ie_append_raw(&ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
1824 if (ies.encblock)
1825 dundi_ie_append_encdata(&ied, DUNDI_IE_ENCDATA, ies.encblock->iv, ies.encblock->encdata, ies.enclen);
1826 dundi_send(trans, DUNDI_COMMAND_ENCRYPT, 0, (AST_LIST_FIRST(&trans->lasttrans))->h->cmdresp & 0x80, &ied);
1827 peer->sentfullkey = 1;
1831 break;
1832 case DUNDI_COMMAND_ENCRYPT:
1833 if (!encrypted) {
1834 /* No nested encryption! */
1835 if ((trans->iseqno == 1) && !trans->oseqno) {
1836 if (!ies.eids[0] || !(peer = find_peer(ies.eids[0])) ||
1837 ((!ies.encsharedkey || !ies.encsig) && !ies.keycrc32) ||
1838 (check_key(peer, ies.encsharedkey, ies.encsig, ies.keycrc32) < 1)) {
1839 if (!final) {
1840 dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
1842 break;
1844 apply_peer(trans, peer);
1845 /* Key passed, use new contexts for this session */
1846 trans->ecx = peer->them_ecx;
1847 trans->dcx = peer->them_dcx;
1849 if (ast_test_flag(trans, FLAG_ENCRYPT) && ies.encblock && ies.enclen) {
1850 struct dundi_hdr *dhdr;
1851 unsigned char decoded[MAX_PACKET_SIZE];
1852 int ddatalen;
1853 ddatalen = sizeof(decoded);
1854 dhdr = dundi_decrypt(trans, decoded, &ddatalen, hdr, ies.encblock, ies.enclen);
1855 if (dhdr) {
1856 /* Handle decrypted response */
1857 if (dundidebug)
1858 dundi_showframe(dhdr, 3, &trans->addr, ddatalen - sizeof(struct dundi_hdr));
1859 handle_command_response(trans, dhdr, ddatalen - sizeof(struct dundi_hdr), 1);
1860 /* Carry back final flag */
1861 hdr->cmdresp |= dhdr->cmdresp & 0x80;
1862 break;
1863 } else
1864 ast_log(LOG_DEBUG, "Ouch, decrypt failed :(\n");
1867 if (!final) {
1868 /* Turn off encryption */
1869 ast_clear_flag(trans, FLAG_ENCRYPT);
1870 dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
1872 break;
1873 default:
1874 /* Send unknown command if we don't know it, with final flag IFF it's the
1875 first command in the dialog and only if we haven't recieved final notification */
1876 if (!final) {
1877 dundi_ie_append_byte(&ied, DUNDI_IE_UNKNOWN, cmd);
1878 dundi_send(trans, DUNDI_COMMAND_UNKNOWN, 0, !hdr->oseqno, &ied);
1881 return 0;
1884 static void destroy_packet(struct dundi_packet *pack, int needfree);
1885 static void destroy_packets(struct packetlist *p)
1887 struct dundi_packet *pack;
1889 while ((pack = AST_LIST_REMOVE_HEAD(p, list))) {
1890 if (pack->retransid > -1)
1891 ast_sched_del(sched, pack->retransid);
1892 free(pack);
1897 static int ack_trans(struct dundi_transaction *trans, int iseqno)
1899 struct dundi_packet *pack;
1901 /* Ack transmitted packet corresponding to iseqno */
1902 AST_LIST_TRAVERSE(&trans->packets, pack, list) {
1903 if ((pack->h->oseqno + 1) % 255 == iseqno) {
1904 destroy_packet(pack, 0);
1905 if (!AST_LIST_EMPTY(&trans->lasttrans)) {
1906 ast_log(LOG_WARNING, "Whoa, there was still a last trans?\n");
1907 destroy_packets(&trans->lasttrans);
1909 AST_LIST_INSERT_HEAD(&trans->lasttrans, pack, list);
1910 if (trans->autokillid > -1)
1911 ast_sched_del(sched, trans->autokillid);
1912 trans->autokillid = -1;
1913 return 1;
1917 return 0;
1920 static int handle_frame(struct dundi_hdr *h, struct sockaddr_in *sin, int datalen)
1922 struct dundi_transaction *trans;
1923 trans = find_transaction(h, sin);
1924 if (!trans) {
1925 dundi_reject(h, sin);
1926 return 0;
1928 /* Got a transaction, see where this header fits in */
1929 if (h->oseqno == trans->iseqno) {
1930 /* Just what we were looking for... Anything but ack increments iseqno */
1931 if (ack_trans(trans, h->iseqno) && ast_test_flag(trans, FLAG_FINAL)) {
1932 /* If final, we're done */
1933 destroy_trans(trans, 0);
1934 return 0;
1936 if (h->cmdresp != DUNDI_COMMAND_ACK) {
1937 trans->oiseqno = trans->iseqno;
1938 trans->iseqno++;
1939 handle_command_response(trans, h, datalen, 0);
1941 if (trans->aseqno != trans->iseqno) {
1942 dundi_ack(trans, h->cmdresp & 0x80);
1943 trans->aseqno = trans->iseqno;
1945 /* Delete any saved last transmissions */
1946 destroy_packets(&trans->lasttrans);
1947 if (h->cmdresp & 0x80) {
1948 /* Final -- destroy now */
1949 destroy_trans(trans, 0);
1951 } else if (h->oseqno == trans->oiseqno) {
1952 /* Last incoming sequence number -- send ACK without processing */
1953 dundi_ack(trans, 0);
1954 } else {
1955 /* Out of window -- simply drop */
1956 ast_log(LOG_DEBUG, "Dropping packet out of window!\n");
1958 return 0;
1961 static int socket_read(int *id, int fd, short events, void *cbdata)
1963 struct sockaddr_in sin;
1964 int res;
1965 struct dundi_hdr *h;
1966 char buf[MAX_PACKET_SIZE];
1967 socklen_t len;
1968 len = sizeof(sin);
1969 res = recvfrom(netsocket, buf, sizeof(buf) - 1, 0,(struct sockaddr *) &sin, &len);
1970 if (res < 0) {
1971 if (errno != ECONNREFUSED)
1972 ast_log(LOG_WARNING, "Error: %s\n", strerror(errno));
1973 return 1;
1975 if (res < sizeof(struct dundi_hdr)) {
1976 ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int)sizeof(struct dundi_hdr));
1977 return 1;
1979 buf[res] = '\0';
1980 h = (struct dundi_hdr *)buf;
1981 if (dundidebug)
1982 dundi_showframe(h, 1, &sin, res - sizeof(struct dundi_hdr));
1983 AST_LIST_LOCK(&peers);
1984 handle_frame(h, &sin, res - sizeof(struct dundi_hdr));
1985 AST_LIST_UNLOCK(&peers);
1986 return 1;
1989 static void build_secret(char *secret, int seclen)
1991 unsigned char tmp[16];
1992 char *s;
1993 build_iv(tmp);
1994 secret[0] = '\0';
1995 ast_base64encode(secret, tmp, sizeof(tmp), seclen);
1996 /* Eliminate potential bad characters */
1997 while((s = strchr(secret, ';'))) *s = '+';
1998 while((s = strchr(secret, '/'))) *s = '+';
1999 while((s = strchr(secret, ':'))) *s = '+';
2000 while((s = strchr(secret, '@'))) *s = '+';
2004 static void save_secret(const char *newkey, const char *oldkey)
2006 char tmp[256];
2007 if (oldkey)
2008 snprintf(tmp, sizeof(tmp), "%s;%s", oldkey, newkey);
2009 else
2010 snprintf(tmp, sizeof(tmp), "%s", newkey);
2011 rotatetime = time(NULL) + DUNDI_SECRET_TIME;
2012 ast_db_put(secretpath, "secret", tmp);
2013 snprintf(tmp, sizeof(tmp), "%d", (int)rotatetime);
2014 ast_db_put(secretpath, "secretexpiry", tmp);
2017 static void load_password(void)
2019 char *current=NULL;
2020 char *last=NULL;
2021 char tmp[256];
2022 time_t expired;
2024 ast_db_get(secretpath, "secretexpiry", tmp, sizeof(tmp));
2025 if (!ast_get_time_t(tmp, &expired, 0, NULL)) {
2026 ast_db_get(secretpath, "secret", tmp, sizeof(tmp));
2027 current = strchr(tmp, ';');
2028 if (!current)
2029 current = tmp;
2030 else {
2031 *current = '\0';
2032 current++;
2034 if ((time(NULL) - expired) < 0) {
2035 if ((expired - time(NULL)) > DUNDI_SECRET_TIME)
2036 expired = time(NULL) + DUNDI_SECRET_TIME;
2037 } else if ((time(NULL) - (expired + DUNDI_SECRET_TIME)) < 0) {
2038 last = current;
2039 current = NULL;
2040 } else {
2041 last = NULL;
2042 current = NULL;
2045 if (current) {
2046 /* Current key is still valid, just setup rotatation properly */
2047 ast_copy_string(cursecret, current, sizeof(cursecret));
2048 rotatetime = expired;
2049 } else {
2050 /* Current key is out of date, rotate or eliminate all together */
2051 build_secret(cursecret, sizeof(cursecret));
2052 save_secret(cursecret, last);
2056 static void check_password(void)
2058 char oldsecret[80];
2059 time_t now;
2061 time(&now);
2062 #if 0
2063 printf("%ld/%ld\n", now, rotatetime);
2064 #endif
2065 if ((now - rotatetime) >= 0) {
2066 /* Time to rotate keys */
2067 ast_copy_string(oldsecret, cursecret, sizeof(oldsecret));
2068 build_secret(cursecret, sizeof(cursecret));
2069 save_secret(cursecret, oldsecret);
2073 static void *network_thread(void *ignore)
2075 /* Our job is simple: Send queued messages, retrying if necessary. Read frames
2076 from the network, and queue them for delivery to the channels */
2077 int res;
2078 /* Establish I/O callback for socket read */
2079 ast_io_add(io, netsocket, socket_read, AST_IO_IN, NULL);
2080 for(;;) {
2081 res = ast_sched_wait(sched);
2082 if ((res > 1000) || (res < 0))
2083 res = 1000;
2084 res = ast_io_wait(io, res);
2085 if (res >= 0) {
2086 AST_LIST_LOCK(&peers);
2087 ast_sched_runq(sched);
2088 AST_LIST_UNLOCK(&peers);
2090 check_password();
2092 return NULL;
2095 static void *process_precache(void *ign)
2097 struct dundi_precache_queue *qe;
2098 time_t now;
2099 char context[256];
2100 char number[256];
2101 int run;
2103 for (;;) {
2104 time(&now);
2105 run = 0;
2106 AST_LIST_LOCK(&pcq);
2107 if ((qe = AST_LIST_FIRST(&pcq))) {
2108 if (!qe->expiration) {
2109 /* Gone... Remove... */
2110 AST_LIST_REMOVE_HEAD(&pcq, list);
2111 free(qe);
2112 } else if (qe->expiration < now) {
2113 /* Process this entry */
2114 qe->expiration = 0;
2115 ast_copy_string(context, qe->context, sizeof(context));
2116 ast_copy_string(number, qe->number, sizeof(number));
2117 run = 1;
2120 AST_LIST_UNLOCK(&pcq);
2121 if (run) {
2122 dundi_precache(context, number);
2123 } else
2124 sleep(1);
2127 return NULL;
2130 static int start_network_thread(void)
2132 ast_pthread_create_background(&netthreadid, NULL, network_thread, NULL);
2133 ast_pthread_create_background(&precachethreadid, NULL, process_precache, NULL);
2134 return 0;
2137 static int dundi_do_debug(int fd, int argc, char *argv[])
2139 if (argc != 2)
2140 return RESULT_SHOWUSAGE;
2141 dundidebug = 1;
2142 ast_cli(fd, "DUNDi Debugging Enabled\n");
2143 return RESULT_SUCCESS;
2146 static int dundi_do_store_history(int fd, int argc, char *argv[])
2148 if (argc != 3)
2149 return RESULT_SHOWUSAGE;
2150 global_storehistory = 1;
2151 ast_cli(fd, "DUNDi History Storage Enabled\n");
2152 return RESULT_SUCCESS;
2155 static int dundi_flush(int fd, int argc, char *argv[])
2157 int stats = 0;
2158 if ((argc < 2) || (argc > 3))
2159 return RESULT_SHOWUSAGE;
2160 if (argc > 2) {
2161 if (!strcasecmp(argv[2], "stats"))
2162 stats = 1;
2163 else
2164 return RESULT_SHOWUSAGE;
2166 if (stats) {
2167 /* Flush statistics */
2168 struct dundi_peer *p;
2169 int x;
2170 AST_LIST_LOCK(&peers);
2171 AST_LIST_TRAVERSE(&peers, p, list) {
2172 for (x = 0;x < DUNDI_TIMING_HISTORY; x++) {
2173 if (p->lookups[x])
2174 free(p->lookups[x]);
2175 p->lookups[x] = NULL;
2176 p->lookuptimes[x] = 0;
2178 p->avgms = 0;
2180 AST_LIST_UNLOCK(&peers);
2181 } else {
2182 ast_db_deltree("dundi/cache", NULL);
2183 ast_cli(fd, "DUNDi Cache Flushed\n");
2185 return RESULT_SUCCESS;
2188 static int dundi_no_debug(int fd, int argc, char *argv[])
2190 if (argc != 3)
2191 return RESULT_SHOWUSAGE;
2192 dundidebug = 0;
2193 ast_cli(fd, "DUNDi Debugging Disabled\n");
2194 return RESULT_SUCCESS;
2197 static int dundi_no_store_history(int fd, int argc, char *argv[])
2199 if (argc != 4)
2200 return RESULT_SHOWUSAGE;
2201 global_storehistory = 0;
2202 ast_cli(fd, "DUNDi History Storage Disabled\n");
2203 return RESULT_SUCCESS;
2206 static char *model2str(int model)
2208 switch(model) {
2209 case DUNDI_MODEL_INBOUND:
2210 return "Inbound";
2211 case DUNDI_MODEL_OUTBOUND:
2212 return "Outbound";
2213 case DUNDI_MODEL_SYMMETRIC:
2214 return "Symmetric";
2215 default:
2216 return "Unknown";
2220 static char *complete_peer_helper(const char *line, const char *word, int pos, int state, int rpos)
2222 int which=0, len;
2223 char *ret = NULL;
2224 struct dundi_peer *p;
2225 char eid_str[20];
2227 if (pos != rpos)
2228 return NULL;
2229 AST_LIST_LOCK(&peers);
2230 len = strlen(word);
2231 AST_LIST_TRAVERSE(&peers, p, list) {
2232 const char *s = dundi_eid_to_str(eid_str, sizeof(eid_str), &p->eid);
2233 if (!strncasecmp(word, s, len) && ++which > state)
2234 ret = ast_strdup(s);
2236 AST_LIST_UNLOCK(&peers);
2237 return ret;
2240 static char *complete_peer_4(const char *line, const char *word, int pos, int state)
2242 return complete_peer_helper(line, word, pos, state, 3);
2245 static int rescomp(const void *a, const void *b)
2247 const struct dundi_result *resa, *resb;
2248 resa = a;
2249 resb = b;
2250 if (resa->weight < resb->weight)
2251 return -1;
2252 if (resa->weight > resb->weight)
2253 return 1;
2254 return 0;
2257 static void sort_results(struct dundi_result *results, int count)
2259 qsort(results, count, sizeof(results[0]), rescomp);
2262 static int dundi_do_lookup(int fd, int argc, char *argv[])
2264 int res;
2265 char tmp[256];
2266 char fs[80] = "";
2267 char *context;
2268 int x;
2269 int bypass = 0;
2270 struct dundi_result dr[MAX_RESULTS];
2271 struct timeval start;
2272 if ((argc < 3) || (argc > 4))
2273 return RESULT_SHOWUSAGE;
2274 if (argc > 3) {
2275 if (!strcasecmp(argv[3], "bypass"))
2276 bypass=1;
2277 else
2278 return RESULT_SHOWUSAGE;
2280 ast_copy_string(tmp, argv[2], sizeof(tmp));
2281 context = strchr(tmp, '@');
2282 if (context) {
2283 *context = '\0';
2284 context++;
2286 start = ast_tvnow();
2287 res = dundi_lookup(dr, MAX_RESULTS, NULL, context, tmp, bypass);
2289 if (res < 0)
2290 ast_cli(fd, "DUNDi lookup returned error.\n");
2291 else if (!res)
2292 ast_cli(fd, "DUNDi lookup returned no results.\n");
2293 else
2294 sort_results(dr, res);
2295 for (x=0;x<res;x++) {
2296 ast_cli(fd, "%3d. %5d %s/%s (%s)\n", x + 1, dr[x].weight, dr[x].tech, dr[x].dest, dundi_flags2str(fs, sizeof(fs), dr[x].flags));
2297 ast_cli(fd, " from %s, expires in %d s\n", dr[x].eid_str, dr[x].expiration);
2299 ast_cli(fd, "DUNDi lookup completed in %d ms\n", ast_tvdiff_ms(ast_tvnow(), start));
2300 return RESULT_SUCCESS;
2303 static int dundi_do_precache(int fd, int argc, char *argv[])
2305 int res;
2306 char tmp[256];
2307 char *context;
2308 struct timeval start;
2309 if ((argc < 3) || (argc > 3))
2310 return RESULT_SHOWUSAGE;
2311 ast_copy_string(tmp, argv[2], sizeof(tmp));
2312 context = strchr(tmp, '@');
2313 if (context) {
2314 *context = '\0';
2315 context++;
2317 start = ast_tvnow();
2318 res = dundi_precache(context, tmp);
2320 if (res < 0)
2321 ast_cli(fd, "DUNDi precache returned error.\n");
2322 else if (!res)
2323 ast_cli(fd, "DUNDi precache returned no error.\n");
2324 ast_cli(fd, "DUNDi lookup completed in %d ms\n", ast_tvdiff_ms(ast_tvnow(), start));
2325 return RESULT_SUCCESS;
2328 static int dundi_do_query(int fd, int argc, char *argv[])
2330 int res;
2331 char tmp[256];
2332 char *context;
2333 dundi_eid eid;
2334 struct dundi_entity_info dei;
2335 if ((argc < 3) || (argc > 3))
2336 return RESULT_SHOWUSAGE;
2337 if (dundi_str_to_eid(&eid, argv[2])) {
2338 ast_cli(fd, "'%s' is not a valid EID!\n", argv[2]);
2339 return RESULT_SHOWUSAGE;
2341 ast_copy_string(tmp, argv[2], sizeof(tmp));
2342 context = strchr(tmp, '@');
2343 if (context) {
2344 *context = '\0';
2345 context++;
2347 res = dundi_query_eid(&dei, context, eid);
2348 if (res < 0)
2349 ast_cli(fd, "DUNDi Query EID returned error.\n");
2350 else if (!res)
2351 ast_cli(fd, "DUNDi Query EID returned no results.\n");
2352 else {
2353 ast_cli(fd, "DUNDi Query EID succeeded:\n");
2354 ast_cli(fd, "Department: %s\n", dei.orgunit);
2355 ast_cli(fd, "Organization: %s\n", dei.org);
2356 ast_cli(fd, "City/Locality: %s\n", dei.locality);
2357 ast_cli(fd, "State/Province: %s\n", dei.stateprov);
2358 ast_cli(fd, "Country: %s\n", dei.country);
2359 ast_cli(fd, "E-mail: %s\n", dei.email);
2360 ast_cli(fd, "Phone: %s\n", dei.phone);
2361 ast_cli(fd, "IP Address: %s\n", dei.ipaddr);
2363 return RESULT_SUCCESS;
2366 static int dundi_show_peer(int fd, int argc, char *argv[])
2368 struct dundi_peer *peer;
2369 struct permission *p;
2370 char *order;
2371 char eid_str[20];
2372 int x, cnt;
2374 if (argc != 4)
2375 return RESULT_SHOWUSAGE;
2376 AST_LIST_LOCK(&peers);
2377 AST_LIST_TRAVERSE(&peers, peer, list) {
2378 if (!strcasecmp(dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), argv[3]))
2379 break;
2381 if (peer) {
2382 switch(peer->order) {
2383 case 0:
2384 order = "Primary";
2385 break;
2386 case 1:
2387 order = "Secondary";
2388 break;
2389 case 2:
2390 order = "Tertiary";
2391 break;
2392 case 3:
2393 order = "Quartiary";
2394 break;
2395 default:
2396 order = "Unknown";
2398 ast_cli(fd, "Peer: %s\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
2399 ast_cli(fd, "Model: %s\n", model2str(peer->model));
2400 ast_cli(fd, "Host: %s\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "<Unspecified>");
2401 ast_cli(fd, "Dynamic: %s\n", peer->dynamic ? "yes" : "no");
2402 ast_cli(fd, "Reg: %s\n", peer->registerid < 0 ? "No" : "Yes");
2403 ast_cli(fd, "In Key: %s\n", ast_strlen_zero(peer->inkey) ? "<None>" : peer->inkey);
2404 ast_cli(fd, "Out Key: %s\n", ast_strlen_zero(peer->outkey) ? "<None>" : peer->outkey);
2405 if (!AST_LIST_EMPTY(&peer->include))
2406 ast_cli(fd, "Include logic%s:\n", peer->model & DUNDI_MODEL_OUTBOUND ? "" : " (IGNORED)");
2407 AST_LIST_TRAVERSE(&peer->include, p, list)
2408 ast_cli(fd, "-- %s %s\n", p->allow ? "include" : "do not include", p->name);
2409 if (!AST_LIST_EMPTY(&peer->permit))
2410 ast_cli(fd, "Query logic%s:\n", peer->model & DUNDI_MODEL_INBOUND ? "" : " (IGNORED)");
2411 AST_LIST_TRAVERSE(&peer->permit, p, list)
2412 ast_cli(fd, "-- %s %s\n", p->allow ? "permit" : "deny", p->name);
2413 cnt = 0;
2414 for (x = 0;x < DUNDI_TIMING_HISTORY; x++) {
2415 if (peer->lookups[x]) {
2416 if (!cnt)
2417 ast_cli(fd, "Last few query times:\n");
2418 ast_cli(fd, "-- %d. %s (%d ms)\n", x + 1, peer->lookups[x], peer->lookuptimes[x]);
2419 cnt++;
2422 if (cnt)
2423 ast_cli(fd, "Average query time: %d ms\n", peer->avgms);
2424 } else
2425 ast_cli(fd, "No such peer '%s'\n", argv[3]);
2426 AST_LIST_UNLOCK(&peers);
2427 return RESULT_SUCCESS;
2430 static int dundi_show_peers(int fd, int argc, char *argv[])
2432 #define FORMAT2 "%-20.20s %-15.15s %-10.10s %-8.8s %-15.15s\n"
2433 #define FORMAT "%-20.20s %-15.15s %s %-10.10s %-8.8s %-15.15s\n"
2434 struct dundi_peer *peer;
2435 int registeredonly=0;
2436 char avgms[20];
2437 char eid_str[20];
2438 int online_peers = 0;
2439 int offline_peers = 0;
2440 int unmonitored_peers = 0;
2441 int total_peers = 0;
2443 if ((argc != 3) && (argc != 4) && (argc != 5))
2444 return RESULT_SHOWUSAGE;
2445 if ((argc == 4)) {
2446 if (!strcasecmp(argv[3], "registered")) {
2447 registeredonly = 1;
2448 } else
2449 return RESULT_SHOWUSAGE;
2451 AST_LIST_LOCK(&peers);
2452 ast_cli(fd, FORMAT2, "EID", "Host", "Model", "AvgTime", "Status");
2453 AST_LIST_TRAVERSE(&peers, peer, list) {
2454 char status[20];
2455 int print_line = -1;
2456 char srch[2000];
2457 total_peers++;
2458 if (registeredonly && !peer->addr.sin_addr.s_addr)
2459 continue;
2460 if (peer->maxms) {
2461 if (peer->lastms < 0) {
2462 strcpy(status, "UNREACHABLE");
2463 offline_peers++;
2465 else if (peer->lastms > peer->maxms) {
2466 snprintf(status, sizeof(status), "LAGGED (%d ms)", peer->lastms);
2467 offline_peers++;
2469 else if (peer->lastms) {
2470 snprintf(status, sizeof(status), "OK (%d ms)", peer->lastms);
2471 online_peers++;
2473 else {
2474 strcpy(status, "UNKNOWN");
2475 offline_peers++;
2477 } else {
2478 strcpy(status, "Unmonitored");
2479 unmonitored_peers++;
2481 if (peer->avgms)
2482 snprintf(avgms, sizeof(avgms), "%d ms", peer->avgms);
2483 else
2484 strcpy(avgms, "Unavail");
2485 snprintf(srch, sizeof(srch), FORMAT, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
2486 peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)",
2487 peer->dynamic ? "(D)" : "(S)", model2str(peer->model), avgms, status);
2489 if (argc == 5) {
2490 if (!strcasecmp(argv[3],"include") && strstr(srch,argv[4])) {
2491 print_line = -1;
2492 } else if (!strcasecmp(argv[3],"exclude") && !strstr(srch,argv[4])) {
2493 print_line = 1;
2494 } else if (!strcasecmp(argv[3],"begin") && !strncasecmp(srch,argv[4],strlen(argv[4]))) {
2495 print_line = -1;
2496 } else {
2497 print_line = 0;
2501 if (print_line) {
2502 ast_cli(fd, FORMAT, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
2503 peer->addr.sin_addr.s_addr ? ast_inet_ntoa(peer->addr.sin_addr) : "(Unspecified)",
2504 peer->dynamic ? "(D)" : "(S)", model2str(peer->model), avgms, status);
2507 ast_cli(fd, "%d dundi peers [%d online, %d offline, %d unmonitored]\n", total_peers, online_peers, offline_peers, unmonitored_peers);
2508 AST_LIST_UNLOCK(&peers);
2509 return RESULT_SUCCESS;
2510 #undef FORMAT
2511 #undef FORMAT2
2514 static int dundi_show_trans(int fd, int argc, char *argv[])
2516 #define FORMAT2 "%-22.22s %-5.5s %-5.5s %-3.3s %-3.3s %-3.3s\n"
2517 #define FORMAT "%-16.16s:%5d %-5.5d %-5.5d %-3.3d %-3.3d %-3.3d\n"
2518 struct dundi_transaction *trans;
2519 if (argc != 3)
2520 return RESULT_SHOWUSAGE;
2521 AST_LIST_LOCK(&peers);
2522 ast_cli(fd, FORMAT2, "Remote", "Src", "Dst", "Tx", "Rx", "Ack");
2523 AST_LIST_TRAVERSE(&alltrans, trans, all) {
2524 ast_cli(fd, FORMAT, ast_inet_ntoa(trans->addr.sin_addr),
2525 ntohs(trans->addr.sin_port), trans->strans, trans->dtrans, trans->oseqno, trans->iseqno, trans->aseqno);
2527 AST_LIST_UNLOCK(&peers);
2528 return RESULT_SUCCESS;
2529 #undef FORMAT
2530 #undef FORMAT2
2533 static int dundi_show_entityid(int fd, int argc, char *argv[])
2535 char eid_str[20];
2536 if (argc != 3)
2537 return RESULT_SHOWUSAGE;
2538 AST_LIST_LOCK(&peers);
2539 dundi_eid_to_str(eid_str, sizeof(eid_str), &global_eid);
2540 AST_LIST_UNLOCK(&peers);
2541 ast_cli(fd, "Global EID for this system is '%s'\n", eid_str);
2542 return RESULT_SUCCESS;
2545 static int dundi_show_requests(int fd, int argc, char *argv[])
2547 #define FORMAT2 "%-15s %-15s %-15s %-3.3s %-3.3s\n"
2548 #define FORMAT "%-15s %-15s %-15s %-3.3d %-3.3d\n"
2549 struct dundi_request *req;
2550 char eidstr[20];
2551 if (argc != 3)
2552 return RESULT_SHOWUSAGE;
2553 AST_LIST_LOCK(&peers);
2554 ast_cli(fd, FORMAT2, "Number", "Context", "Root", "Max", "Rsp");
2555 AST_LIST_TRAVERSE(&requests, req, list) {
2556 ast_cli(fd, FORMAT, req->number, req->dcontext,
2557 dundi_eid_zero(&req->root_eid) ? "<unspecified>" : dundi_eid_to_str(eidstr, sizeof(eidstr), &req->root_eid), req->maxcount, req->respcount);
2559 AST_LIST_UNLOCK(&peers);
2560 return RESULT_SUCCESS;
2561 #undef FORMAT
2562 #undef FORMAT2
2565 /* Grok-a-dial DUNDi */
2567 static int dundi_show_mappings(int fd, int argc, char *argv[])
2569 #define FORMAT2 "%-12.12s %-7.7s %-12.12s %-10.10s %-5.5s %-25.25s\n"
2570 #define FORMAT "%-12.12s %-7d %-12.12s %-10.10s %-5.5s %-25.25s\n"
2571 struct dundi_mapping *map;
2572 char fs[256];
2573 if (argc != 3)
2574 return RESULT_SHOWUSAGE;
2575 AST_LIST_LOCK(&peers);
2576 ast_cli(fd, FORMAT2, "DUNDi Cntxt", "Weight", "Local Cntxt", "Options", "Tech", "Destination");
2577 AST_LIST_TRAVERSE(&mappings, map, list) {
2578 ast_cli(fd, FORMAT, map->dcontext, map->weight,
2579 ast_strlen_zero(map->lcontext) ? "<none>" : map->lcontext,
2580 dundi_flags2str(fs, sizeof(fs), map->options), tech2str(map->tech), map->dest);
2582 AST_LIST_UNLOCK(&peers);
2583 return RESULT_SUCCESS;
2584 #undef FORMAT
2585 #undef FORMAT2
2588 static int dundi_show_precache(int fd, int argc, char *argv[])
2590 #define FORMAT2 "%-12.12s %-12.12s %-10.10s\n"
2591 #define FORMAT "%-12.12s %-12.12s %02d:%02d:%02d\n"
2592 struct dundi_precache_queue *qe;
2593 int h,m,s;
2594 time_t now;
2596 if (argc != 3)
2597 return RESULT_SHOWUSAGE;
2598 time(&now);
2599 ast_cli(fd, FORMAT2, "Number", "Context", "Expiration");
2600 AST_LIST_LOCK(&pcq);
2601 AST_LIST_TRAVERSE(&pcq, qe, list) {
2602 s = qe->expiration - now;
2603 h = s / 3600;
2604 s = s % 3600;
2605 m = s / 60;
2606 s = s % 60;
2607 ast_cli(fd, FORMAT, qe->number, qe->context, h,m,s);
2609 AST_LIST_UNLOCK(&pcq);
2611 return RESULT_SUCCESS;
2612 #undef FORMAT
2613 #undef FORMAT2
2616 static char debug_usage[] =
2617 "Usage: dundi debug\n"
2618 " Enables dumping of DUNDi packets for debugging purposes\n";
2620 static char no_debug_usage[] =
2621 "Usage: dundi no debug\n"
2622 " Disables dumping of DUNDi packets for debugging purposes\n";
2624 static char store_history_usage[] =
2625 "Usage: dundi store history\n"
2626 " Enables storing of DUNDi requests and times for debugging\n"
2627 "purposes\n";
2629 static char no_store_history_usage[] =
2630 "Usage: dundi no store history\n"
2631 " Disables storing of DUNDi requests and times for debugging\n"
2632 "purposes\n";
2634 static char show_peers_usage[] =
2635 "Usage: dundi show peers\n"
2636 " Lists all known DUNDi peers.\n";
2638 static char show_trans_usage[] =
2639 "Usage: dundi show trans\n"
2640 " Lists all known DUNDi transactions.\n";
2642 static char show_mappings_usage[] =
2643 "Usage: dundi show mappings\n"
2644 " Lists all known DUNDi mappings.\n";
2646 static char show_precache_usage[] =
2647 "Usage: dundi show precache\n"
2648 " Lists all known DUNDi scheduled precache updates.\n";
2650 static char show_entityid_usage[] =
2651 "Usage: dundi show entityid\n"
2652 " Displays the global entityid for this host.\n";
2654 static char show_peer_usage[] =
2655 "Usage: dundi show peer [peer]\n"
2656 " Provide a detailed description of a specifid DUNDi peer.\n";
2658 static char show_requests_usage[] =
2659 "Usage: dundi show requests\n"
2660 " Lists all known pending DUNDi requests.\n";
2662 static char lookup_usage[] =
2663 "Usage: dundi lookup <number>[@context] [bypass]\n"
2664 " Lookup the given number within the given DUNDi context\n"
2665 "(or e164 if none is specified). Bypasses cache if 'bypass'\n"
2666 "keyword is specified.\n";
2668 static char precache_usage[] =
2669 "Usage: dundi precache <number>[@context]\n"
2670 " Lookup the given number within the given DUNDi context\n"
2671 "(or e164 if none is specified) and precaches the results to any\n"
2672 "upstream DUNDi push servers.\n";
2674 static char query_usage[] =
2675 "Usage: dundi query <entity>[@context]\n"
2676 " Attempts to retrieve contact information for a specific\n"
2677 "DUNDi entity identifier (EID) within a given DUNDi context (or\n"
2678 "e164 if none is specified).\n";
2680 static char flush_usage[] =
2681 "Usage: dundi flush [stats]\n"
2682 " Flushes DUNDi answer cache, used primarily for debug. If\n"
2683 "'stats' is present, clears timer statistics instead of normal\n"
2684 "operation.\n";
2686 static struct ast_cli_entry cli_dundi[] = {
2687 { { "dundi", "debug", NULL },
2688 dundi_do_debug, "Enable DUNDi debugging",
2689 debug_usage },
2691 { { "dundi", "store", "history", NULL },
2692 dundi_do_store_history, "Enable DUNDi historic records",
2693 store_history_usage },
2695 { { "dundi", "no", "store", "history", NULL },
2696 dundi_no_store_history, "Disable DUNDi historic records",
2697 no_store_history_usage },
2699 { { "dundi", "flush", NULL },
2700 dundi_flush, "Flush DUNDi cache",
2701 flush_usage },
2703 { { "dundi", "no", "debug", NULL },
2704 dundi_no_debug, "Disable DUNDi debugging",
2705 no_debug_usage },
2707 { { "dundi", "show", "peers", NULL },
2708 dundi_show_peers, "Show defined DUNDi peers",
2709 show_peers_usage },
2711 { { "dundi", "show", "trans", NULL },
2712 dundi_show_trans, "Show active DUNDi transactions",
2713 show_trans_usage },
2715 { { "dundi", "show", "entityid", NULL },
2716 dundi_show_entityid, "Display Global Entity ID",
2717 show_entityid_usage },
2719 { { "dundi", "show", "mappings", NULL },
2720 dundi_show_mappings, "Show DUNDi mappings",
2721 show_mappings_usage },
2723 { { "dundi", "show", "precache", NULL },
2724 dundi_show_precache, "Show DUNDi precache",
2725 show_precache_usage },
2727 { { "dundi", "show", "requests", NULL },
2728 dundi_show_requests, "Show DUNDi requests",
2729 show_requests_usage },
2731 { { "dundi", "show", "peer", NULL },
2732 dundi_show_peer, "Show info on a specific DUNDi peer",
2733 show_peer_usage, complete_peer_4 },
2735 { { "dundi", "lookup", NULL },
2736 dundi_do_lookup, "Lookup a number in DUNDi",
2737 lookup_usage },
2739 { { "dundi", "precache", NULL },
2740 dundi_do_precache, "Precache a number in DUNDi",
2741 precache_usage },
2743 { { "dundi", "query", NULL },
2744 dundi_do_query, "Query a DUNDi EID",
2745 query_usage },
2748 static struct dundi_transaction *create_transaction(struct dundi_peer *p)
2750 struct dundi_transaction *trans;
2751 int tid;
2753 /* Don't allow creation of transactions to non-registered peers */
2754 if (p && !p->addr.sin_addr.s_addr)
2755 return NULL;
2756 tid = get_trans_id();
2757 if (tid < 1)
2758 return NULL;
2759 trans = ast_calloc(1, sizeof(*trans));
2760 if (trans) {
2761 if (global_storehistory) {
2762 trans->start = ast_tvnow();
2763 ast_set_flag(trans, FLAG_STOREHIST);
2765 trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER;
2766 trans->autokillid = -1;
2767 if (p) {
2768 apply_peer(trans, p);
2769 if (!p->sentfullkey)
2770 ast_set_flag(trans, FLAG_SENDFULLKEY);
2772 trans->strans = tid;
2773 AST_LIST_INSERT_HEAD(&alltrans, trans, all);
2775 return trans;
2778 static int dundi_xmit(struct dundi_packet *pack)
2780 int res;
2781 if (dundidebug)
2782 dundi_showframe(pack->h, 0, &pack->parent->addr, pack->datalen - sizeof(struct dundi_hdr));
2783 res = sendto(netsocket, pack->data, pack->datalen, 0, (struct sockaddr *)&pack->parent->addr, sizeof(pack->parent->addr));
2784 if (res < 0) {
2785 ast_log(LOG_WARNING, "Failed to transmit to '%s:%d': %s\n",
2786 ast_inet_ntoa(pack->parent->addr.sin_addr),
2787 ntohs(pack->parent->addr.sin_port), strerror(errno));
2789 if (res > 0)
2790 res = 0;
2791 return res;
2794 static void destroy_packet(struct dundi_packet *pack, int needfree)
2796 if (pack->parent)
2797 AST_LIST_REMOVE(&pack->parent->packets, pack, list);
2798 if (pack->retransid > -1)
2799 ast_sched_del(sched, pack->retransid);
2800 if (needfree)
2801 free(pack);
2802 else
2803 pack->retransid = -1;
2806 static void destroy_trans(struct dundi_transaction *trans, int fromtimeout)
2808 struct dundi_peer *peer;
2809 int ms;
2810 int x;
2811 int cnt;
2812 char eid_str[20];
2813 if (ast_test_flag(trans, FLAG_ISREG | FLAG_ISQUAL | FLAG_STOREHIST)) {
2814 AST_LIST_TRAVERSE(&peers, peer, list) {
2815 if (peer->regtrans == trans)
2816 peer->regtrans = NULL;
2817 if (peer->qualtrans == trans) {
2818 if (fromtimeout) {
2819 if (peer->lastms > -1)
2820 ast_log(LOG_NOTICE, "Peer '%s' has become UNREACHABLE!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
2821 peer->lastms = -1;
2822 } else {
2823 ms = ast_tvdiff_ms(ast_tvnow(), peer->qualtx);
2824 if (ms < 1)
2825 ms = 1;
2826 if (ms < peer->maxms) {
2827 if ((peer->lastms >= peer->maxms) || (peer->lastms < 0))
2828 ast_log(LOG_NOTICE, "Peer '%s' has become REACHABLE!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
2829 } else if (peer->lastms < peer->maxms) {
2830 ast_log(LOG_NOTICE, "Peer '%s' has become TOO LAGGED (%d ms)\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), ms);
2832 peer->lastms = ms;
2834 peer->qualtrans = NULL;
2836 if (ast_test_flag(trans, FLAG_STOREHIST)) {
2837 if (trans->parent && !ast_strlen_zero(trans->parent->number)) {
2838 if (!dundi_eid_cmp(&trans->them_eid, &peer->eid)) {
2839 peer->avgms = 0;
2840 cnt = 0;
2841 if (peer->lookups[DUNDI_TIMING_HISTORY-1])
2842 free(peer->lookups[DUNDI_TIMING_HISTORY-1]);
2843 for (x=DUNDI_TIMING_HISTORY-1;x>0;x--) {
2844 peer->lookuptimes[x] = peer->lookuptimes[x-1];
2845 peer->lookups[x] = peer->lookups[x-1];
2846 if (peer->lookups[x]) {
2847 peer->avgms += peer->lookuptimes[x];
2848 cnt++;
2851 peer->lookuptimes[0] = ast_tvdiff_ms(ast_tvnow(), trans->start);
2852 peer->lookups[0] = ast_malloc(strlen(trans->parent->number) + strlen(trans->parent->dcontext) + 2);
2853 if (peer->lookups[0]) {
2854 sprintf(peer->lookups[0], "%s@%s", trans->parent->number, trans->parent->dcontext);
2855 peer->avgms += peer->lookuptimes[0];
2856 cnt++;
2858 if (cnt)
2859 peer->avgms /= cnt;
2865 if (trans->parent) {
2866 /* Unlink from parent if appropriate */
2867 AST_LIST_REMOVE(&trans->parent->trans, trans, parentlist);
2868 if (AST_LIST_EMPTY(&trans->parent->trans)) {
2869 /* Wake up sleeper */
2870 if (trans->parent->pfds[1] > -1) {
2871 write(trans->parent->pfds[1], "killa!", 6);
2875 /* Unlink from all trans */
2876 AST_LIST_REMOVE(&alltrans, trans, all);
2877 destroy_packets(&trans->packets);
2878 destroy_packets(&trans->lasttrans);
2879 if (trans->autokillid > -1)
2880 ast_sched_del(sched, trans->autokillid);
2881 trans->autokillid = -1;
2882 if (trans->thread) {
2883 /* If used by a thread, mark as dead and be done */
2884 ast_set_flag(trans, FLAG_DEAD);
2885 } else
2886 free(trans);
2889 static int dundi_rexmit(void *data)
2891 struct dundi_packet *pack;
2892 int res;
2893 AST_LIST_LOCK(&peers);
2894 pack = data;
2895 if (pack->retrans < 1) {
2896 pack->retransid = -1;
2897 if (!ast_test_flag(pack->parent, FLAG_ISQUAL))
2898 ast_log(LOG_NOTICE, "Max retries exceeded to host '%s:%d' msg %d on call %d\n",
2899 ast_inet_ntoa(pack->parent->addr.sin_addr),
2900 ntohs(pack->parent->addr.sin_port), pack->h->oseqno, ntohs(pack->h->strans));
2901 destroy_trans(pack->parent, 1);
2902 res = 0;
2903 } else {
2904 /* Decrement retransmission, try again */
2905 pack->retrans--;
2906 dundi_xmit(pack);
2907 res = 1;
2909 AST_LIST_UNLOCK(&peers);
2910 return res;
2913 static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied)
2915 struct dundi_packet *pack;
2916 int res;
2917 int len;
2918 char eid_str[20];
2919 len = sizeof(struct dundi_packet) + sizeof(struct dundi_hdr) + (ied ? ied->pos : 0);
2920 /* Reserve enough space for encryption */
2921 if (ast_test_flag(trans, FLAG_ENCRYPT))
2922 len += 384;
2923 pack = ast_calloc(1, len);
2924 if (pack) {
2925 pack->h = (struct dundi_hdr *)(pack->data);
2926 if (cmdresp != DUNDI_COMMAND_ACK) {
2927 pack->retransid = ast_sched_add(sched, trans->retranstimer, dundi_rexmit, pack);
2928 pack->retrans = DUNDI_DEFAULT_RETRANS - 1;
2929 AST_LIST_INSERT_HEAD(&trans->packets, pack, list);
2931 pack->parent = trans;
2932 pack->h->strans = htons(trans->strans);
2933 pack->h->dtrans = htons(trans->dtrans);
2934 pack->h->iseqno = trans->iseqno;
2935 pack->h->oseqno = trans->oseqno;
2936 pack->h->cmdresp = cmdresp;
2937 pack->datalen = sizeof(struct dundi_hdr);
2938 if (ied) {
2939 memcpy(pack->h->ies, ied->buf, ied->pos);
2940 pack->datalen += ied->pos;
2942 if (final) {
2943 pack->h->cmdresp |= DUNDI_COMMAND_FINAL;
2944 ast_set_flag(trans, FLAG_FINAL);
2946 pack->h->cmdflags = flags;
2947 if (cmdresp != DUNDI_COMMAND_ACK) {
2948 trans->oseqno++;
2949 trans->oseqno = trans->oseqno % 256;
2951 trans->aseqno = trans->iseqno;
2952 /* If we have their public key, encrypt */
2953 if (ast_test_flag(trans, FLAG_ENCRYPT)) {
2954 switch(cmdresp) {
2955 case DUNDI_COMMAND_REGREQ:
2956 case DUNDI_COMMAND_REGRESPONSE:
2957 case DUNDI_COMMAND_DPDISCOVER:
2958 case DUNDI_COMMAND_DPRESPONSE:
2959 case DUNDI_COMMAND_EIDQUERY:
2960 case DUNDI_COMMAND_EIDRESPONSE:
2961 case DUNDI_COMMAND_PRECACHERQ:
2962 case DUNDI_COMMAND_PRECACHERP:
2963 if (dundidebug)
2964 dundi_showframe(pack->h, 2, &trans->addr, pack->datalen - sizeof(struct dundi_hdr));
2965 res = dundi_encrypt(trans, pack);
2966 break;
2967 default:
2968 res = 0;
2970 } else
2971 res = 0;
2972 if (!res)
2973 res = dundi_xmit(pack);
2974 if (res)
2975 ast_log(LOG_NOTICE, "Failed to send packet to '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));
2977 if (cmdresp == DUNDI_COMMAND_ACK)
2978 free(pack);
2979 return res;
2981 return -1;
2984 static int do_autokill(void *data)
2986 struct dundi_transaction *trans = data;
2987 char eid_str[20];
2988 ast_log(LOG_NOTICE, "Transaction to '%s' took too long to ACK, destroying\n",
2989 dundi_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));
2990 trans->autokillid = -1;
2991 destroy_trans(trans, 0); /* We could actually set it to 1 instead of 0, but we won't ;-) */
2992 return 0;
2995 static void dundi_ie_append_eid_appropriately(struct dundi_ie_data *ied, char *context, dundi_eid *eid, dundi_eid *us)
2997 struct dundi_peer *p;
2998 if (!dundi_eid_cmp(eid, us)) {
2999 dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
3000 return;
3002 AST_LIST_LOCK(&peers);
3003 AST_LIST_TRAVERSE(&peers, p, list) {
3004 if (!dundi_eid_cmp(&p->eid, eid)) {
3005 if (has_permission(&p->include, context))
3006 dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
3007 else
3008 dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
3009 break;
3012 if (!p)
3013 dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
3014 AST_LIST_UNLOCK(&peers);
3017 static int dundi_discover(struct dundi_transaction *trans)
3019 struct dundi_ie_data ied;
3020 int x;
3021 if (!trans->parent) {
3022 ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
3023 return -1;
3025 memset(&ied, 0, sizeof(ied));
3026 dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
3027 if (!dundi_eid_zero(&trans->us_eid))
3028 dundi_ie_append_eid(&ied, DUNDI_IE_EID_DIRECT, &trans->us_eid);
3029 for (x=0;x<trans->eidcount;x++)
3030 dundi_ie_append_eid_appropriately(&ied, trans->parent->dcontext, &trans->eids[x], &trans->us_eid);
3031 dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
3032 dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
3033 dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
3034 if (trans->parent->cbypass)
3035 dundi_ie_append(&ied, DUNDI_IE_CACHEBYPASS);
3036 if (trans->autokilltimeout)
3037 trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
3038 return dundi_send(trans, DUNDI_COMMAND_DPDISCOVER, 0, 0, &ied);
3041 static int precache_trans(struct dundi_transaction *trans, struct dundi_mapping *maps, int mapcount, int *minexp, int *foundanswers)
3043 struct dundi_ie_data ied;
3044 int x, res;
3045 int max = 999999;
3046 int expiration = dundi_cache_time;
3047 int ouranswers=0;
3048 dundi_eid *avoid[1] = { NULL, };
3049 int direct[1] = { 0, };
3050 struct dundi_result dr[MAX_RESULTS];
3051 struct dundi_hint_metadata hmd;
3052 if (!trans->parent) {
3053 ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
3054 return -1;
3056 memset(&hmd, 0, sizeof(hmd));
3057 memset(&dr, 0, sizeof(dr));
3058 /* Look up the answers we're going to include */
3059 for (x=0;x<mapcount;x++)
3060 ouranswers = dundi_lookup_local(dr, maps + x, trans->parent->number, &trans->us_eid, ouranswers, &hmd);
3061 if (ouranswers < 0)
3062 ouranswers = 0;
3063 for (x=0;x<ouranswers;x++) {
3064 if (dr[x].weight < max)
3065 max = dr[x].weight;
3067 if (max) {
3068 /* If we do not have a canonical result, keep looking */
3069 res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, trans->parent->dcontext, trans->parent->number, trans->ttl, 1, &hmd, &expiration, 0, 1, &trans->them_eid, avoid, direct);
3070 if (res > 0) {
3071 /* Append answer in result */
3072 ouranswers += res;
3076 if (ouranswers > 0) {
3077 *foundanswers += ouranswers;
3078 memset(&ied, 0, sizeof(ied));
3079 dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
3080 if (!dundi_eid_zero(&trans->us_eid))
3081 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
3082 for (x=0;x<trans->eidcount;x++)
3083 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
3084 dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
3085 dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
3086 dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
3087 for (x=0;x<ouranswers;x++) {
3088 /* Add answers */
3089 if (dr[x].expiration && (expiration > dr[x].expiration))
3090 expiration = dr[x].expiration;
3091 dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
3093 dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
3094 dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
3095 if (trans->autokilltimeout)
3096 trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
3097 if (expiration < *minexp)
3098 *minexp = expiration;
3099 return dundi_send(trans, DUNDI_COMMAND_PRECACHERQ, 0, 0, &ied);
3100 } else {
3101 /* Oops, nothing to send... */
3102 destroy_trans(trans, 0);
3103 return 0;
3107 static int dundi_query(struct dundi_transaction *trans)
3109 struct dundi_ie_data ied;
3110 int x;
3111 if (!trans->parent) {
3112 ast_log(LOG_WARNING, "Tried to query a transaction with no parent?!?\n");
3113 return -1;
3115 memset(&ied, 0, sizeof(ied));
3116 dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
3117 if (!dundi_eid_zero(&trans->us_eid))
3118 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
3119 for (x=0;x<trans->eidcount;x++)
3120 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
3121 dundi_ie_append_eid(&ied, DUNDI_IE_REQEID, &trans->parent->query_eid);
3122 dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
3123 dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
3124 if (trans->autokilltimeout)
3125 trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
3126 return dundi_send(trans, DUNDI_COMMAND_EIDQUERY, 0, 0, &ied);
3129 static int discover_transactions(struct dundi_request *dr)
3131 struct dundi_transaction *trans;
3132 AST_LIST_LOCK(&peers);
3133 AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
3134 dundi_discover(trans);
3136 AST_LIST_UNLOCK(&peers);
3137 return 0;
3140 static int precache_transactions(struct dundi_request *dr, struct dundi_mapping *maps, int mapcount, int *expiration, int *foundanswers)
3142 struct dundi_transaction *trans;
3144 /* Mark all as "in thread" so they don't disappear */
3145 AST_LIST_LOCK(&peers);
3146 AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
3147 if (trans->thread)
3148 ast_log(LOG_WARNING, "This shouldn't happen, really...\n");
3149 trans->thread = 1;
3151 AST_LIST_UNLOCK(&peers);
3153 AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
3154 if (!ast_test_flag(trans, FLAG_DEAD))
3155 precache_trans(trans, maps, mapcount, expiration, foundanswers);
3158 /* Cleanup any that got destroyed in the mean time */
3159 AST_LIST_LOCK(&peers);
3160 AST_LIST_TRAVERSE_SAFE_BEGIN(&dr->trans, trans, parentlist) {
3161 trans->thread = 0;
3162 if (ast_test_flag(trans, FLAG_DEAD)) {
3163 ast_log(LOG_DEBUG, "Our transaction went away!\n");
3164 /* This is going to remove the transaction from the dundi_request's list, as well
3165 * as the global transactions list */
3166 destroy_trans(trans, 0);
3169 AST_LIST_TRAVERSE_SAFE_END
3170 AST_LIST_UNLOCK(&peers);
3172 return 0;
3175 static int query_transactions(struct dundi_request *dr)
3177 struct dundi_transaction *trans;
3179 AST_LIST_LOCK(&peers);
3180 AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
3181 dundi_query(trans);
3183 AST_LIST_UNLOCK(&peers);
3185 return 0;
3188 static int optimize_transactions(struct dundi_request *dr, int order)
3190 /* Minimize the message propagation through DUNDi by
3191 alerting the network to hops which should be not be considered */
3192 struct dundi_transaction *trans;
3193 struct dundi_peer *peer;
3194 dundi_eid tmp;
3195 int x;
3196 int needpush;
3198 AST_LIST_LOCK(&peers);
3199 AST_LIST_TRAVERSE(&dr->trans, trans, parentlist) {
3200 /* Pop off the true root */
3201 if (trans->eidcount) {
3202 tmp = trans->eids[--trans->eidcount];
3203 needpush = 1;
3204 } else {
3205 tmp = trans->us_eid;
3206 needpush = 0;
3209 AST_LIST_TRAVERSE(&peers, peer, list) {
3210 if (has_permission(&peer->include, dr->dcontext) &&
3211 dundi_eid_cmp(&peer->eid, &trans->them_eid) &&
3212 (peer->order <= order)) {
3213 /* For each other transaction, make sure we don't
3214 ask this EID about the others if they're not
3215 already in the list */
3216 if (!dundi_eid_cmp(&tmp, &peer->eid))
3217 x = -1;
3218 else {
3219 for (x=0;x<trans->eidcount;x++) {
3220 if (!dundi_eid_cmp(&trans->eids[x], &peer->eid))
3221 break;
3224 if (x == trans->eidcount) {
3225 /* Nope not in the list, if needed, add us at the end since we're the source */
3226 if (trans->eidcount < DUNDI_MAX_STACK - needpush) {
3227 trans->eids[trans->eidcount++] = peer->eid;
3228 /* Need to insert the real root (or us) at the bottom now as
3229 a requirement now. */
3230 needpush = 1;
3235 /* If necessary, push the true root back on the end */
3236 if (needpush)
3237 trans->eids[trans->eidcount++] = tmp;
3239 AST_LIST_UNLOCK(&peers);
3241 return 0;
3244 static int append_transaction(struct dundi_request *dr, struct dundi_peer *p, int ttl, dundi_eid *avoid[])
3246 struct dundi_transaction *trans;
3247 int x;
3248 char eid_str[20];
3249 char eid_str2[20];
3251 /* Ignore if not registered */
3252 if (!p->addr.sin_addr.s_addr)
3253 return 0;
3254 if (p->maxms && ((p->lastms < 0) || (p->lastms >= p->maxms)))
3255 return 0;
3256 if (ast_strlen_zero(dr->number))
3257 ast_log(LOG_DEBUG, "Will query peer '%s' for '%s' (context '%s')\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &p->eid), dundi_eid_to_str(eid_str2, sizeof(eid_str2), &dr->query_eid), dr->dcontext);
3258 else
3259 ast_log(LOG_DEBUG, "Will query peer '%s' for '%s@%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &p->eid), dr->number, dr->dcontext);
3260 trans = create_transaction(p);
3261 if (!trans)
3262 return -1;
3263 trans->parent = dr;
3264 trans->ttl = ttl;
3265 for (x = 0; avoid[x] && (x < DUNDI_MAX_STACK); x++)
3266 trans->eids[x] = *avoid[x];
3267 trans->eidcount = x;
3268 AST_LIST_INSERT_HEAD(&dr->trans, trans, parentlist);
3270 return 0;
3273 static void cancel_request(struct dundi_request *dr)
3275 struct dundi_transaction *trans;
3277 AST_LIST_LOCK(&peers);
3278 while ((trans = AST_LIST_REMOVE_HEAD(&dr->trans, parentlist))) {
3279 /* Orphan transaction from request */
3280 trans->parent = NULL;
3281 /* Send final cancel */
3282 dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
3284 AST_LIST_UNLOCK(&peers);
3287 static void abort_request(struct dundi_request *dr)
3289 struct dundi_transaction *trans;
3291 AST_LIST_LOCK(&peers);
3292 while ((trans = AST_LIST_FIRST(&dr->trans))) {
3293 /* This will remove the transaction from the list */
3294 destroy_trans(trans, 0);
3296 AST_LIST_UNLOCK(&peers);
3299 static void build_transactions(struct dundi_request *dr, int ttl, int order, int *foundcache, int *skipped, int blockempty, int nocache, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int directs[])
3301 struct dundi_peer *p;
3302 int x;
3303 int res;
3304 int pass;
3305 int allowconnect;
3306 char eid_str[20];
3307 AST_LIST_LOCK(&peers);
3308 AST_LIST_TRAVERSE(&peers, p, list) {
3309 if (modeselect == 1) {
3310 /* Send the precache to push upstreams only! */
3311 pass = has_permission(&p->permit, dr->dcontext) && (p->pcmodel & DUNDI_MODEL_OUTBOUND);
3312 allowconnect = 1;
3313 } else {
3314 /* Normal lookup / EID query */
3315 pass = has_permission(&p->include, dr->dcontext);
3316 allowconnect = p->model & DUNDI_MODEL_OUTBOUND;
3318 if (skip) {
3319 if (!dundi_eid_cmp(skip, &p->eid))
3320 pass = 0;
3322 if (pass) {
3323 if (p->order <= order) {
3324 /* Check order first, then check cache, regardless of
3325 omissions, this gets us more likely to not have an
3326 affected answer. */
3327 if((nocache || !(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration)))) {
3328 res = 0;
3329 /* Make sure we haven't already seen it and that it won't
3330 affect our answer */
3331 for (x=0;avoid[x];x++) {
3332 if (!dundi_eid_cmp(avoid[x], &p->eid) || !dundi_eid_cmp(avoid[x], &p->us_eid)) {
3333 /* If not a direct connection, it affects our answer */
3334 if (directs && !directs[x])
3335 ast_clear_flag_nonstd(dr->hmd, DUNDI_HINT_UNAFFECTED);
3336 break;
3339 /* Make sure we can ask */
3340 if (allowconnect) {
3341 if (!avoid[x] && (!blockempty || !dundi_eid_zero(&p->us_eid))) {
3342 /* Check for a matching or 0 cache entry */
3343 append_transaction(dr, p, ttl, avoid);
3344 } else
3345 ast_log(LOG_DEBUG, "Avoiding '%s' in transaction\n", dundi_eid_to_str(eid_str, sizeof(eid_str), avoid[x]));
3348 *foundcache |= res;
3349 } else if (!*skipped || (p->order < *skipped))
3350 *skipped = p->order;
3353 AST_LIST_UNLOCK(&peers);
3356 static int register_request(struct dundi_request *dr, struct dundi_request **pending)
3358 struct dundi_request *cur;
3359 int res=0;
3360 char eid_str[20];
3361 AST_LIST_LOCK(&peers);
3362 AST_LIST_TRAVERSE(&requests, cur, list) {
3363 if (option_debug)
3364 ast_log(LOG_DEBUG, "Checking '%s@%s' vs '%s@%s'\n", cur->dcontext, cur->number,
3365 dr->dcontext, dr->number);
3366 if (!strcasecmp(cur->dcontext, dr->dcontext) &&
3367 !strcasecmp(cur->number, dr->number) &&
3368 (!dundi_eid_cmp(&cur->root_eid, &dr->root_eid) || (cur->crc32 == dr->crc32))) {
3369 ast_log(LOG_DEBUG, "Found existing query for '%s@%s' for '%s' crc '%08lx'\n",
3370 cur->dcontext, cur->number, dundi_eid_to_str(eid_str, sizeof(eid_str), &cur->root_eid), cur->crc32);
3371 *pending = cur;
3372 res = 1;
3373 break;
3376 if (!res) {
3377 ast_log(LOG_DEBUG, "Registering request for '%s@%s' on behalf of '%s' crc '%08lx'\n",
3378 dr->number, dr->dcontext, dundi_eid_to_str(eid_str, sizeof(eid_str), &dr->root_eid), dr->crc32);
3379 /* Go ahead and link us in since nobody else is searching for this */
3380 AST_LIST_INSERT_HEAD(&requests, dr, list);
3381 *pending = NULL;
3383 AST_LIST_UNLOCK(&peers);
3384 return res;
3387 static void unregister_request(struct dundi_request *dr)
3389 AST_LIST_LOCK(&peers);
3390 AST_LIST_REMOVE(&requests, dr, list);
3391 AST_LIST_UNLOCK(&peers);
3394 static int check_request(struct dundi_request *dr)
3396 struct dundi_request *cur;
3398 AST_LIST_LOCK(&peers);
3399 AST_LIST_TRAVERSE(&requests, cur, list) {
3400 if (cur == dr)
3401 break;
3403 AST_LIST_UNLOCK(&peers);
3405 return cur ? 1 : 0;
3408 static unsigned long avoid_crc32(dundi_eid *avoid[])
3410 /* Idea is that we're calculating a checksum which is independent of
3411 the order that the EID's are listed in */
3412 unsigned long acrc32 = 0;
3413 int x;
3414 for (x=0;avoid[x];x++) {
3415 /* Order doesn't matter */
3416 if (avoid[x+1]) {
3417 acrc32 ^= crc32(0L, (unsigned char *)avoid[x], sizeof(dundi_eid));
3420 return acrc32;
3423 static int dundi_lookup_internal(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int ttl, int blockempty, struct dundi_hint_metadata *hmd, int *expiration, int cbypass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[])
3425 int res;
3426 struct dundi_request dr, *pending;
3427 dundi_eid *rooteid=NULL;
3428 int x;
3429 int ttlms;
3430 int ms;
3431 int foundcache;
3432 int skipped=0;
3433 int order=0;
3434 char eid_str[20];
3435 struct timeval start;
3437 /* Don't do anthing for a hungup channel */
3438 if (chan && chan->_softhangup)
3439 return 0;
3441 ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
3443 for (x=0;avoid[x];x++)
3444 rooteid = avoid[x];
3445 /* Now perform real check */
3446 memset(&dr, 0, sizeof(dr));
3447 if (pipe(dr.pfds)) {
3448 ast_log(LOG_WARNING, "pipe failed: %s\n" , strerror(errno));
3449 return -1;
3451 dr.dr = result;
3452 dr.hmd = hmd;
3453 dr.maxcount = maxret;
3454 dr.expiration = *expiration;
3455 dr.cbypass = cbypass;
3456 dr.crc32 = avoid_crc32(avoid);
3457 ast_copy_string(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext));
3458 ast_copy_string(dr.number, number, sizeof(dr.number));
3459 if (rooteid)
3460 dr.root_eid = *rooteid;
3461 res = register_request(&dr, &pending);
3462 if (res) {
3463 /* Already a request */
3464 if (rooteid && !dundi_eid_cmp(&dr.root_eid, &pending->root_eid)) {
3465 /* This is on behalf of someone else. Go ahead and close this out since
3466 they'll get their answer anyway. */
3467 ast_log(LOG_DEBUG, "Oooh, duplicate request for '%s@%s' for '%s'\n",
3468 dr.number,dr.dcontext,dundi_eid_to_str(eid_str, sizeof(eid_str), &dr.root_eid));
3469 close(dr.pfds[0]);
3470 close(dr.pfds[1]);
3471 return -2;
3472 } else {
3473 /* Wait for the cache to populate */
3474 ast_log(LOG_DEBUG, "Waiting for similar request for '%s@%s' for '%s'\n",
3475 dr.number,dr.dcontext,dundi_eid_to_str(eid_str, sizeof(eid_str), &pending->root_eid));
3476 start = ast_tvnow();
3477 while(check_request(pending) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms) && (!chan || !chan->_softhangup)) {
3478 /* XXX Would be nice to have a way to poll/select here XXX */
3479 /* XXX this is a busy wait loop!!! */
3480 usleep(1);
3482 /* Continue on as normal, our cache should kick in */
3485 /* Create transactions */
3486 do {
3487 order = skipped;
3488 skipped = 0;
3489 foundcache = 0;
3490 build_transactions(&dr, ttl, order, &foundcache, &skipped, blockempty, cbypass, modeselect, skip, avoid, direct);
3491 } while (skipped && !foundcache && AST_LIST_EMPTY(&dr.trans));
3492 /* If no TTL, abort and return 0 now after setting TTL expired hint. Couldn't
3493 do this earlier because we didn't know if we were going to have transactions
3494 or not. */
3495 if (!ttl) {
3496 ast_set_flag_nonstd(hmd, DUNDI_HINT_TTL_EXPIRED);
3497 abort_request(&dr);
3498 unregister_request(&dr);
3499 close(dr.pfds[0]);
3500 close(dr.pfds[1]);
3501 return 0;
3504 /* Optimize transactions */
3505 optimize_transactions(&dr, order);
3506 /* Actually perform transactions */
3507 discover_transactions(&dr);
3508 /* Wait for transaction to come back */
3509 start = ast_tvnow();
3510 while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms) && (!chan || !chan->_softhangup)) {
3511 ms = 100;
3512 ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL);
3514 if (chan && chan->_softhangup)
3515 ast_log(LOG_DEBUG, "Hrm, '%s' hungup before their query for %s@%s finished\n", chan->name, dr.number, dr.dcontext);
3516 cancel_request(&dr);
3517 unregister_request(&dr);
3518 res = dr.respcount;
3519 *expiration = dr.expiration;
3520 close(dr.pfds[0]);
3521 close(dr.pfds[1]);
3522 return res;
3525 int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int cbypass)
3527 struct dundi_hint_metadata hmd;
3528 dundi_eid *avoid[1] = { NULL, };
3529 int direct[1] = { 0, };
3530 int expiration = dundi_cache_time;
3531 memset(&hmd, 0, sizeof(hmd));
3532 hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
3533 return dundi_lookup_internal(result, maxret, chan, dcontext, number, dundi_ttl, 0, &hmd, &expiration, cbypass, 0, NULL, avoid, direct);
3536 static void reschedule_precache(const char *number, const char *context, int expiration)
3538 int len;
3539 struct dundi_precache_queue *qe, *prev;
3541 AST_LIST_LOCK(&pcq);
3542 AST_LIST_TRAVERSE_SAFE_BEGIN(&pcq, qe, list) {
3543 if (!strcmp(number, qe->number) && !strcasecmp(context, qe->context)) {
3544 AST_LIST_REMOVE_CURRENT(&pcq, list);
3545 break;
3548 AST_LIST_TRAVERSE_SAFE_END
3549 if (!qe) {
3550 len = sizeof(*qe);
3551 len += strlen(number) + 1;
3552 len += strlen(context) + 1;
3553 if (!(qe = ast_calloc(1, len))) {
3554 AST_LIST_UNLOCK(&pcq);
3555 return;
3557 strcpy(qe->number, number);
3558 qe->context = qe->number + strlen(number) + 1;
3559 strcpy(qe->context, context);
3561 time(&qe->expiration);
3562 qe->expiration += expiration;
3563 if ((prev = AST_LIST_FIRST(&pcq))) {
3564 while (AST_LIST_NEXT(prev, list) && ((AST_LIST_NEXT(prev, list))->expiration <= qe->expiration))
3565 prev = AST_LIST_NEXT(prev, list);
3566 AST_LIST_INSERT_AFTER(&pcq, prev, qe, list);
3567 } else
3568 AST_LIST_INSERT_HEAD(&pcq, qe, list);
3569 AST_LIST_UNLOCK(&pcq);
3572 static void dundi_precache_full(void)
3574 struct dundi_mapping *cur;
3575 struct ast_context *con;
3576 struct ast_exten *e;
3578 AST_LIST_TRAVERSE(&mappings, cur, list) {
3579 ast_log(LOG_NOTICE, "Should precache context '%s'\n", cur->dcontext);
3580 ast_lock_contexts();
3581 con = ast_walk_contexts(NULL);
3582 while (con) {
3583 if (!strcasecmp(cur->lcontext, ast_get_context_name(con))) {
3584 /* Found the match, now queue them all up */
3585 ast_lock_context(con);
3586 e = ast_walk_context_extensions(con, NULL);
3587 while (e) {
3588 reschedule_precache(ast_get_extension_name(e), cur->dcontext, 0);
3589 e = ast_walk_context_extensions(con, e);
3591 ast_unlock_context(con);
3593 con = ast_walk_contexts(con);
3595 ast_unlock_contexts();
3599 static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[])
3601 struct dundi_request dr;
3602 struct dundi_hint_metadata hmd;
3603 struct dundi_result dr2[MAX_RESULTS];
3604 struct timeval start;
3605 struct dundi_mapping *maps = NULL, *cur;
3606 int nummaps = 0;
3607 int foundanswers;
3608 int foundcache, skipped, ttlms, ms;
3609 if (!context)
3610 context = "e164";
3611 ast_log(LOG_DEBUG, "Precache internal (%s@%s)!\n", number, context);
3613 AST_LIST_LOCK(&peers);
3614 AST_LIST_TRAVERSE(&mappings, cur, list) {
3615 if (!strcasecmp(cur->dcontext, context))
3616 nummaps++;
3618 if (nummaps) {
3619 maps = alloca(nummaps * sizeof(*maps));
3620 nummaps = 0;
3621 if (maps) {
3622 AST_LIST_TRAVERSE(&mappings, cur, list) {
3623 if (!strcasecmp(cur->dcontext, context))
3624 maps[nummaps++] = *cur;
3628 AST_LIST_UNLOCK(&peers);
3629 if (!nummaps || !maps)
3630 return -1;
3631 ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
3632 memset(&dr2, 0, sizeof(dr2));
3633 memset(&dr, 0, sizeof(dr));
3634 memset(&hmd, 0, sizeof(hmd));
3635 dr.dr = dr2;
3636 ast_copy_string(dr.number, number, sizeof(dr.number));
3637 ast_copy_string(dr.dcontext, context ? context : "e164", sizeof(dr.dcontext));
3638 dr.maxcount = MAX_RESULTS;
3639 dr.expiration = dundi_cache_time;
3640 dr.hmd = &hmd;
3641 dr.pfds[0] = dr.pfds[1] = -1;
3642 pipe(dr.pfds);
3643 build_transactions(&dr, ttl, 0, &foundcache, &skipped, 0, 1, 1, NULL, avoids, NULL);
3644 optimize_transactions(&dr, 0);
3645 foundanswers = 0;
3646 precache_transactions(&dr, maps, nummaps, &dr.expiration, &foundanswers);
3647 if (foundanswers) {
3648 if (dr.expiration > 0)
3649 reschedule_precache(dr.number, dr.dcontext, dr.expiration);
3650 else
3651 ast_log(LOG_NOTICE, "Weird, expiration = %d, but need to precache for %s@%s?!\n", dr.expiration, dr.number, dr.dcontext);
3653 start = ast_tvnow();
3654 while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms)) {
3655 if (dr.pfds[0] > -1) {
3656 ms = 100;
3657 ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL);
3658 } else
3659 usleep(1);
3661 cancel_request(&dr);
3662 if (dr.pfds[0] > -1) {
3663 close(dr.pfds[0]);
3664 close(dr.pfds[1]);
3666 return 0;
3669 int dundi_precache(const char *context, const char *number)
3671 dundi_eid *avoid[1] = { NULL, };
3672 return dundi_precache_internal(context, number, dundi_ttl, avoid);
3675 static int dundi_query_eid_internal(struct dundi_entity_info *dei, const char *dcontext, dundi_eid *eid, struct dundi_hint_metadata *hmd, int ttl, int blockempty, dundi_eid *avoid[])
3677 int res;
3678 struct dundi_request dr;
3679 dundi_eid *rooteid=NULL;
3680 int x;
3681 int ttlms;
3682 int skipped=0;
3683 int foundcache=0;
3684 struct timeval start;
3686 ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
3688 for (x=0;avoid[x];x++)
3689 rooteid = avoid[x];
3690 /* Now perform real check */
3691 memset(&dr, 0, sizeof(dr));
3692 dr.hmd = hmd;
3693 dr.dei = dei;
3694 dr.pfds[0] = dr.pfds[1] = -1;
3695 ast_copy_string(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext));
3696 memcpy(&dr.query_eid, eid, sizeof(dr.query_eid));
3697 if (rooteid)
3698 dr.root_eid = *rooteid;
3699 /* Create transactions */
3700 build_transactions(&dr, ttl, 9999, &foundcache, &skipped, blockempty, 0, 0, NULL, avoid, NULL);
3702 /* If no TTL, abort and return 0 now after setting TTL expired hint. Couldn't
3703 do this earlier because we didn't know if we were going to have transactions
3704 or not. */
3705 if (!ttl) {
3706 ast_set_flag_nonstd(hmd, DUNDI_HINT_TTL_EXPIRED);
3707 return 0;
3710 /* Optimize transactions */
3711 optimize_transactions(&dr, 9999);
3712 /* Actually perform transactions */
3713 query_transactions(&dr);
3714 /* Wait for transaction to come back */
3715 start = ast_tvnow();
3716 while (!AST_LIST_EMPTY(&dr.trans) && (ast_tvdiff_ms(ast_tvnow(), start) < ttlms))
3717 usleep(1);
3718 res = dr.respcount;
3719 return res;
3722 int dundi_query_eid(struct dundi_entity_info *dei, const char *dcontext, dundi_eid eid)
3724 dundi_eid *avoid[1] = { NULL, };
3725 struct dundi_hint_metadata hmd;
3726 memset(&hmd, 0, sizeof(hmd));
3727 return dundi_query_eid_internal(dei, dcontext, &eid, &hmd, dundi_ttl, 0, avoid);
3730 static int dundifunc_read(struct ast_channel *chan, char *cmd, char *num, char *buf, size_t len)
3732 char *context;
3733 char *opts;
3734 int results;
3735 int x;
3736 int bypass = 0;
3737 struct ast_module_user *u;
3738 struct dundi_result dr[MAX_RESULTS];
3740 buf[0] = '\0';
3742 if (ast_strlen_zero(num)) {
3743 ast_log(LOG_WARNING, "DUNDILOOKUP requires an argument (number)\n");
3744 return -1;
3747 u = ast_module_user_add(chan);
3749 context = strchr(num, '|');
3750 if (context) {
3751 *context++ = '\0';
3752 opts = strchr(context, '|');
3753 if (opts) {
3754 *opts++ = '\0';
3755 if (strchr(opts, 'b'))
3756 bypass = 1;
3760 if (ast_strlen_zero(context))
3761 context = "e164";
3763 results = dundi_lookup(dr, MAX_RESULTS, NULL, context, num, bypass);
3764 if (results > 0) {
3765 sort_results(dr, results);
3766 for (x = 0; x < results; x++) {
3767 if (ast_test_flag(dr + x, DUNDI_FLAG_EXISTS)) {
3768 snprintf(buf, len, "%s/%s", dr[x].tech, dr[x].dest);
3769 break;
3774 ast_module_user_remove(u);
3776 return 0;
3779 /*! DUNDILOOKUP
3780 * \ingroup functions
3783 static struct ast_custom_function dundi_function = {
3784 .name = "DUNDILOOKUP",
3785 .synopsis = "Do a DUNDi lookup of a phone number.",
3786 .syntax = "DUNDILOOKUP(number[|context[|options]])",
3787 .desc = "This will do a DUNDi lookup of the given phone number.\n"
3788 "If no context is given, the default will be e164. The result of\n"
3789 "this function will the Technology/Resource found in the DUNDi\n"
3790 "lookup. If no results were found, the result will be blank.\n"
3791 "If the 'b' option is specified, the internal DUNDi cache will\n"
3792 "be bypassed.\n",
3793 .read = dundifunc_read,
3796 static void mark_peers(void)
3798 struct dundi_peer *peer;
3799 AST_LIST_LOCK(&peers);
3800 AST_LIST_TRAVERSE(&peers, peer, list) {
3801 peer->dead = 1;
3803 AST_LIST_UNLOCK(&peers);
3806 static void mark_mappings(void)
3808 struct dundi_mapping *map;
3810 AST_LIST_LOCK(&peers);
3811 AST_LIST_TRAVERSE(&mappings, map, list) {
3812 map->dead = 1;
3814 AST_LIST_UNLOCK(&peers);
3817 static void destroy_permissions(struct permissionlist *permlist)
3819 struct permission *perm;
3821 while ((perm = AST_LIST_REMOVE_HEAD(permlist, list)))
3822 free(perm);
3825 static void destroy_peer(struct dundi_peer *peer)
3827 if (peer->registerid > -1)
3828 ast_sched_del(sched, peer->registerid);
3829 if (peer->regtrans)
3830 destroy_trans(peer->regtrans, 0);
3831 if (peer->qualifyid > -1)
3832 ast_sched_del(sched, peer->qualifyid);
3833 destroy_permissions(&peer->permit);
3834 destroy_permissions(&peer->include);
3835 free(peer);
3838 static void destroy_map(struct dundi_mapping *map)
3840 free(map);
3843 static void prune_peers(void)
3845 struct dundi_peer *peer;
3847 AST_LIST_LOCK(&peers);
3848 AST_LIST_TRAVERSE_SAFE_BEGIN(&peers, peer, list) {
3849 if (peer->dead) {
3850 AST_LIST_REMOVE_CURRENT(&peers, list);
3851 destroy_peer(peer);
3854 AST_LIST_TRAVERSE_SAFE_END
3855 AST_LIST_UNLOCK(&peers);
3858 static void prune_mappings(void)
3860 struct dundi_mapping *map;
3862 AST_LIST_LOCK(&peers);
3863 AST_LIST_TRAVERSE_SAFE_BEGIN(&mappings, map, list) {
3864 if (map->dead) {
3865 AST_LIST_REMOVE_CURRENT(&mappings, list);
3866 destroy_map(map);
3869 AST_LIST_TRAVERSE_SAFE_END
3870 AST_LIST_UNLOCK(&peers);
3873 static void append_permission(struct permissionlist *permlist, char *s, int allow)
3875 struct permission *perm;
3877 if (!(perm = ast_calloc(1, sizeof(*perm) + strlen(s) + 1)))
3878 return;
3880 strcpy(perm->name, s);
3881 perm->allow = allow;
3883 AST_LIST_INSERT_TAIL(permlist, perm, list);
3886 #define MAX_OPTS 128
3888 static void build_mapping(char *name, char *value)
3890 char *t, *fields[MAX_OPTS];
3891 struct dundi_mapping *map;
3892 int x;
3893 int y;
3895 t = ast_strdupa(value);
3897 AST_LIST_TRAVERSE(&mappings, map, list) {
3898 /* Find a double match */
3899 if (!strcasecmp(map->dcontext, name) &&
3900 (!strncasecmp(map->lcontext, value, strlen(map->lcontext)) &&
3901 (!value[strlen(map->lcontext)] ||
3902 (value[strlen(map->lcontext)] == ','))))
3903 break;
3905 if (!map) {
3906 if (!(map = ast_calloc(1, sizeof(*map))))
3907 return;
3908 AST_LIST_INSERT_HEAD(&mappings, map, list);
3909 map->dead = 1;
3911 map->options = 0;
3912 memset(fields, 0, sizeof(fields));
3913 x = 0;
3914 while (t && x < MAX_OPTS) {
3915 fields[x++] = t;
3916 t = strchr(t, ',');
3917 if (t) {
3918 *t = '\0';
3919 t++;
3921 } /* Russell was here, arrrr! */
3922 if ((x == 1) && ast_strlen_zero(fields[0])) {
3923 /* Placeholder mapping */
3924 ast_copy_string(map->dcontext, name, sizeof(map->dcontext));
3925 map->dead = 0;
3926 } else if (x >= 4) {
3927 ast_copy_string(map->dcontext, name, sizeof(map->dcontext));
3928 ast_copy_string(map->lcontext, fields[0], sizeof(map->lcontext));
3929 if ((sscanf(fields[1], "%d", &map->weight) == 1) && (map->weight >= 0) && (map->weight < 60000)) {
3930 ast_copy_string(map->dest, fields[3], sizeof(map->dest));
3931 if ((map->tech = str2tech(fields[2]))) {
3932 map->dead = 0;
3934 } else {
3935 ast_log(LOG_WARNING, "Invalid weight '%s' specified, deleting entry '%s/%s'\n", fields[1], map->dcontext, map->lcontext);
3937 for (y = 4;y < x; y++) {
3938 if (!strcasecmp(fields[y], "nounsolicited"))
3939 map->options |= DUNDI_FLAG_NOUNSOLICITED;
3940 else if (!strcasecmp(fields[y], "nocomunsolicit"))
3941 map->options |= DUNDI_FLAG_NOCOMUNSOLICIT;
3942 else if (!strcasecmp(fields[y], "residential"))
3943 map->options |= DUNDI_FLAG_RESIDENTIAL;
3944 else if (!strcasecmp(fields[y], "commercial"))
3945 map->options |= DUNDI_FLAG_COMMERCIAL;
3946 else if (!strcasecmp(fields[y], "mobile"))
3947 map->options |= DUNDI_FLAG_MOBILE;
3948 else if (!strcasecmp(fields[y], "nopartial"))
3949 map->options |= DUNDI_FLAG_INTERNAL_NOPARTIAL;
3950 else
3951 ast_log(LOG_WARNING, "Don't know anything about option '%s'\n", fields[y]);
3953 } else
3954 ast_log(LOG_WARNING, "Expected at least %d arguments in map, but got only %d\n", 4, x);
3957 /* \note Called with the peers list already locked */
3958 static int do_register(void *data)
3960 struct dundi_ie_data ied;
3961 struct dundi_peer *peer = data;
3962 char eid_str[20];
3963 char eid_str2[20];
3964 ast_log(LOG_DEBUG, "Register us as '%s' to '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->us_eid), dundi_eid_to_str(eid_str2, sizeof(eid_str2), &peer->eid));
3965 peer->registerid = ast_sched_add(sched, default_expiration * 1000, do_register, data);
3966 /* Destroy old transaction if there is one */
3967 if (peer->regtrans)
3968 destroy_trans(peer->regtrans, 0);
3969 peer->regtrans = create_transaction(peer);
3970 if (peer->regtrans) {
3971 ast_set_flag(peer->regtrans, FLAG_ISREG);
3972 memset(&ied, 0, sizeof(ied));
3973 dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
3974 dundi_ie_append_eid(&ied, DUNDI_IE_EID, &peer->regtrans->us_eid);
3975 dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, default_expiration);
3976 dundi_send(peer->regtrans, DUNDI_COMMAND_REGREQ, 0, 0, &ied);
3978 } else
3979 ast_log(LOG_NOTICE, "Unable to create new transaction for registering to '%s'!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
3981 return 0;
3984 static int do_qualify(void *data)
3986 struct dundi_peer *peer;
3987 peer = data;
3988 peer->qualifyid = -1;
3989 qualify_peer(peer, 0);
3990 return 0;
3993 static void qualify_peer(struct dundi_peer *peer, int schedonly)
3995 int when;
3996 if (peer->qualifyid > -1)
3997 ast_sched_del(sched, peer->qualifyid);
3998 peer->qualifyid = -1;
3999 if (peer->qualtrans)
4000 destroy_trans(peer->qualtrans, 0);
4001 peer->qualtrans = NULL;
4002 if (peer->maxms > 0) {
4003 when = 60000;
4004 if (peer->lastms < 0)
4005 when = 10000;
4006 if (schedonly)
4007 when = 5000;
4008 peer->qualifyid = ast_sched_add(sched, when, do_qualify, peer);
4009 if (!schedonly)
4010 peer->qualtrans = create_transaction(peer);
4011 if (peer->qualtrans) {
4012 peer->qualtx = ast_tvnow();
4013 ast_set_flag(peer->qualtrans, FLAG_ISQUAL);
4014 dundi_send(peer->qualtrans, DUNDI_COMMAND_NULL, 0, 1, NULL);
4018 static void populate_addr(struct dundi_peer *peer, dundi_eid *eid)
4020 char data[256];
4021 char *c;
4022 int port, expire;
4023 char eid_str[20];
4024 dundi_eid_to_str(eid_str, sizeof(eid_str), eid);
4025 if (!ast_db_get("dundi/dpeers", eid_str, data, sizeof(data))) {
4026 c = strchr(data, ':');
4027 if (c) {
4028 *c = '\0';
4029 c++;
4030 if (sscanf(c, "%d:%d", &port, &expire) == 2) {
4031 /* Got it! */
4032 inet_aton(data, &peer->addr.sin_addr);
4033 peer->addr.sin_family = AF_INET;
4034 peer->addr.sin_port = htons(port);
4035 peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
4042 static void build_peer(dundi_eid *eid, struct ast_variable *v, int *globalpcmode)
4044 struct dundi_peer *peer;
4045 struct ast_hostent he;
4046 struct hostent *hp;
4047 dundi_eid testeid;
4048 int needregister=0;
4049 char eid_str[20];
4051 AST_LIST_LOCK(&peers);
4052 AST_LIST_TRAVERSE(&peers, peer, list) {
4053 if (!dundi_eid_cmp(&peer->eid, eid)) {
4054 break;
4057 if (!peer) {
4058 /* Add us into the list */
4059 if (!(peer = ast_calloc(1, sizeof(*peer)))) {
4060 AST_LIST_UNLOCK(&peers);
4061 return;
4063 peer->registerid = -1;
4064 peer->registerexpire = -1;
4065 peer->qualifyid = -1;
4066 peer->addr.sin_family = AF_INET;
4067 peer->addr.sin_port = htons(DUNDI_PORT);
4068 populate_addr(peer, eid);
4069 AST_LIST_INSERT_HEAD(&peers, peer, list);
4071 peer->dead = 0;
4072 peer->eid = *eid;
4073 peer->us_eid = global_eid;
4074 destroy_permissions(&peer->permit);
4075 destroy_permissions(&peer->include);
4076 if (peer->registerid > -1)
4077 ast_sched_del(sched, peer->registerid);
4078 peer->registerid = -1;
4079 for (; v; v = v->next) {
4080 if (!strcasecmp(v->name, "inkey")) {
4081 ast_copy_string(peer->inkey, v->value, sizeof(peer->inkey));
4082 } else if (!strcasecmp(v->name, "outkey")) {
4083 ast_copy_string(peer->outkey, v->value, sizeof(peer->outkey));
4084 } else if (!strcasecmp(v->name, "host")) {
4085 if (!strcasecmp(v->value, "dynamic")) {
4086 peer->dynamic = 1;
4087 } else {
4088 hp = ast_gethostbyname(v->value, &he);
4089 if (hp) {
4090 memcpy(&peer->addr.sin_addr, hp->h_addr, sizeof(peer->addr.sin_addr));
4091 peer->dynamic = 0;
4092 } else {
4093 ast_log(LOG_WARNING, "Unable to find host '%s' at line %d\n", v->value, v->lineno);
4094 peer->dead = 1;
4097 } else if (!strcasecmp(v->name, "ustothem")) {
4098 if (!dundi_str_to_eid(&testeid, v->value))
4099 peer->us_eid = testeid;
4100 else
4101 ast_log(LOG_WARNING, "'%s' is not a valid DUNDi Entity Identifier at line %d\n", v->value, v->lineno);
4102 } else if (!strcasecmp(v->name, "include")) {
4103 append_permission(&peer->include, v->value, 1);
4104 } else if (!strcasecmp(v->name, "permit")) {
4105 append_permission(&peer->permit, v->value, 1);
4106 } else if (!strcasecmp(v->name, "noinclude")) {
4107 append_permission(&peer->include, v->value, 0);
4108 } else if (!strcasecmp(v->name, "deny")) {
4109 append_permission(&peer->permit, v->value, 0);
4110 } else if (!strcasecmp(v->name, "register")) {
4111 needregister = ast_true(v->value);
4112 } else if (!strcasecmp(v->name, "order")) {
4113 if (!strcasecmp(v->value, "primary"))
4114 peer->order = 0;
4115 else if (!strcasecmp(v->value, "secondary"))
4116 peer->order = 1;
4117 else if (!strcasecmp(v->value, "tertiary"))
4118 peer->order = 2;
4119 else if (!strcasecmp(v->value, "quartiary"))
4120 peer->order = 3;
4121 else {
4122 ast_log(LOG_WARNING, "'%s' is not a valid order, should be primary, secondary, tertiary or quartiary at line %d\n", v->value, v->lineno);
4124 } else if (!strcasecmp(v->name, "qualify")) {
4125 if (!strcasecmp(v->value, "no")) {
4126 peer->maxms = 0;
4127 } else if (!strcasecmp(v->value, "yes")) {
4128 peer->maxms = DEFAULT_MAXMS;
4129 } else if (sscanf(v->value, "%d", &peer->maxms) != 1) {
4130 ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of dundi.conf\n",
4131 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), v->lineno);
4132 peer->maxms = 0;
4134 } else if (!strcasecmp(v->name, "model")) {
4135 if (!strcasecmp(v->value, "inbound"))
4136 peer->model = DUNDI_MODEL_INBOUND;
4137 else if (!strcasecmp(v->value, "outbound"))
4138 peer->model = DUNDI_MODEL_OUTBOUND;
4139 else if (!strcasecmp(v->value, "symmetric"))
4140 peer->model = DUNDI_MODEL_SYMMETRIC;
4141 else if (!strcasecmp(v->value, "none"))
4142 peer->model = 0;
4143 else {
4144 ast_log(LOG_WARNING, "Unknown model '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n",
4145 v->value, v->lineno);
4147 } else if (!strcasecmp(v->name, "precache")) {
4148 if (!strcasecmp(v->value, "inbound"))
4149 peer->pcmodel = DUNDI_MODEL_INBOUND;
4150 else if (!strcasecmp(v->value, "outbound"))
4151 peer->pcmodel = DUNDI_MODEL_OUTBOUND;
4152 else if (!strcasecmp(v->value, "symmetric"))
4153 peer->pcmodel = DUNDI_MODEL_SYMMETRIC;
4154 else if (!strcasecmp(v->value, "none"))
4155 peer->pcmodel = 0;
4156 else {
4157 ast_log(LOG_WARNING, "Unknown pcmodel '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n",
4158 v->value, v->lineno);
4162 (*globalpcmode) |= peer->pcmodel;
4163 if (!peer->model && !peer->pcmodel) {
4164 ast_log(LOG_WARNING, "Peer '%s' lacks a model or pcmodel, discarding!\n",
4165 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
4166 peer->dead = 1;
4167 } else if ((peer->model & DUNDI_MODEL_INBOUND) && (peer->pcmodel & DUNDI_MODEL_OUTBOUND)) {
4168 ast_log(LOG_WARNING, "Peer '%s' may not be both inbound/symmetric model and outbound/symmetric precache model, discarding!\n",
4169 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
4170 peer->dead = 1;
4171 } else if ((peer->model & DUNDI_MODEL_OUTBOUND) && (peer->pcmodel & DUNDI_MODEL_INBOUND)) {
4172 ast_log(LOG_WARNING, "Peer '%s' may not be both outbound/symmetric model and inbound/symmetric precache model, discarding!\n",
4173 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
4174 peer->dead = 1;
4175 } else if (!AST_LIST_EMPTY(&peer->include) && !(peer->model & DUNDI_MODEL_OUTBOUND) && !(peer->pcmodel & DUNDI_MODEL_INBOUND)) {
4176 ast_log(LOG_WARNING, "Peer '%s' is supposed to be included in outbound searches but isn't an outbound peer or inbound precache!\n",
4177 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
4178 } else if (!AST_LIST_EMPTY(&peer->permit) && !(peer->model & DUNDI_MODEL_INBOUND) && !(peer->pcmodel & DUNDI_MODEL_OUTBOUND)) {
4179 ast_log(LOG_WARNING, "Peer '%s' is supposed to have permission for some inbound searches but isn't an inbound peer or outbound precache!\n",
4180 dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
4181 } else {
4182 if (needregister) {
4183 peer->registerid = ast_sched_add(sched, 2000, do_register, peer);
4185 qualify_peer(peer, 1);
4187 AST_LIST_UNLOCK(&peers);
4190 static int dundi_helper(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *data, int flag)
4192 struct dundi_result results[MAX_RESULTS];
4193 int res;
4194 int x;
4195 int found = 0;
4196 if (!strncasecmp(context, "macro-", 6)) {
4197 if (!chan) {
4198 ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
4199 return -1;
4201 /* If done as a macro, use macro extension */
4202 if (!strcasecmp(exten, "s")) {
4203 exten = pbx_builtin_getvar_helper(chan, "ARG1");
4204 if (ast_strlen_zero(exten))
4205 exten = chan->macroexten;
4206 if (ast_strlen_zero(exten))
4207 exten = chan->exten;
4208 if (ast_strlen_zero(exten)) {
4209 ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
4210 return -1;
4213 if (ast_strlen_zero(data))
4214 data = "e164";
4215 } else {
4216 if (ast_strlen_zero(data))
4217 data = context;
4219 res = dundi_lookup(results, MAX_RESULTS, chan, data, exten, 0);
4220 for (x=0;x<res;x++) {
4221 if (ast_test_flag(results + x, flag))
4222 found++;
4224 if (found >= priority)
4225 return 1;
4226 return 0;
4229 static int dundi_exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
4231 return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_EXISTS);
4234 static int dundi_canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
4236 return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_CANMATCH);
4239 static int dundi_exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
4241 struct dundi_result results[MAX_RESULTS];
4242 int res;
4243 int x=0;
4244 char req[1024];
4245 struct ast_app *dial;
4247 if (!strncasecmp(context, "macro-", 6)) {
4248 if (!chan) {
4249 ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
4250 return -1;
4252 /* If done as a macro, use macro extension */
4253 if (!strcasecmp(exten, "s")) {
4254 exten = pbx_builtin_getvar_helper(chan, "ARG1");
4255 if (ast_strlen_zero(exten))
4256 exten = chan->macroexten;
4257 if (ast_strlen_zero(exten))
4258 exten = chan->exten;
4259 if (ast_strlen_zero(exten)) {
4260 ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
4261 return -1;
4264 if (ast_strlen_zero(data))
4265 data = "e164";
4266 } else {
4267 if (ast_strlen_zero(data))
4268 data = context;
4270 res = dundi_lookup(results, MAX_RESULTS, chan, data, exten, 0);
4271 if (res > 0) {
4272 sort_results(results, res);
4273 for (x=0;x<res;x++) {
4274 if (ast_test_flag(results + x, DUNDI_FLAG_EXISTS)) {
4275 if (!--priority)
4276 break;
4280 if (x < res) {
4281 /* Got a hit! */
4282 snprintf(req, sizeof(req), "%s/%s", results[x].tech, results[x].dest);
4283 dial = pbx_findapp("Dial");
4284 if (dial)
4285 res = pbx_exec(chan, dial, req);
4286 } else
4287 res = -1;
4288 return res;
4291 static int dundi_matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
4293 return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_MATCHMORE);
4296 static struct ast_switch dundi_switch =
4298 name: "DUNDi",
4299 description: "DUNDi Discovered Dialplan Switch",
4300 exists: dundi_exists,
4301 canmatch: dundi_canmatch,
4302 exec: dundi_exec,
4303 matchmore: dundi_matchmore,
4306 static int set_config(char *config_file, struct sockaddr_in* sin)
4308 struct ast_config *cfg;
4309 struct ast_variable *v;
4310 char *cat;
4311 int format;
4312 int x;
4313 char hn[MAXHOSTNAMELEN] = "";
4314 struct ast_hostent he;
4315 struct hostent *hp;
4316 struct sockaddr_in sin2;
4317 static int last_port = 0;
4318 int globalpcmodel = 0;
4319 dundi_eid testeid;
4321 dundi_ttl = DUNDI_DEFAULT_TTL;
4322 dundi_cache_time = DUNDI_DEFAULT_CACHE_TIME;
4323 cfg = ast_config_load(config_file);
4326 if (!cfg) {
4327 ast_log(LOG_ERROR, "Unable to load config %s\n", config_file);
4328 return -1;
4330 ipaddr[0] = '\0';
4331 if (!gethostname(hn, sizeof(hn)-1)) {
4332 hp = ast_gethostbyname(hn, &he);
4333 if (hp) {
4334 memcpy(&sin2.sin_addr, hp->h_addr, sizeof(sin2.sin_addr));
4335 ast_copy_string(ipaddr, ast_inet_ntoa(sin2.sin_addr), sizeof(ipaddr));
4336 } else
4337 ast_log(LOG_WARNING, "Unable to look up host '%s'\n", hn);
4338 } else
4339 ast_log(LOG_WARNING, "Unable to get host name!\n");
4340 AST_LIST_LOCK(&peers);
4341 reset_global_eid();
4342 global_storehistory = 0;
4343 ast_copy_string(secretpath, "dundi", sizeof(secretpath));
4344 v = ast_variable_browse(cfg, "general");
4345 while(v) {
4346 if (!strcasecmp(v->name, "port")){
4347 sin->sin_port = ntohs(atoi(v->value));
4348 if(last_port==0){
4349 last_port=sin->sin_port;
4350 } else if(sin->sin_port != last_port)
4351 ast_log(LOG_WARNING, "change to port ignored until next asterisk re-start\n");
4352 } else if (!strcasecmp(v->name, "bindaddr")) {
4353 struct hostent *hp;
4354 struct ast_hostent he;
4355 hp = ast_gethostbyname(v->value, &he);
4356 if (hp) {
4357 memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr));
4358 } else
4359 ast_log(LOG_WARNING, "Invalid host/IP '%s'\n", v->value);
4360 } else if (!strcasecmp(v->name, "authdebug")) {
4361 authdebug = ast_true(v->value);
4362 } else if (!strcasecmp(v->name, "ttl")) {
4363 if ((sscanf(v->value, "%d", &x) == 1) && (x > 0) && (x < DUNDI_DEFAULT_TTL)) {
4364 dundi_ttl = x;
4365 } else {
4366 ast_log(LOG_WARNING, "'%s' is not a valid TTL at line %d, must be number from 1 to %d\n",
4367 v->value, v->lineno, DUNDI_DEFAULT_TTL);
4369 } else if (!strcasecmp(v->name, "autokill")) {
4370 if (sscanf(v->value, "%d", &x) == 1) {
4371 if (x >= 0)
4372 global_autokilltimeout = x;
4373 else
4374 ast_log(LOG_NOTICE, "Nice try, but autokill has to be >0 or 'yes' or 'no' at line %d\n", v->lineno);
4375 } else if (ast_true(v->value)) {
4376 global_autokilltimeout = DEFAULT_MAXMS;
4377 } else {
4378 global_autokilltimeout = 0;
4380 } else if (!strcasecmp(v->name, "entityid")) {
4381 if (!dundi_str_to_eid(&testeid, v->value))
4382 global_eid = testeid;
4383 else
4384 ast_log(LOG_WARNING, "Invalid global endpoint identifier '%s' at line %d\n", v->value, v->lineno);
4385 } else if (!strcasecmp(v->name, "tos")) {
4386 if (sscanf(v->value, "%d", &format) == 1)
4387 tos = format & 0xff;
4388 else if (!strcasecmp(v->value, "lowdelay"))
4389 tos = IPTOS_LOWDELAY;
4390 else if (!strcasecmp(v->value, "throughput"))
4391 tos = IPTOS_THROUGHPUT;
4392 else if (!strcasecmp(v->value, "reliability"))
4393 tos = IPTOS_RELIABILITY;
4394 #if !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(SOLARIS)
4395 else if (!strcasecmp(v->value, "mincost"))
4396 tos = IPTOS_MINCOST;
4397 #endif
4398 else if (!strcasecmp(v->value, "none"))
4399 tos = 0;
4400 else
4401 #if !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(SOLARIS)
4402 ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\n", v->lineno);
4403 #else
4404 ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', or 'none'\n", v->lineno);
4405 #endif
4406 } else if (!strcasecmp(v->name, "department")) {
4407 ast_copy_string(dept, v->value, sizeof(dept));
4408 } else if (!strcasecmp(v->name, "organization")) {
4409 ast_copy_string(org, v->value, sizeof(org));
4410 } else if (!strcasecmp(v->name, "locality")) {
4411 ast_copy_string(locality, v->value, sizeof(locality));
4412 } else if (!strcasecmp(v->name, "stateprov")) {
4413 ast_copy_string(stateprov, v->value, sizeof(stateprov));
4414 } else if (!strcasecmp(v->name, "country")) {
4415 ast_copy_string(country, v->value, sizeof(country));
4416 } else if (!strcasecmp(v->name, "email")) {
4417 ast_copy_string(email, v->value, sizeof(email));
4418 } else if (!strcasecmp(v->name, "phone")) {
4419 ast_copy_string(phone, v->value, sizeof(phone));
4420 } else if (!strcasecmp(v->name, "storehistory")) {
4421 global_storehistory = ast_true(v->value);
4422 } else if (!strcasecmp(v->name, "cachetime")) {
4423 if ((sscanf(v->value, "%d", &x) == 1)) {
4424 dundi_cache_time = x;
4425 } else {
4426 ast_log(LOG_WARNING, "'%s' is not a valid cache time at line %d. Using default value '%d'.\n",
4427 v->value, v->lineno, DUNDI_DEFAULT_CACHE_TIME);
4430 v = v->next;
4432 AST_LIST_UNLOCK(&peers);
4433 mark_mappings();
4434 v = ast_variable_browse(cfg, "mappings");
4435 while(v) {
4436 build_mapping(v->name, v->value);
4437 v = v->next;
4439 prune_mappings();
4440 mark_peers();
4441 cat = ast_category_browse(cfg, NULL);
4442 while(cat) {
4443 if (strcasecmp(cat, "general") && strcasecmp(cat, "mappings")) {
4444 /* Entries */
4445 if (!dundi_str_to_eid(&testeid, cat))
4446 build_peer(&testeid, ast_variable_browse(cfg, cat), &globalpcmodel);
4447 else
4448 ast_log(LOG_NOTICE, "Ignoring invalid EID entry '%s'\n", cat);
4450 cat = ast_category_browse(cfg, cat);
4452 prune_peers();
4453 ast_config_destroy(cfg);
4454 load_password();
4455 if (globalpcmodel & DUNDI_MODEL_OUTBOUND)
4456 dundi_precache_full();
4457 return 0;
4460 static int unload_module(void)
4462 ast_module_user_hangup_all();
4464 ast_cli_unregister_multiple(cli_dundi, sizeof(cli_dundi) / sizeof(struct ast_cli_entry));
4465 ast_unregister_switch(&dundi_switch);
4466 ast_custom_function_unregister(&dundi_function);
4467 sched_context_destroy(sched);
4469 return 0;
4472 static int reload(void)
4474 struct sockaddr_in sin;
4475 set_config("dundi.conf",&sin);
4476 return 0;
4479 static int load_module(void)
4481 int res = 0;
4482 struct sockaddr_in sin;
4484 dundi_set_output(dundi_debug_output);
4485 dundi_set_error(dundi_error_output);
4487 sin.sin_family = AF_INET;
4488 sin.sin_port = ntohs(DUNDI_PORT);
4489 sin.sin_addr.s_addr = INADDR_ANY;
4491 /* Make a UDP socket */
4492 io = io_context_create();
4493 sched = sched_context_create();
4495 if (!io || !sched) {
4496 ast_log(LOG_ERROR, "Out of memory\n");
4497 return -1;
4500 if(set_config("dundi.conf",&sin))
4501 return AST_MODULE_LOAD_DECLINE;
4503 netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
4505 if (netsocket < 0) {
4506 ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
4507 return -1;
4509 if (bind(netsocket,(struct sockaddr *)&sin, sizeof(sin))) {
4510 ast_log(LOG_ERROR, "Unable to bind to %s port %d: %s\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), strerror(errno));
4511 return -1;
4514 if (option_verbose > 1)
4515 ast_verbose(VERBOSE_PREFIX_2 "Using TOS bits %d\n", tos);
4517 if (setsockopt(netsocket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))
4518 ast_log(LOG_WARNING, "Unable to set TOS to %d\n", tos);
4520 res = start_network_thread();
4521 if (res) {
4522 ast_log(LOG_ERROR, "Unable to start network thread\n");
4523 close(netsocket);
4524 return -1;
4527 if (option_verbose > 1)
4528 ast_verbose(VERBOSE_PREFIX_2 "DUNDi Ready and Listening on %s port %d\n", ast_inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
4530 ast_cli_register_multiple(cli_dundi, sizeof(cli_dundi) / sizeof(struct ast_cli_entry));
4531 if (ast_register_switch(&dundi_switch))
4532 ast_log(LOG_ERROR, "Unable to register DUNDi switch\n");
4533 ast_custom_function_register(&dundi_function);
4535 return res;
4538 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Distributed Universal Number Discovery (DUNDi)",
4539 .load = load_module,
4540 .unload = unload_module,
4541 .reload = reload,