aboutsummaryrefslogtreecommitdiffstats
path: root/pbx
diff options
context:
space:
mode:
authormarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2004-10-18 21:45:13 +0000
committermarkster <markster@f38db490-d61c-443f-a65b-d21fe96a405b>2004-10-18 21:45:13 +0000
commit852e93e07c03c5289067af53f2d7a3294f0b725f (patch)
tree80940dd6683a810dd916c78d8a080531ce4785d6 /pbx
parent70ae1d18dbb3fc9c760798ef2c2c12038ef5cebd (diff)
Add DUNDi.... (http://www.dundi.com)
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@4033 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'pbx')
-rwxr-xr-xpbx/Makefile5
-rwxr-xr-xpbx/dundi-parser.c802
-rwxr-xr-xpbx/dundi-parser.h87
-rwxr-xr-xpbx/pbx_dundi.c3827
4 files changed, 4720 insertions, 1 deletions
diff --git a/pbx/Makefile b/pbx/Makefile
index aa71a0e22..8fb70b645 100755
--- a/pbx/Makefile
+++ b/pbx/Makefile
@@ -13,7 +13,7 @@
-PBX_LIBS=pbx_config.so pbx_spool.so # pbx_gtkconsole.so pbx_kdeconsole.so
+PBX_LIBS=pbx_config.so pbx_spool.so pbx_dundi.so
# Add GTK console if appropriate
#PBX_LIBS+=$(shell gtk-config --cflags >/dev/null 2>/dev/null && echo "pbx_gtkconsole.so")
@@ -55,6 +55,9 @@ pbx_kdeconsole_main.o: pbx_kdeconsole_main.cc pbx_kdeconsole.h
pbx_kdeconsole.so: $(KDE_CONSOLE_OBJS)
$(CC) $(SOLINK) -o $@ $(KDE_CONSOLE_OBJS) $(KDE_LIBS)
+pbx_dundi.so: dundi-parser.o pbx_dundi.o
+ $(CC) $(SOLINK) -o $@ pbx_dundi.o dundi-parser.o -lz
+
%.moc : %.h
$(MOC) $< -o $@
diff --git a/pbx/dundi-parser.c b/pbx/dundi-parser.c
new file mode 100755
index 000000000..45e186c78
--- /dev/null
+++ b/pbx/dundi-parser.c
@@ -0,0 +1,802 @@
+/*
+ * Distributed Universal Number Discovery (DUNDi)
+ *
+ * Copyright (C) 2004, Digium Inc.
+ *
+ * Written by Mark Spencer <markster@digium.com>
+ *
+ * This program is Free Software distributed under the terms of
+ * of the GNU General Public License.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <asterisk/frame.h>
+#include <asterisk/utils.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <asterisk/dundi.h>
+#include "dundi-parser.h"
+#include <asterisk/dundi.h>
+static void internaloutput(const char *str)
+{
+ printf(str);
+}
+
+static void internalerror(const char *str)
+{
+ fprintf(stderr, "WARNING: %s", str);
+}
+
+static void (*outputf)(const char *str) = internaloutput;
+static void (*errorf)(const char *str) = internalerror;
+
+char *dundi_eid_to_str(char *s, int maxlen, dundi_eid *eid)
+{
+ int x;
+ char *os = s;
+ if (maxlen < 18) {
+ if (s && (maxlen > 0))
+ *s = '\0';
+ } else {
+ for (x=0;x<5;x++) {
+ sprintf(s, "%02x:", eid->eid[x]);
+ s += 3;
+ }
+ sprintf(s, "%02x", eid->eid[5]);
+ }
+ return os;
+}
+
+char *dundi_eid_to_str_short(char *s, int maxlen, dundi_eid *eid)
+{
+ int x;
+ char *os = s;
+ if (maxlen < 13) {
+ if (s && (maxlen > 0))
+ *s = '\0';
+ } else {
+ for (x=0;x<6;x++) {
+ sprintf(s, "%02X", eid->eid[x]);
+ s += 2;
+ }
+ }
+ return os;
+}
+
+int dundi_str_to_eid(dundi_eid *eid, char *s)
+{
+ unsigned int eid_int[6];
+ int x;
+ if (sscanf(s, "%x:%x:%x:%x:%x:%x", &eid_int[0], &eid_int[1], &eid_int[2],
+ &eid_int[3], &eid_int[4], &eid_int[5]) != 6)
+ return -1;
+ for (x=0;x<6;x++)
+ eid->eid[x] = eid_int[x];
+ return 0;
+}
+
+int dundi_str_short_to_eid(dundi_eid *eid, char *s)
+{
+ unsigned int eid_int[6];
+ int x;
+ if (sscanf(s, "%2x%2x%2x%2x%2x%2x", &eid_int[0], &eid_int[1], &eid_int[2],
+ &eid_int[3], &eid_int[4], &eid_int[5]) != 6)
+ return -1;
+ for (x=0;x<6;x++)
+ eid->eid[x] = eid_int[x];
+ return 0;
+}
+int dundi_eid_zero(dundi_eid *eid)
+{
+ int x;
+ for (x=0;x<sizeof(eid->eid) / sizeof(eid->eid[0]);x++)
+ if (eid->eid[x]) return 0;
+ return 1;
+}
+
+int dundi_eid_cmp(dundi_eid *eid1, dundi_eid *eid2)
+{
+ return memcmp(eid1, eid2, sizeof(dundi_eid));
+}
+
+static void dump_string(char *output, int maxlen, void *value, int len)
+{
+ maxlen--;
+ if (maxlen > len)
+ maxlen = len;
+ strncpy(output,value, maxlen);
+ output[maxlen] = '\0';
+}
+
+static void dump_eid(char *output, int maxlen, void *value, int len)
+{
+ if (len == 6)
+ dundi_eid_to_str(output, maxlen, (dundi_eid *)value);
+ else
+ snprintf(output, maxlen, "Invalid EID len %d", len);
+}
+
+char *dundi_hint2str(char *buf, int bufsiz, int flags)
+{
+ strcpy(buf, "");
+ buf[bufsiz-1] = '\0';
+ if (flags & DUNDI_HINT_TTL_EXPIRED) {
+ strncat(buf, "TTLEXPIRED|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_HINT_DONT_ASK) {
+ strncat(buf, "DONTASK|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_HINT_UNAFFECTED) {
+ strncat(buf, "UNAFFECTED|", bufsiz - strlen(buf) - 1);
+ }
+ /* Get rid of trailing | */
+ if (ast_strlen_zero(buf))
+ strcpy(buf, "NONE|");
+ buf[strlen(buf)-1] = '\0';
+ return buf;
+}
+
+static void dump_hint(char *output, int maxlen, void *value, int len)
+{
+ unsigned short flags;
+ char tmp[512];
+ char tmp2[256];
+ if (len < 2) {
+ strncpy(output, "<invalid contents>", maxlen);
+ return;
+ }
+ memcpy(&flags, value, sizeof(flags));
+ flags = ntohs(flags);
+ memset(tmp, 0, sizeof(tmp));
+ dundi_hint2str(tmp2, sizeof(tmp2), flags);
+ snprintf(tmp, sizeof(tmp), "[%s] ", tmp2);
+ memcpy(tmp + strlen(tmp), value + 2, len - 2);
+ strncpy(output, tmp, maxlen - 1);
+}
+
+static void dump_cause(char *output, int maxlen, void *value, int len)
+{
+ static char *causes[] = {
+ "SUCCESS",
+ "GENERAL",
+ "DYNAMIC",
+ "NOAUTH" ,
+ };
+ char tmp[256];
+ char tmp2[256];
+ int mlen;
+ unsigned char cause;
+ if (len < 1) {
+ strncpy(output, "<invalid contents>", maxlen);
+ return;
+ }
+ cause = *((unsigned char *)value);
+ memset(tmp2, 0, sizeof(tmp2));
+ mlen = len - 1;
+ if (mlen > 255)
+ mlen = 255;
+ memcpy(tmp2, value + 1, mlen);
+ if (cause < sizeof(causes) / sizeof(causes[0])) {
+ if (len > 1)
+ snprintf(tmp, sizeof(tmp), "%s: %s", causes[cause], tmp2);
+ else
+ snprintf(tmp, sizeof(tmp), "%s", causes[cause]);
+ } else {
+ if (len > 1)
+ snprintf(tmp, sizeof(tmp), "%d: %s", cause, tmp2);
+ else
+ snprintf(tmp, sizeof(tmp), "%d", cause);
+ }
+
+ strncpy(output,tmp, maxlen);
+ output[maxlen] = '\0';
+}
+
+static void dump_int(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned int))
+ snprintf(output, maxlen, "%lu", (unsigned long)ntohl(*((unsigned int *)value)));
+ else
+ snprintf(output, maxlen, "Invalid INT");
+}
+
+static void dump_short(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned short))
+ snprintf(output, maxlen, "%d", ntohs(*((unsigned short *)value)));
+ else
+ snprintf(output, maxlen, "Invalid SHORT");
+}
+
+static void dump_byte(char *output, int maxlen, void *value, int len)
+{
+ if (len == (int)sizeof(unsigned char))
+ snprintf(output, maxlen, "%d", *((unsigned char *)value));
+ else
+ snprintf(output, maxlen, "Invalid BYTE");
+}
+
+static char *proto2str(int proto, char *buf, int bufsiz)
+{
+
+ switch(proto) {
+ case DUNDI_PROTO_NONE:
+ strncpy(buf, "None", bufsiz - 1);
+ break;
+ case DUNDI_PROTO_IAX:
+ strncpy(buf, "IAX", bufsiz - 1);
+ break;
+ case DUNDI_PROTO_SIP:
+ strncpy(buf, "SIP", bufsiz - 1);
+ break;
+ case DUNDI_PROTO_H323:
+ strncpy(buf, "H.323", bufsiz - 1);
+ break;
+ default:
+ snprintf(buf, bufsiz, "Unknown Proto(%d)", proto);
+ }
+ buf[bufsiz-1] = '\0';
+ return buf;
+}
+
+char *dundi_flags2str(char *buf, int bufsiz, int flags)
+{
+ strcpy(buf, "");
+ buf[bufsiz-1] = '\0';
+ if (flags & DUNDI_FLAG_EXISTS) {
+ strncat(buf, "EXISTS|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_MATCHMORE) {
+ strncat(buf, "MATCHMORE|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_CANMATCH) {
+ strncat(buf, "CANMATCH|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_IGNOREPAT) {
+ strncat(buf, "IGNOREPAT|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_RESIDENTIAL) {
+ strncat(buf, "RESIDENCE|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_COMMERCIAL) {
+ strncat(buf, "COMMERCIAL|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_MOBILE) {
+ strncat(buf, "MOBILE", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_NOUNSOLICITED) {
+ strncat(buf, "NOUNSLCTD|", bufsiz - strlen(buf) - 1);
+ }
+ if (flags & DUNDI_FLAG_NOCOMUNSOLICIT) {
+ strncat(buf, "NOCOMUNSLTD|", bufsiz - strlen(buf) - 1);
+ }
+ /* Get rid of trailing | */
+ if (ast_strlen_zero(buf))
+ strcpy(buf, "NONE|");
+ buf[strlen(buf)-1] = '\0';
+ return buf;
+}
+
+static void dump_answer(char *output, int maxlen, void *value, int len)
+{
+ struct dundi_answer *answer;
+ char proto[40];
+ char flags[40];
+ char eid_str[40];
+ char tmp[512]="";
+ if (len >= 10) {
+ answer = (struct dundi_answer *)(value);
+ memcpy(tmp, answer->data, (len >= 500) ? 500 : len - 10);
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &answer->eid);
+ snprintf(output, maxlen, "[%s] %d <%s/%s> from [%s]",
+ dundi_flags2str(flags, sizeof(flags), ntohs(answer->flags)),
+ ntohs(answer->weight),
+ proto2str(answer->protocol, proto, sizeof(proto)),
+ tmp, eid_str);
+ } else
+ strncpy(output, "Invalid Answer", maxlen - 1);
+}
+
+static void dump_encrypted(char *output, int maxlen, void *value, int len)
+{
+ char iv[33];
+ int x;
+ if ((len > 16) && !(len % 16)) {
+ /* Build up IV */
+ for (x=0;x<16;x++) {
+ snprintf(iv + (x << 1), 3, "%02x", ((unsigned char *)value)[x]);
+ }
+ snprintf(output, maxlen, "[IV %s] %d encrypted blocks\n", iv, len / 16);
+ } else
+ snprintf(output, maxlen, "Invalid Encrypted Datalen %d", len);
+}
+
+static void dump_raw(char *output, int maxlen, void *value, int len)
+{
+ int x;
+ unsigned char *u = value;
+ output[maxlen - 1] = '\0';
+ strcpy(output, "[ ");
+ for (x=0;x<len;x++) {
+ snprintf(output + strlen(output), maxlen - strlen(output) - 1, "%02x ", u[x]);
+ }
+ strncat(output + strlen(output), "]", maxlen - strlen(output) - 1);
+}
+
+static struct dundi_ie {
+ int ie;
+ char *name;
+ void (*dump)(char *output, int maxlen, void *value, int len);
+} ies[] = {
+ { DUNDI_IE_EID, "ENTITY IDENT", dump_eid },
+ { DUNDI_IE_CALLED_CONTEXT, "CALLED CONTEXT", dump_string },
+ { DUNDI_IE_CALLED_NUMBER, "CALLED NUMBER", dump_string },
+ { DUNDI_IE_EID_DIRECT, "DIRECT EID", dump_eid },
+ { DUNDI_IE_ANSWER, "ANSWER", dump_answer },
+ { DUNDI_IE_TTL, "TTL", dump_short },
+ { DUNDI_IE_VERSION, "VERSION", dump_short },
+ { DUNDI_IE_EXPIRATION, "EXPIRATION", dump_short },
+ { DUNDI_IE_UNKNOWN, "UKWN DUNDI CMD", dump_byte },
+ { DUNDI_IE_CAUSE, "CAUSE", dump_cause },
+ { DUNDI_IE_REQEID, "REQUEST EID", dump_eid },
+ { DUNDI_IE_ENCDATA, "ENCDATA", dump_encrypted },
+ { DUNDI_IE_SHAREDKEY, "SHAREDKEY", dump_raw },
+ { DUNDI_IE_SIGNATURE, "SIGNATURE", dump_raw },
+ { DUNDI_IE_KEYCRC32, "KEYCRC32", dump_int },
+ { DUNDI_IE_HINT, "HINT", dump_hint },
+ { DUNDI_IE_DEPARTMENT, "DEPARTMENT", dump_string },
+ { DUNDI_IE_ORGANIZATION, "ORGANIZTN", dump_string },
+ { DUNDI_IE_LOCALITY, "LOCALITY", dump_string },
+ { DUNDI_IE_STATE_PROV, "STATEPROV", dump_string },
+ { DUNDI_IE_COUNTRY, "COUNTRY", dump_string },
+ { DUNDI_IE_EMAIL, "EMAIL", dump_string },
+ { DUNDI_IE_PHONE, "PHONE", dump_string },
+ { DUNDI_IE_IPADDR, "ADDRESS", dump_string },
+};
+
+const char *dundi_ie2str(int ie)
+{
+ int x;
+ for (x=0;x<(int)sizeof(ies) / (int)sizeof(ies[0]); x++) {
+ if (ies[x].ie == ie)
+ return ies[x].name;
+ }
+ return "Unknown IE";
+}
+
+static void dump_ies(unsigned char *iedata, int spaces, int len)
+{
+ int ielen;
+ int ie;
+ int x;
+ int found;
+ char interp[1024];
+ char tmp[1024];
+ if (len < 2)
+ return;
+ while(len > 2) {
+ ie = iedata[0];
+ ielen = iedata[1];
+ /* Encrypted data is the remainder */
+ if (ie == DUNDI_IE_ENCDATA)
+ ielen = len - 2;
+ if (ielen + 2> len) {
+ snprintf(tmp, (int)sizeof(tmp), "Total IE length of %d bytes exceeds remaining frame length of %d bytes\n", ielen + 2, len);
+ outputf(tmp);
+ return;
+ }
+ found = 0;
+ for (x=0;x<(int)sizeof(ies) / (int)sizeof(ies[0]); x++) {
+ if (ies[x].ie == ie) {
+ if (ies[x].dump) {
+ ies[x].dump(interp, (int)sizeof(interp), iedata + 2, ielen);
+ snprintf(tmp, (int)sizeof(tmp), " %s%-15.15s : %s\n", (spaces ? " " : "" ), ies[x].name, interp);
+ outputf(tmp);
+ } else {
+ if (ielen)
+ snprintf(interp, (int)sizeof(interp), "%d bytes", ielen);
+ else
+ strcpy(interp, "Present");
+ snprintf(tmp, (int)sizeof(tmp), " %s%-15.15s : %s\n", (spaces ? " " : "" ), ies[x].name, interp);
+ outputf(tmp);
+ }
+ found++;
+ }
+ }
+ if (!found) {
+ snprintf(tmp, (int)sizeof(tmp), " %sUnknown IE %03d : Present\n", (spaces ? " " : "" ), ie);
+ outputf(tmp);
+ }
+ iedata += (2 + ielen);
+ len -= (2 + ielen);
+ }
+ outputf("\n");
+}
+
+void dundi_showframe(struct dundi_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen)
+{
+ char *pref[] = {
+ "Tx",
+ "Rx",
+ " ETx",
+ " Erx" };
+ char *commands[] = {
+ "ACK ",
+ "DPDISCOVER ",
+ "DPRESPONSE ",
+ "EIDQUERY ",
+ "EIDRESPONSE ",
+ "UNKNOWN (5)?",
+ "UNKNOWN (6)?",
+ "INVALID ",
+ "UNKNOWN CMD ",
+ "NULL ",
+ "REQREQ ",
+ "REGRESPONSE ",
+ "CANCEL ",
+ "ENCRYPT ",
+ "ENCREJ " };
+ char class2[20];
+ char *class;
+ char subclass2[20];
+ char *subclass;
+ char tmp[256];
+ char retries[20];
+ char iabuf[INET_ADDRSTRLEN];
+ if (ntohs(fhi->dtrans) & DUNDI_FLAG_RETRANS)
+ strcpy(retries, "Yes");
+ else
+ strcpy(retries, "No");
+ if ((ntohs(fhi->strans) & DUNDI_FLAG_RESERVED)) {
+ /* Ignore frames with high bit set to 1 */
+ return;
+ }
+ if ((fhi->cmdresp & 0x3f) > (int)sizeof(commands)/(int)sizeof(char *)) {
+ snprintf(class2, (int)sizeof(class2), "(%d?)", fhi->cmdresp);
+ class = class2;
+ } else {
+ class = commands[(int)(fhi->cmdresp & 0x3f)];
+ }
+ snprintf(subclass2, (int)sizeof(subclass2), "%02x", fhi->cmdflags);
+ subclass = subclass2;
+snprintf(tmp, (int)sizeof(tmp),
+"%s-Frame Retry[%s] -- OSeqno: %3.3d ISeqno: %3.3d Type: %s (%s)\n",
+ pref[rx],
+ retries, fhi->oseqno, fhi->iseqno, class, fhi->cmdresp & 0x40 ? "Response" : "Command");
+ outputf(tmp);
+snprintf(tmp, (int)sizeof(tmp),
+"%s Flags: %s STrans: %5.5d DTrans: %5.5d [%s:%d]%s\n", (rx > 1) ? " " : "",
+ subclass, ntohs(fhi->strans) & ~DUNDI_FLAG_RESERVED, ntohs(fhi->dtrans) & ~DUNDI_FLAG_RETRANS,
+ ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), ntohs(sin->sin_port),
+ fhi->cmdresp & 0x80 ? " (Final)" : "");
+ outputf(tmp);
+ dump_ies(fhi->ies, rx > 1, datalen);
+}
+
+int dundi_ie_append_raw(struct dundi_ie_data *ied, unsigned char ie, void *data, int datalen)
+{
+ char tmp[256];
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", dundi_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ memcpy(ied->buf + ied->pos, data, datalen);
+ ied->pos += datalen;
+ return 0;
+}
+
+int dundi_ie_append_cause(struct dundi_ie_data *ied, unsigned char ie, unsigned char cause, unsigned char *data)
+{
+ char tmp[256];
+ int datalen = data ? strlen(data) + 1 : 1;
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", dundi_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ ied->buf[ied->pos++] = cause;
+ memcpy(ied->buf + ied->pos, data, datalen-1);
+ ied->pos += datalen-1;
+ return 0;
+}
+
+int dundi_ie_append_hint(struct dundi_ie_data *ied, unsigned char ie, unsigned short flags, unsigned char *data)
+{
+ char tmp[256];
+ int datalen = data ? strlen(data) + 2 : 2;
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", dundi_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ flags = htons(flags);
+ memcpy(ied->buf + ied->pos, &flags, sizeof(flags));
+ ied->pos += 2;
+ memcpy(ied->buf + ied->pos, data, datalen-1);
+ ied->pos += datalen-2;
+ return 0;
+}
+
+int dundi_ie_append_encdata(struct dundi_ie_data *ied, unsigned char ie, unsigned char *iv, void *data, int datalen)
+{
+ char tmp[256];
+ datalen += 16;
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", dundi_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ memcpy(ied->buf + ied->pos, iv, 16);
+ ied->pos += 16;
+ if (data) {
+ memcpy(ied->buf + ied->pos, data, datalen-16);
+ ied->pos += datalen-16;
+ }
+ return 0;
+}
+
+int dundi_ie_append_answer(struct dundi_ie_data *ied, unsigned char ie, dundi_eid *eid, unsigned char protocol, unsigned short flags, unsigned short weight, unsigned char *data)
+{
+ char tmp[256];
+ int datalen = data ? strlen(data) + 11 : 11;
+ int x;
+ unsigned short myw;
+ if (datalen > ((int)sizeof(ied->buf) - ied->pos)) {
+ snprintf(tmp, (int)sizeof(tmp), "Out of space for ie '%s' (%d), need %d have %d\n", dundi_ie2str(ie), ie, datalen, (int)sizeof(ied->buf) - ied->pos);
+ errorf(tmp);
+ return -1;
+ }
+ ied->buf[ied->pos++] = ie;
+ ied->buf[ied->pos++] = datalen;
+ for (x=0;x<6;x++)
+ ied->buf[ied->pos++] = eid->eid[x];
+ ied->buf[ied->pos++] = protocol;
+ myw = htons(flags);
+ memcpy(ied->buf + ied->pos, &myw, 2);
+ ied->pos += 2;
+ myw = htons(weight);
+ memcpy(ied->buf + ied->pos, &myw, 2);
+ ied->pos += 2;
+ memcpy(ied->buf + ied->pos, data, datalen-11);
+ ied->pos += datalen-11;
+ return 0;
+}
+
+int dundi_ie_append_addr(struct dundi_ie_data *ied, unsigned char ie, struct sockaddr_in *sin)
+{
+ return dundi_ie_append_raw(ied, ie, sin, (int)sizeof(struct sockaddr_in));
+}
+
+int dundi_ie_append_int(struct dundi_ie_data *ied, unsigned char ie, unsigned int value)
+{
+ unsigned int newval;
+ newval = htonl(value);
+ return dundi_ie_append_raw(ied, ie, &newval, (int)sizeof(newval));
+}
+
+int dundi_ie_append_short(struct dundi_ie_data *ied, unsigned char ie, unsigned short value)
+{
+ unsigned short newval;
+ newval = htons(value);
+ return dundi_ie_append_raw(ied, ie, &newval, (int)sizeof(newval));
+}
+
+int dundi_ie_append_str(struct dundi_ie_data *ied, unsigned char ie, unsigned char *str)
+{
+ return dundi_ie_append_raw(ied, ie, str, strlen(str));
+}
+
+int dundi_ie_append_eid(struct dundi_ie_data *ied, unsigned char ie, dundi_eid *eid)
+{
+ return dundi_ie_append_raw(ied, ie, (unsigned char *)eid, sizeof(dundi_eid));
+}
+
+int dundi_ie_append_byte(struct dundi_ie_data *ied, unsigned char ie, unsigned char dat)
+{
+ return dundi_ie_append_raw(ied, ie, &dat, 1);
+}
+
+int dundi_ie_append(struct dundi_ie_data *ied, unsigned char ie)
+{
+ return dundi_ie_append_raw(ied, ie, NULL, 0);
+}
+
+void dundi_set_output(void (*func)(const char *))
+{
+ outputf = func;
+}
+
+void dundi_set_error(void (*func)(const char *))
+{
+ errorf = func;
+}
+
+int dundi_parse_ies(struct dundi_ies *ies, unsigned char *data, int datalen)
+{
+ /* Parse data into information elements */
+ int len;
+ int ie;
+ char tmp[256];
+ memset(ies, 0, (int)sizeof(struct dundi_ies));
+ ies->ttl = -1;
+ ies->expiration = -1;
+ ies->unknowncmd = -1;
+ ies->cause = -1;
+ while(datalen >= 2) {
+ ie = data[0];
+ len = data[1];
+ if (len > datalen - 2) {
+ errorf("Information element length exceeds message size\n");
+ return -1;
+ }
+ switch(ie) {
+ case DUNDI_IE_EID:
+ case DUNDI_IE_EID_DIRECT:
+ if (len != (int)sizeof(dundi_eid)) {
+ errorf("Improper entity identifer, expecting 6 bytes!\n");
+ } else if (ies->eidcount < DUNDI_MAX_STACK) {
+ ies->eids[ies->eidcount] = (dundi_eid *)(data + 2);
+ ies->eid_direct[ies->eidcount] = (ie == DUNDI_IE_EID_DIRECT);
+ ies->eidcount++;
+ } else
+ errorf("Too many entities in stack!\n");
+ break;
+ case DUNDI_IE_REQEID:
+ if (len != (int)sizeof(dundi_eid)) {
+ errorf("Improper requested entity identifer, expecting 6 bytes!\n");
+ } else
+ ies->reqeid = (dundi_eid *)(data + 2);
+ break;
+ case DUNDI_IE_CALLED_CONTEXT:
+ ies->called_context = data + 2;
+ break;
+ case DUNDI_IE_CALLED_NUMBER:
+ ies->called_number = data + 2;
+ break;
+ case DUNDI_IE_ANSWER:
+ if (len < sizeof(struct dundi_answer)) {
+ snprintf(tmp, (int)sizeof(tmp), "Answer expected to be >=%d bytes long but was %d\n", (int)sizeof(struct dundi_answer), len);
+ errorf(tmp);
+ } else {
+ if (ies->anscount < DUNDI_MAX_ANSWERS)
+ ies->answers[ies->anscount++]= (struct dundi_answer *)(data + 2);
+ else
+ errorf("Ignoring extra answers!\n");
+ }
+ break;
+ case DUNDI_IE_TTL:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting ttl to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->ttl = ntohs(*((unsigned short *)(data + 2)));
+ break;
+ case DUNDI_IE_VERSION:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting version to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->version = ntohs(*((unsigned short *)(data + 2)));
+ break;
+ case DUNDI_IE_EXPIRATION:
+ if (len != (int)sizeof(unsigned short)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting expiration to be %d bytes long but was %d\n", (int)sizeof(unsigned short), len);
+ errorf(tmp);
+ } else
+ ies->expiration = ntohs(*((unsigned short *)(data + 2)));
+ break;
+ case DUNDI_IE_KEYCRC32:
+ if (len != (int)sizeof(unsigned int)) {
+ snprintf(tmp, (int)sizeof(tmp), "Expecting expiration to be %d bytes long but was %d\n", (int)sizeof(unsigned int), len);
+ errorf(tmp);
+ } else
+ ies->keycrc32 = ntohl(*((unsigned int *)(data + 2)));
+ break;
+ case DUNDI_IE_UNKNOWN:
+ if (len == 1)
+ ies->unknowncmd = data[2];
+ else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected single byte Unknown command, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case DUNDI_IE_CAUSE:
+ if (len >= 1) {
+ ies->cause = data[2];
+ ies->causestr = data + 3;
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected at least one byte cause, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case DUNDI_IE_HINT:
+ if (len >= 2) {
+ ies->hint = (struct dundi_hint *)(data + 2);
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Expected at least two byte hint, but was %d long\n", len);
+ errorf(tmp);
+ }
+ break;
+ case DUNDI_IE_DEPARTMENT:
+ ies->q_dept = data + 2;
+ break;
+ case DUNDI_IE_ORGANIZATION:
+ ies->q_org = data + 2;
+ break;
+ case DUNDI_IE_LOCALITY:
+ ies->q_locality = data + 2;
+ break;
+ case DUNDI_IE_STATE_PROV:
+ ies->q_stateprov = data + 2;
+ break;
+ case DUNDI_IE_COUNTRY:
+ ies->q_country = data + 2;
+ break;
+ case DUNDI_IE_EMAIL:
+ ies->q_email = data + 2;
+ break;
+ case DUNDI_IE_PHONE:
+ ies->q_phone = data + 2;
+ break;
+ case DUNDI_IE_IPADDR:
+ ies->q_ipaddr = data + 2;
+ break;
+ case DUNDI_IE_ENCDATA:
+ /* Recalculate len as the remainder of the message, regardless of
+ theoretical length */
+ len = datalen - 2;
+ if ((len > 16) && !(len % 16)) {
+ ies->encblock = (struct dundi_encblock *)(data + 2);
+ ies->enclen = len - 16;
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Invalid encrypted data length %d\n", len);
+ errorf(tmp);
+ }
+ break;
+ case DUNDI_IE_SHAREDKEY:
+ if (len == 128) {
+ ies->encsharedkey = (unsigned char *)(data + 2);
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Invalid encrypted shared key length %d\n", len);
+ errorf(tmp);
+ }
+ break;
+ case DUNDI_IE_SIGNATURE:
+ if (len == 128) {
+ ies->encsig = (unsigned char *)(data + 2);
+ } else {
+ snprintf(tmp, (int)sizeof(tmp), "Invalid encrypted signature length %d\n", len);
+ errorf(tmp);
+ }
+ break;
+ default:
+ snprintf(tmp, (int)sizeof(tmp), "Ignoring unknown information element '%s' (%d) of length %d\n", dundi_ie2str(ie), ie, len);
+ outputf(tmp);
+ }
+ /* Overwrite information element with 0, to null terminate previous portion */
+ data[0] = 0;
+ datalen -= (len + 2);
+ data += (len + 2);
+ }
+ /* Null-terminate last field */
+ *data = '\0';
+ if (datalen) {
+ errorf("Invalid information element contents, strange boundary\n");
+ return -1;
+ }
+ return 0;
+}
diff --git a/pbx/dundi-parser.h b/pbx/dundi-parser.h
new file mode 100755
index 000000000..c50dd1ed5
--- /dev/null
+++ b/pbx/dundi-parser.h
@@ -0,0 +1,87 @@
+/*
+ * Distributed Universal Number Discovery (DUNDi)
+ *
+ * Copyright (C) 2004, Digium Inc.
+ *
+ * Written by Mark Spencer <markster@digium.com>
+ *
+ * This program is Free Software distributed under the terms of
+ * of the GNU General Public License.
+ */
+
+#ifndef _DUNDI_PARSER_H
+#define _DUNDI_PARSER_H
+
+#include <asterisk/dundi.h>
+#include <asterisk/aes.h>
+
+#define DUNDI_MAX_STACK 512
+#define DUNDI_MAX_ANSWERS 100
+
+struct dundi_ies {
+ dundi_eid *eids[DUNDI_MAX_STACK + 1];
+ int eid_direct[DUNDI_MAX_STACK + 1];
+ dundi_eid *reqeid;
+ int eidcount;
+ char *called_context;
+ char *called_number;
+ struct dundi_answer *answers[DUNDI_MAX_ANSWERS + 1];
+ struct dundi_hint *hint;
+ int anscount;
+ int ttl;
+ int version;
+ int expiration;
+ int unknowncmd;
+ unsigned char *pubkey;
+ int cause;
+ unsigned char *q_dept;
+ unsigned char *q_org;
+ unsigned char *q_locality;
+ unsigned char *q_stateprov;
+ unsigned char *q_country;
+ unsigned char *q_email;
+ unsigned char *q_phone;
+ unsigned char *q_ipaddr;
+ unsigned char *causestr;
+ unsigned char *encsharedkey;
+ unsigned char *encsig;
+ unsigned long keycrc32;
+ struct dundi_encblock *encblock;
+ int enclen;
+};
+
+struct dundi_ie_data {
+ int pos;
+ unsigned char buf[8192];
+};
+
+/* Choose a different function for output */
+extern void dundi_set_output(void (*output)(const char *data));
+/* Choose a different function for errors */
+extern void dundi_set_error(void (*output)(const char *data));
+extern void dundi_showframe(struct dundi_hdr *fhi, int rx, struct sockaddr_in *sin, int datalen);
+
+extern const char *dundi_ie2str(int ie);
+
+extern int dundi_ie_append_raw(struct dundi_ie_data *ied, unsigned char ie, void *data, int datalen);
+extern int dundi_ie_append_addr(struct dundi_ie_data *ied, unsigned char ie, struct sockaddr_in *sin);
+extern int dundi_ie_append_int(struct dundi_ie_data *ied, unsigned char ie, unsigned int value);
+extern int dundi_ie_append_short(struct dundi_ie_data *ied, unsigned char ie, unsigned short value);
+extern int dundi_ie_append_str(struct dundi_ie_data *ied, unsigned char ie, unsigned char *str);
+extern int dundi_ie_append_eid(struct dundi_ie_data *ied, unsigned char ie, dundi_eid *eid);
+extern int dundi_ie_append_cause(struct dundi_ie_data *ied, unsigned char ie, unsigned char cause, unsigned char *desc);
+extern int dundi_ie_append_hint(struct dundi_ie_data *ied, unsigned char ie, unsigned short flags, unsigned char *data);
+extern int dundi_ie_append_answer(struct dundi_ie_data *ied, unsigned char ie, dundi_eid *eid, unsigned char protocol, unsigned short flags, unsigned short weight, unsigned char *desc);
+extern int dundi_ie_append_encdata(struct dundi_ie_data *ied, unsigned char ie, unsigned char *iv, void *data, int datalen);
+extern int dundi_ie_append_byte(struct dundi_ie_data *ied, unsigned char ie, unsigned char dat);
+extern int dundi_ie_append(struct dundi_ie_data *ied, unsigned char ie);
+extern int dundi_parse_ies(struct dundi_ies *ies, unsigned char *data, int datalen);
+extern char *dundi_eid_to_str(char *s, int maxlen, dundi_eid *eid);
+extern char *dundi_eid_to_str_short(char *s, int maxlen, dundi_eid *eid);
+extern int dundi_str_to_eid(dundi_eid *eid, char *s);
+extern int dundi_str_short_to_eid(dundi_eid *eid, char *s);
+extern int dundi_eid_zero(dundi_eid *eid);
+extern int dundi_eid_cmp(dundi_eid *eid1, dundi_eid *eid2);
+extern char *dundi_flags2str(char *s, int maxlen, int flags);
+extern char *dundi_hint2str(char *s, int maxlen, int flags);
+#endif
diff --git a/pbx/pbx_dundi.c b/pbx/pbx_dundi.c
new file mode 100755
index 000000000..bcb1e146f
--- /dev/null
+++ b/pbx/pbx_dundi.c
@@ -0,0 +1,3827 @@
+/*
+ * Distributed Universal Number Discovery (DUNDi)
+ *
+ * Copyright (C) 2004, Digium Inc.
+ *
+ * Written by Mark Spencer <markster@digium.com>
+ *
+ * This program is Free Software distributed under the terms of
+ * of the GNU General Public License.
+ */
+
+#include <asterisk/file.h>
+#include <asterisk/logger.h>
+#include <asterisk/channel.h>
+#include <asterisk/config.h>
+#include <asterisk/options.h>
+#include <asterisk/pbx.h>
+#include <asterisk/module.h>
+#include <asterisk/frame.h>
+#include <asterisk/file.h>
+#include <asterisk/channel_pvt.h>
+#include <asterisk/cli.h>
+#include <asterisk/lock.h>
+#include <asterisk/md5.h>
+#include <asterisk/dundi.h>
+#include <asterisk/sched.h>
+#include <asterisk/io.h>
+#include <asterisk/utils.h>
+#include <asterisk/crypto.h>
+#include <asterisk/astdb.h>
+#include <asterisk/acl.h>
+#include <asterisk/aes.h>
+
+#include "dundi-parser.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <errno.h>
+#include <netinet/ip.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <zlib.h>
+
+#define MAX_RESULTS 64
+
+#define MAX_PACKET_SIZE 8192
+
+extern char ast_config_AST_KEY_DIR[];
+
+static char *tdesc = "Distributed Universal Number Discovery (DUNDi)";
+
+static char *app = "DUNDiLookup";
+static char *synopsis = "Look up a number with DUNDi";
+static char *descrip =
+"DUNDiLookup(number[|context[|options]])\n"
+" Looks up a given number in the global context specified or in\n"
+"the reserved 'e164' context if not specified. Returns -1 if the channel\n"
+"is hungup during the lookup or 0 otherwise. On completion, the variable\n"
+"${DUNDTECH} and ${DUNDDEST} will contain the technology and destination\n"
+"of the appropriate technology and destination to access the number. If no\n"
+"answer was found, and the priority n + 101 exists, execution will continue\n"
+"at that location.\n";
+
+#define DUNDI_MODEL_INBOUND (1 << 0)
+#define DUNDI_MODEL_OUTBOUND (1 << 1)
+#define DUNDI_MODEL_SYMMETRIC (DUNDI_MODEL_INBOUND | DUNDI_MODEL_OUTBOUND)
+
+
+#define FLAG_ISREG (1 << 0) /* Transaction is register request */
+#define FLAG_DEAD (1 << 1) /* Transaction is dead */
+#define FLAG_FINAL (1 << 2) /* Transaction has final message sent */
+#define FLAG_ISQUAL (1 << 3) /* Transaction is a qualification */
+#define FLAG_ENCRYPT (1 << 4) /* Transaction is encrypted wiht ECX/DCX */
+#define FLAG_SENDFULLKEY (1 << 5) /* Send full key on transaction */
+
+#define DUNDI_FLAG_INTERNAL_NOPARTIAL (1 << 17)
+
+#if 0
+#define DUNDI_SECRET_TIME 15 /* Testing only */
+#else
+#define DUNDI_SECRET_TIME DUNDI_DEFAULT_CACHE_TIME
+#endif
+
+#define KEY_OUT 0
+#define KEY_IN 1
+
+static struct io_context *io;
+static struct sched_context *sched;
+static int netsocket = -1;
+static pthread_t netthreadid = AST_PTHREADT_NULL;
+static int tos = 0;
+static int dundidebug = 0;
+static int authdebug = 0;
+static int dundi_ttl = DUNDI_DEFAULT_TTL;
+static int dundi_key_ttl = DUNDI_DEFAULT_KEY_EXPIRE;
+static int global_autokilltimeout = 0;
+static dundi_eid global_eid;
+static int default_expiration = 60;
+static char dept[80];
+static char org[80];
+static char locality[80];
+static char stateprov[80];
+static char country[80];
+static char email[80];
+static char phone[80];
+static char secretpath[80];
+static char cursecret[80];
+static char ipaddr[80];
+static time_t rotatetime;
+static dundi_eid empty_eid = { { 0, 0, 0, 0, 0, 0 } };
+struct permission {
+ struct permission *next;
+ int allow;
+ char name[0];
+};
+
+struct dundi_packet {
+ struct dundi_hdr *h;
+ struct dundi_packet *next;
+ int datalen;
+ struct dundi_transaction *parent;
+ int retransid;
+ int retrans;
+ unsigned char data[0];
+};
+
+struct dundi_hint_metadata {
+ unsigned short flags;
+ char exten[AST_MAX_EXTENSION];
+};
+
+struct dundi_request;
+
+struct dundi_transaction {
+ struct sockaddr_in addr; /* Other end of transaction */
+ dundi_eid eids[DUNDI_MAX_STACK + 1];
+ int eidcount; /* Number of eids in eids */
+ dundi_eid us_eid; /* Our EID, to them */
+ dundi_eid them_eid; /* Their EID, to us */
+ aes_encrypt_ctx ecx; /* AES 128 Encryption context */
+ aes_decrypt_ctx dcx; /* AES 128 Decryption context */
+ int flags; /* Has final packet been sent */
+ int ttl; /* Remaining TTL for queries on this one */
+ int thread; /* We have a calling thread */
+ int autokillid; /* ID to kill connection if answer doesn't come back fast enough */
+ int autokilltimeout; /* Recommended timeout for autokill */
+ unsigned short strans; /* Our transaction identifier */
+ unsigned short dtrans; /* Their transaction identifer */
+ unsigned char iseqno; /* Next expected received seqno */
+ unsigned char oiseqno; /* Last received incoming seqno */
+ unsigned char oseqno; /* Next transmitted seqno */
+ unsigned char aseqno; /* Last acknowledge seqno */
+ struct dundi_packet *packets; /* Packets to be retransmitted */
+ struct dundi_packet *lasttrans; /* Last transmitted / ACK'd packet */
+ struct dundi_transaction *next; /* Next with respect to the parent */
+ struct dundi_request *parent; /* Parent request (if there is one) */
+ struct dundi_transaction *allnext; /* Next with respect to all DUNDi transactions */
+} *alltrans;
+
+struct dundi_request {
+ char dcontext[AST_MAX_EXTENSION];
+ char number[AST_MAX_EXTENSION];
+ dundi_eid query_eid;
+ dundi_eid root_eid;
+ struct dundi_result *dr;
+ struct dundi_entity_info *dei;
+ struct dundi_hint_metadata *hmd;
+ int maxcount;
+ int respcount;
+ int expiration;
+ unsigned long crc32; /* CRC-32 of all but root EID's in avoid list */
+ struct dundi_transaction *trans; /* Transactions */
+ struct dundi_request *next;
+} *requests;
+
+static struct dundi_mapping {
+ char dcontext[AST_MAX_EXTENSION];
+ char lcontext[AST_MAX_EXTENSION];
+ int weight;
+ int options;
+ int tech;
+ int dead;
+ char dest[AST_MAX_EXTENSION];
+ struct dundi_mapping *next;
+} *mappings = NULL;
+
+static struct dundi_peer {
+ dundi_eid eid;
+ struct sockaddr_in addr; /* Address of DUNDi peer */
+ struct permission *permit;
+ struct permission *include;
+ dundi_eid us_eid;
+ char inkey[80];
+ char outkey[80];
+ int dead;
+ int registerid;
+ int qualifyid;
+ int sentfullkey;
+ int order;
+ unsigned char txenckey[256]; /* Transmitted encrypted key + sig */
+ unsigned char rxenckey[256]; /* Cache received encrypted key + sig */
+ unsigned long us_keycrc32; /* CRC-32 of our key */
+ aes_encrypt_ctx us_ecx; /* Cached AES 128 Encryption context */
+ aes_decrypt_ctx us_dcx; /* Cached AES 128 Decryption context */
+ unsigned long them_keycrc32;/* CRC-32 of our key */
+ aes_encrypt_ctx them_ecx; /* Cached AES 128 Encryption context */
+ aes_decrypt_ctx them_dcx; /* Cached AES 128 Decryption context */
+ time_t keyexpire; /* When to expire/recreate key */
+ int registerexpire;
+ struct dundi_transaction *regtrans; /* Registration transaction */
+ struct dundi_transaction *qualtrans; /* Qualify transaction */
+ struct dundi_transaction *keypending;
+ int model;
+ int dynamic; /* Are we dynamic? */
+ int lastms; /* Last measured latency */
+ int maxms; /* Max permissible latency */
+ struct timeval qualtx; /* Time of transmit */
+ struct dundi_peer *next;
+} *peers;
+
+AST_MUTEX_DEFINE_STATIC(peerlock);
+
+static int dundi_xmit(struct dundi_packet *pack);
+
+static void dundi_debug_output(const char *data)
+{
+ if (dundidebug)
+ ast_verbose(data);
+}
+
+static void dundi_error_output(const char *data)
+{
+ ast_log(LOG_WARNING, data);
+}
+
+static int has_permission(struct permission *ps, char *cont)
+{
+ int res=0;
+ while(ps) {
+ if (!strcasecmp(ps->name, "all") || !strcasecmp(ps->name, cont))
+ res = ps->allow;
+ ps = ps->next;
+ }
+ return res;
+}
+
+static char *tech2str(int tech)
+{
+ switch(tech) {
+ case DUNDI_PROTO_NONE:
+ return "None";
+ case DUNDI_PROTO_IAX:
+ return "IAX2";
+ case DUNDI_PROTO_SIP:
+ return "SIP";
+ case DUNDI_PROTO_H323:
+ return "H323";
+ default:
+ return "Unknown";
+ }
+}
+
+static int str2tech(char *str)
+{
+ if (!strcasecmp(str, "IAX") || !strcasecmp(str, "IAX2"))
+ return DUNDI_PROTO_IAX;
+ else if (!strcasecmp(str, "SIP"))
+ return DUNDI_PROTO_SIP;
+ else if (!strcasecmp(str, "H323"))
+ return DUNDI_PROTO_H323;
+ else
+ return -1;
+}
+
+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, dundi_eid *avoid[], int direct[]);
+static struct dundi_transaction *create_transaction(struct dundi_peer *p);
+static struct dundi_transaction *find_transaction(struct dundi_hdr *hdr, struct sockaddr_in *sin)
+{
+ /* Look for an exact match first */
+ struct dundi_transaction *trans;
+ trans = alltrans;
+ while(trans) {
+ if (!inaddrcmp(&trans->addr, sin) &&
+ ((trans->strans == (ntohs(hdr->dtrans) & 32767)) /* Matches our destination */ ||
+ ((trans->dtrans == (ntohs(hdr->strans) & 32767)) && (!hdr->dtrans))) /* We match their destination */) {
+ if (hdr->strans)
+ trans->dtrans = ntohs(hdr->strans) & 32767;
+ break;
+ }
+ trans = trans->allnext;
+ }
+ if (!trans) {
+ switch(hdr->cmdresp & 0x7f) {
+ case DUNDI_COMMAND_DPDISCOVER:
+ case DUNDI_COMMAND_EIDQUERY:
+ case DUNDI_COMMAND_REGREQ:
+ case DUNDI_COMMAND_NULL:
+ case DUNDI_COMMAND_ENCRYPT:
+ if (hdr->strans) {
+ /* Create new transaction */
+ trans = create_transaction(NULL);
+ if (trans) {
+ memcpy(&trans->addr, sin, sizeof(trans->addr));
+ trans->dtrans = ntohs(hdr->strans) & 32767;
+ } else
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return trans;
+}
+
+static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied);
+
+static int dundi_ack(struct dundi_transaction *trans, int final)
+{
+ return dundi_send(trans, DUNDI_COMMAND_ACK, 0, final, NULL);
+}
+static void dundi_reject(struct dundi_hdr *h, struct sockaddr_in *sin)
+{
+ struct {
+ struct dundi_packet pack;
+ struct dundi_hdr hdr;
+ } tmp;
+ struct dundi_transaction trans;
+ /* Never respond to an INVALID with another INVALID */
+ if (h->cmdresp == DUNDI_COMMAND_INVALID)
+ return;
+ memset(&tmp, 0, sizeof(tmp));
+ memset(&trans, 0, sizeof(trans));
+ memcpy(&trans.addr, sin, sizeof(trans.addr));
+ tmp.hdr.strans = h->dtrans;
+ tmp.hdr.dtrans = h->strans;
+ tmp.hdr.iseqno = h->oseqno;
+ tmp.hdr.oseqno = h->iseqno;
+ tmp.hdr.cmdresp = DUNDI_COMMAND_INVALID;
+ tmp.hdr.cmdflags = 0;
+ tmp.pack.h = (struct dundi_hdr *)tmp.pack.data;
+ tmp.pack.datalen = sizeof(struct dundi_hdr);
+ tmp.pack.parent = &trans;
+ dundi_xmit(&tmp.pack);
+}
+
+static void reset_global_eid(void)
+{
+ int x,s;
+ char eid_str[20];
+ struct ifreq ifr;
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s > 0) {
+ x = 0;
+ for(x=0;x<10;x++) {
+ memset(&ifr, 0, sizeof(ifr));
+ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "eth%d", x);
+ if (!ioctl(s, SIOCGIFHWADDR, &ifr)) {
+ memcpy(&global_eid, ((unsigned char *)&ifr.ifr_hwaddr) + 2, sizeof(global_eid));
+ 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);
+ return;
+ }
+ }
+ }
+ ast_log(LOG_NOTICE, "No ethernet interface found for seeding global EID You will have to set it manually.\n");
+}
+
+static int get_trans_id(void)
+{
+ struct dundi_transaction *t;
+ int stid = (rand() % 32766) + 1;
+ int tid = stid;
+ do {
+ t = alltrans;
+ while(t) {
+ if (t->strans == tid)
+ break;
+ t = t->allnext;
+ }
+ if (!t)
+ return tid;
+ tid = (tid % 32766) + 1;
+ } while (tid != stid);
+ return 0;
+}
+
+static int reset_transaction(struct dundi_transaction *trans)
+{
+ int tid;
+ tid = get_trans_id();
+ if (tid < 1)
+ return -1;
+ trans->strans = tid;
+ trans->dtrans = 0;
+ trans->iseqno = 0;
+ trans->oiseqno = 0;
+ trans->oseqno = 0;
+ trans->aseqno = 0;
+ trans->flags &= ~FLAG_FINAL;
+ return 0;
+}
+
+static struct dundi_peer *find_peer(dundi_eid *eid)
+{
+ struct dundi_peer *cur;
+ if (!eid)
+ eid = &empty_eid;
+ cur = peers;
+ while(cur) {
+ if (!dundi_eid_cmp(&cur->eid,eid))
+ return cur;
+ cur = cur->next;
+ }
+ return NULL;
+}
+
+static void build_iv(unsigned char *iv)
+{
+ /* XXX Would be nice to be more random XXX */
+ unsigned int *fluffy;
+ int x;
+ fluffy = (unsigned int *)(iv);
+ for (x=0;x<4;x++)
+ fluffy[x] = rand();
+}
+
+struct dundi_query_state {
+ dundi_eid *eids[DUNDI_MAX_STACK + 1];
+ int directs[DUNDI_MAX_STACK + 1];
+ dundi_eid reqeid;
+ char called_context[AST_MAX_EXTENSION];
+ char called_number[AST_MAX_EXTENSION];
+ struct dundi_mapping *maps;
+ int nummaps;
+ struct dundi_transaction *trans;
+ void *chal;
+ int challen;
+ int ttl;
+ char fluffy[0];
+};
+
+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)
+{
+ int flags;
+ int x;
+ struct ast_channel *chan=NULL;
+ if (!ast_strlen_zero(map->lcontext)) {
+ flags = 0;
+ if (ast_exists_extension(NULL, map->lcontext, called_number, 1, NULL))
+ flags |= DUNDI_FLAG_EXISTS;
+ if (ast_canmatch_extension(NULL, map->lcontext, called_number, 1, NULL))
+ flags |= DUNDI_FLAG_CANMATCH;
+ if (ast_matchmore_extension(NULL, map->lcontext, called_number, 1, NULL))
+ flags |= DUNDI_FLAG_MATCHMORE;
+ if (ast_ignore_pattern(map->lcontext, called_number))
+ flags |= DUNDI_FLAG_IGNOREPAT;
+
+ /* Clearly we can't say 'don't ask' anymore if we found anything... */
+ if (flags)
+ hmd->flags &= ~DUNDI_HINT_DONT_ASK;
+
+ if (map->options & DUNDI_FLAG_INTERNAL_NOPARTIAL) {
+ /* Skip partial answers */
+ flags &= ~(DUNDI_FLAG_MATCHMORE|DUNDI_FLAG_CANMATCH);
+ }
+ if (flags) {
+ /* Clearly we can't say 'don't ask' anymore... */
+ chan = ast_channel_alloc(0);
+ if (chan) {
+ flags |= map->options & 0xffff;
+ dr[anscnt].flags = flags;
+ dr[anscnt].techint = map->tech;
+ dr[anscnt].weight = map->weight;
+ dr[anscnt].expiration = DUNDI_DEFAULT_CACHE_TIME;
+ strncpy(dr[anscnt].tech, tech2str(map->tech), sizeof(dr[anscnt].tech));
+ dr[anscnt].eid = *us_eid;
+ dundi_eid_to_str(dr[anscnt].eid_str, sizeof(dr[anscnt].eid_str), &dr[anscnt].eid);
+ if (flags & DUNDI_FLAG_EXISTS) {
+ pbx_builtin_setvar_helper(chan, "NUMBER", called_number);
+ pbx_builtin_setvar_helper(chan, "EID", dr[anscnt].eid_str);
+ pbx_builtin_setvar_helper(chan, "SECRET", cursecret);
+ pbx_builtin_setvar_helper(chan, "IPADDR", ipaddr);
+ pbx_substitute_variables_helper(chan, map->dest, dr[anscnt].dest, sizeof(dr[anscnt].dest));
+ } else
+ dr[anscnt].dest[0] = '\0';
+ anscnt++;
+ }
+ } else {
+ /* No answers... Find the fewest number of digits from the
+ number for which we have no answer. */
+ char tmp[AST_MAX_EXTENSION]="";
+ for (x=0;x<AST_MAX_EXTENSION;x++) {
+ tmp[x] = called_number[x];
+ if (!tmp[x])
+ break;
+ if (!ast_canmatch_extension(NULL, map->lcontext, tmp, 1, NULL)) {
+ /* Oops found something we can't match. If this is longer
+ than the running hint, we have to consider it */
+ if (strlen(tmp) > strlen(hmd->exten)) {
+ strncpy(hmd->exten, tmp, sizeof(hmd->exten) - 1);
+ }
+ break;
+ }
+ }
+ }
+ }
+ if (chan)
+ ast_hangup(chan);
+ return anscnt;
+}
+
+static void destroy_trans(struct dundi_transaction *trans, int fromtimeout);
+
+static void *dundi_lookup_thread(void *data)
+{
+ struct dundi_query_state *st = data;
+ struct dundi_result dr[MAX_RESULTS];
+ struct dundi_ie_data ied;
+ struct dundi_hint_metadata hmd;
+ char eid_str[20];
+ int res, x;
+ int ouranswers=0;
+ int max = 999999;
+ int expiration = DUNDI_DEFAULT_CACHE_TIME;
+
+ ast_log(LOG_DEBUG, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
+ st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) : "ourselves");
+ memset(&ied, 0, sizeof(ied));
+ memset(&dr, 0, sizeof(dr));
+ memset(&hmd, 0, sizeof(hmd));
+ /* Assume 'don't ask for anything' and 'unaffected', no TTL expired */
+ hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
+ for (x=0;x<st->nummaps;x++)
+ ouranswers = dundi_lookup_local(dr, st->maps + x, st->called_number, &st->trans->us_eid, ouranswers, &hmd);
+ if (ouranswers < 0)
+ ouranswers = 0;
+ for (x=0;x<ouranswers;x++) {
+ if (dr[x].weight < max)
+ max = dr[x].weight;
+ }
+
+ if (max) {
+ /* If we do not have a canonical result, keep looking */
+ res = dundi_lookup_internal(dr + ouranswers, MAX_RESULTS - ouranswers, NULL, st->called_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->eids, st->directs);
+ if (res > 0) {
+ /* Append answer in result */
+ ouranswers += res;
+ } else {
+ if ((res < -1) && (!ouranswers))
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_DUPLICATE, "Duplicate Request Pending");
+ }
+ }
+ ast_mutex_lock(&peerlock);
+ /* Truncate if "don't ask" isn't present */
+ if (!(hmd.flags & DUNDI_HINT_DONT_ASK))
+ hmd.exten[0] = '\0';
+ if (st->trans->flags & FLAG_DEAD) {
+ ast_log(LOG_DEBUG, "Our transaction went away!\n");
+ st->trans->thread = 0;
+ destroy_trans(st->trans, 0);
+ } else {
+ for (x=0;x<ouranswers;x++) {
+ /* Add answers */
+ if (dr[x].expiration && (expiration > dr[x].expiration))
+ expiration = dr[x].expiration;
+ dundi_ie_append_answer(&ied, DUNDI_IE_ANSWER, &dr[x].eid, dr[x].techint, dr[x].flags, dr[x].weight, dr[x].dest);
+ }
+ dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
+ dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, expiration);
+ dundi_send(st->trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+ st->trans->thread = 0;
+ }
+ ast_mutex_unlock(&peerlock);
+ free(st);
+ return NULL;
+}
+
+static inline int calc_ms(struct timeval *start)
+{
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ return ((tv.tv_sec - start->tv_sec) * 1000 + (tv.tv_usec - start->tv_usec) / 1000);
+}
+
+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[]);
+
+static void *dundi_query_thread(void *data)
+{
+ struct dundi_query_state *st = data;
+ struct dundi_entity_info dei;
+ struct dundi_ie_data ied;
+ struct dundi_hint_metadata hmd;
+ char eid_str[20];
+ int res;
+ ast_log(LOG_DEBUG, "Whee, looking up '%s@%s' for '%s'\n", st->called_number, st->called_context,
+ st->eids[0] ? dundi_eid_to_str(eid_str, sizeof(eid_str), st->eids[0]) : "ourselves");
+ memset(&ied, 0, sizeof(ied));
+ memset(&dei, 0, sizeof(dei));
+ memset(&hmd, 0, sizeof(hmd));
+ if (!dundi_eid_cmp(&st->trans->us_eid, &st->reqeid)) {
+ /* Ooh, it's us! */
+ ast_log(LOG_DEBUG, "Neat, someone look for us!\n");
+ strncpy(dei.orgunit, dept, sizeof(dei.orgunit));
+ strncpy(dei.org, org, sizeof(dei.org));
+ strncpy(dei.locality, locality, sizeof(dei.locality));
+ strncpy(dei.stateprov, stateprov, sizeof(dei.stateprov));
+ strncpy(dei.country, country, sizeof(dei.country));
+ strncpy(dei.email, email, sizeof(dei.email));
+ strncpy(dei.phone, phone, sizeof(dei.phone));
+ res = 1;
+ } else {
+ /* If we do not have a canonical result, keep looking */
+ res = dundi_query_eid_internal(&dei, st->called_context, &st->reqeid, &hmd, st->ttl, 1, st->eids);
+ }
+ ast_mutex_lock(&peerlock);
+ if (st->trans->flags & FLAG_DEAD) {
+ ast_log(LOG_DEBUG, "Our transaction went away!\n");
+ st->trans->thread = 0;
+ destroy_trans(st->trans, 0);
+ } else {
+ if (res) {
+ dundi_ie_append_str(&ied, DUNDI_IE_DEPARTMENT, dei.orgunit);
+ dundi_ie_append_str(&ied, DUNDI_IE_ORGANIZATION, dei.org);
+ dundi_ie_append_str(&ied, DUNDI_IE_LOCALITY, dei.locality);
+ dundi_ie_append_str(&ied, DUNDI_IE_STATE_PROV, dei.stateprov);
+ dundi_ie_append_str(&ied, DUNDI_IE_COUNTRY, dei.country);
+ dundi_ie_append_str(&ied, DUNDI_IE_EMAIL, dei.email);
+ dundi_ie_append_str(&ied, DUNDI_IE_PHONE, dei.phone);
+ if (!ast_strlen_zero(dei.ipaddr))
+ dundi_ie_append_str(&ied, DUNDI_IE_IPADDR, dei.ipaddr);
+ }
+ dundi_ie_append_hint(&ied, DUNDI_IE_HINT, hmd.flags, hmd.exten);
+ dundi_send(st->trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
+ st->trans->thread = 0;
+ }
+ ast_mutex_unlock(&peerlock);
+ free(st);
+ return NULL;
+}
+
+static int dundi_answer_entity(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
+{
+ struct dundi_query_state *st;
+ int totallen;
+ int x;
+ int skipfirst=0;
+ struct dundi_ie_data ied;
+ char eid_str[20];
+ char *s;
+ pthread_t lookupthread;
+ pthread_attr_t attr;
+ if (ies->eidcount > 1) {
+ /* Since it is a requirement that the first EID is the authenticating host
+ and the last EID is the root, it is permissible that the first and last EID
+ could be the same. In that case, we should go ahead copy only the "root" section
+ since we will not need it for authentication. */
+ if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
+ skipfirst = 1;
+ }
+ totallen = sizeof(struct dundi_query_state);
+ totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
+ st = malloc(totallen);
+ if (st) {
+ memset(st, 0, totallen);
+ strncpy(st->called_context, ies->called_context, sizeof(st->called_context) - 1);
+ memcpy(&st->reqeid, ies->reqeid, sizeof(st->reqeid));
+ st->trans = trans;
+ st->ttl = ies->ttl - 1;
+ if (st->ttl < 0)
+ st->ttl = 0;
+ s = st->fluffy;
+ for (x=skipfirst;ies->eids[x];x++) {
+ st->eids[x-skipfirst] = (dundi_eid *)s;
+ *st->eids[x-skipfirst] = *ies->eids[x];
+ s += sizeof(dundi_eid);
+ }
+ 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);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ trans->thread = 1;
+ if (ast_pthread_create(&lookupthread, &attr, dundi_query_thread, st)) {
+ trans->thread = 0;
+ ast_log(LOG_WARNING, "Unable to create thread!\n");
+ free(st);
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
+ dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
+ return -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
+ dundi_send(trans, DUNDI_COMMAND_EIDRESPONSE, 0, 1, &ied);
+ return -1;
+ }
+ return 0;
+}
+
+static int dundi_answer_query(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext)
+{
+ struct dundi_query_state *st;
+ int totallen;
+ int x;
+ struct dundi_ie_data ied;
+ char *s;
+ struct dundi_mapping *cur;
+ int mapcount;
+ int skipfirst = 0;
+
+ pthread_t lookupthread;
+ pthread_attr_t attr;
+ totallen = sizeof(struct dundi_query_state);
+ /* Count matching map entries */
+ mapcount = 0;
+ cur = mappings;
+ while(cur) {
+ if (!strcasecmp(cur->dcontext, ccontext))
+ mapcount++;
+ cur = cur->next;
+ }
+ /* If no maps, return -1 immediately */
+ if (!mapcount)
+ return -1;
+
+ if (ies->eidcount > 1) {
+ /* Since it is a requirement that the first EID is the authenticating host
+ and the last EID is the root, it is permissible that the first and last EID
+ could be the same. In that case, we should go ahead copy only the "root" section
+ since we will not need it for authentication. */
+ if (!dundi_eid_cmp(ies->eids[0], ies->eids[ies->eidcount - 1]))
+ skipfirst = 1;
+ }
+
+ totallen += mapcount * sizeof(struct dundi_mapping);
+ totallen += (ies->eidcount - skipfirst) * sizeof(dundi_eid);
+ st = malloc(totallen);
+ if (st) {
+ memset(st, 0, totallen);
+ strncpy(st->called_context, ies->called_context, sizeof(st->called_context) - 1);
+ strncpy(st->called_number, ies->called_number, sizeof(st->called_number) - 1);
+ st->trans = trans;
+ st->ttl = ies->ttl - 1;
+ if (st->ttl < 0)
+ st->ttl = 0;
+ s = st->fluffy;
+ for (x=skipfirst;ies->eids[x];x++) {
+ st->eids[x-skipfirst] = (dundi_eid *)s;
+ *st->eids[x-skipfirst] = *ies->eids[x];
+ st->directs[x-skipfirst] = ies->eid_direct[x];
+ s += sizeof(dundi_eid);
+ }
+ /* Append mappings */
+ x = 0;
+ st->maps = (struct dundi_mapping *)s;
+ cur = mappings;
+ while(cur) {
+ if (!strcasecmp(cur->dcontext, ccontext)) {
+ if (x < mapcount) {
+ st->maps[x] = *cur;
+ st->maps[x].next = NULL;
+ x++;
+ }
+ }
+ cur = cur->next;
+ }
+ st->nummaps = mapcount;
+ ast_log(LOG_DEBUG, "Answering query for '%s@%s'!\n", ies->called_number, ies->called_context);
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+ trans->thread = 1;
+ if (ast_pthread_create(&lookupthread, &attr, dundi_lookup_thread, st)) {
+ trans->thread = 0;
+ ast_log(LOG_WARNING, "Unable to create thread!\n");
+ free(st);
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of threads");
+ dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+ return -1;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Out of memory!\n");
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Out of memory");
+ dundi_send(trans, DUNDI_COMMAND_DPRESPONSE, 0, 1, &ied);
+ return -1;
+ }
+ return 0;
+}
+
+static int cache_save_hint(dundi_eid *eidpeer, struct dundi_request *req, struct dundi_hint *hint, int expiration)
+{
+ int unaffected;
+ char key1[256];
+ char key2[256];
+ char eidpeer_str[20];
+ char eidroot_str[20];
+ char data[80]="";
+ time_t timeout;
+
+ if (expiration < 0)
+ expiration = DUNDI_DEFAULT_CACHE_TIME;
+
+ /* Only cache hint if "don't ask" is there... */
+ if (!(ntohs(hint->flags)& DUNDI_HINT_DONT_ASK))
+ return 0;
+
+ unaffected = ntohs(hint->flags) & DUNDI_HINT_UNAFFECTED;
+
+ dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
+ dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
+ snprintf(key1, sizeof(key1), "hint/%s/%s/%s/e%08lx", eidpeer_str, hint->data, req->dcontext, unaffected ? 0 : req->crc32);
+ snprintf(key2, sizeof(key2), "hint/%s/%s/%s/r%s", eidpeer_str, hint->data, req->dcontext, eidroot_str);
+
+ time(&timeout);
+ timeout += expiration;
+ snprintf(data, sizeof(data), "%ld|", (long)(timeout));
+
+ ast_db_put("dundi/cache", key1, data);
+ ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key1);
+ ast_db_put("dundi/cache", key2, data);
+ ast_log(LOG_DEBUG, "Caching hint at '%s'\n", key2);
+ return 0;
+}
+
+static int cache_save(dundi_eid *eidpeer, struct dundi_request *req, int start, int unaffected, int expiration)
+{
+ int x;
+ char key1[256];
+ char key2[256];
+ char data[1024]="";
+ char eidpeer_str[20];
+ char eidroot_str[20];
+ time_t timeout;
+
+ if (expiration < 1)
+ expiration = DUNDI_DEFAULT_CACHE_TIME;
+ dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), eidpeer);
+ dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
+ snprintf(key1, sizeof(key1), "%s/%s/%s/e%08lx", eidpeer_str, req->number, req->dcontext, unaffected ? 0 : req->crc32);
+ snprintf(key2, sizeof(key2), "%s/%s/%s/r%s", eidpeer_str, req->number, req->dcontext, eidroot_str);
+ /* Build request string */
+ time(&timeout);
+ timeout += expiration;
+ snprintf(data, sizeof(data), "%ld|", (long)(timeout));
+ for (x=start;x<req->respcount;x++) {
+ /* Skip anything with an illegal pipe in it */
+ if (strchr(req->dr[x].dest, '|'))
+ continue;
+ snprintf(data + strlen(data), sizeof(data) - strlen(data), "%d/%d/%d/%s/%s|",
+ req->dr[x].flags, req->dr[x].weight, req->dr[x].techint, req->dr[x].dest,
+ dundi_eid_to_str_short(eidpeer_str, sizeof(eidpeer_str), &req->dr[x].eid));
+ }
+ ast_db_put("dundi/cache", key1, data);
+ ast_db_put("dundi/cache", key2, data);
+ return 0;
+}
+
+static int cache_lookup_internal(time_t now, struct dundi_request *req, char *key, char *eid_str_full, int *lowexpiration)
+{
+ char data[1024]="";
+ char *ptr, *term, *src;
+ int tech;
+ int flags;
+ int weight;
+ int length;
+ int z;
+ int expiration;
+ char fs[256];
+ time_t timeout;
+ /* Build request string */
+ if (!ast_db_get("dundi/cache", key, data, sizeof(data))) {
+ ptr = data;
+ if (sscanf(ptr, "%ld|%n", &timeout, &length) == 1) {
+ expiration = timeout - now;
+ if (expiration > 0) {
+ ast_log(LOG_DEBUG, "Found cache expiring in %d seconds!\n", (int)(timeout - now));
+ ptr += length;
+ while((sscanf(ptr, "%d/%d/%d/%n", &flags, &weight, &tech, &length) == 3)) {
+ ptr += length;
+ term = strchr(ptr, '|');
+ if (term) {
+ *term = '\0';
+ src = strrchr(ptr, '/');
+ if (src) {
+ *src = '\0';
+ src++;
+ } else
+ src = "";
+ ast_log(LOG_DEBUG, "Found cached answer '%s/%s' originally from '%s' with flags '%s' on behalf of '%s'\n",
+ tech2str(tech), ptr, src, dundi_flags2str(fs, sizeof(fs), flags), eid_str_full);
+ /* Make sure it's not already there */
+ for (z=0;z<req->respcount;z++) {
+ if ((req->dr[z].techint == tech) &&
+ !strcmp(req->dr[z].dest, ptr))
+ break;
+ }
+ if (z == req->respcount) {
+ /* Copy into parent responses */
+ req->dr[req->respcount].flags = flags;
+ req->dr[req->respcount].weight = weight;
+ req->dr[req->respcount].techint = tech;
+ req->dr[req->respcount].expiration = expiration;
+ dundi_str_short_to_eid(&req->dr[req->respcount].eid, src);
+ strncpy(req->dr[req->respcount].dest, ptr,
+ sizeof(req->dr[req->respcount].dest));
+ strncpy(req->dr[req->respcount].tech, tech2str(tech),
+ sizeof(req->dr[req->respcount].tech));
+ req->respcount++;
+ req->hmd->flags &= ~DUNDI_HINT_DONT_ASK;
+ } else if (req->dr[z].weight > weight)
+ req->dr[z].weight = weight;
+ ptr = term + 1;
+ }
+ }
+ /* We found *something* cached */
+ if (expiration < *lowexpiration)
+ *lowexpiration = expiration;
+ return 1;
+ } else
+ ast_db_del("dundi/cache", key);
+ } else
+ ast_db_del("dundi/cache", key);
+ }
+
+ return 0;
+}
+
+static int cache_lookup(struct dundi_request *req, dundi_eid *peer_eid, unsigned long crc32, int *lowexpiration)
+{
+ char key[256];
+ char eid_str[20];
+ char eidroot_str[20];
+ time_t now;
+ int res=0;
+ int res2=0;
+ char eid_str_full[20];
+ char tmp[256]="";
+ int x;
+
+ time(&now);
+ dundi_eid_to_str_short(eid_str, sizeof(eid_str), peer_eid);
+ dundi_eid_to_str_short(eidroot_str, sizeof(eidroot_str), &req->root_eid);
+ dundi_eid_to_str(eid_str_full, sizeof(eid_str_full), peer_eid);
+ snprintf(key, sizeof(key), "%s/%s/%s/e%08lx", eid_str, req->number, req->dcontext, crc32);
+ res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ snprintf(key, sizeof(key), "%s/%s/%s/e%08lx", eid_str, req->number, req->dcontext, 0L);
+ res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ snprintf(key, sizeof(key), "%s/%s/%s/r%s", eid_str, req->number, req->dcontext, eidroot_str);
+ res |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ x = 0;
+ if (!req->respcount) {
+ while(!res2) {
+ /* Look and see if we have a hint that would preclude us from looking at this
+ peer for this number. */
+ if (!(tmp[x] = req->number[x]))
+ break;
+ x++;
+ /* Check for hints */
+ snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08lx", eid_str, tmp, req->dcontext, crc32);
+ res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ snprintf(key, sizeof(key), "hint/%s/%s/%s/e%08lx", eid_str, tmp, req->dcontext, 0L);
+ res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ snprintf(key, sizeof(key), "hint/%s/%s/%s/r%s", eid_str, tmp, req->dcontext, eidroot_str);
+ res2 |= cache_lookup_internal(now, req, key, eid_str_full, lowexpiration);
+ if (res2) {
+ if (strlen(tmp) > strlen(req->hmd->exten)) {
+ /* Update meta data if appropriate */
+ strncpy(req->hmd->exten, tmp, sizeof(req->hmd->exten) - 1);
+ }
+ }
+ }
+ res |= res2;
+ }
+
+ return res;
+}
+
+static void qualify_peer(struct dundi_peer *peer, int schedonly);
+
+static void apply_peer(struct dundi_transaction *trans, struct dundi_peer *p)
+{
+ if (!trans->addr.sin_addr.s_addr)
+ memcpy(&trans->addr, &p->addr, sizeof(trans->addr));
+ trans->us_eid = p->us_eid;
+ trans->them_eid = p->eid;
+ /* Enable encryption if appropriate */
+ if (!ast_strlen_zero(p->inkey))
+ trans->flags |= FLAG_ENCRYPT;
+ if (p->maxms)
+ trans->autokilltimeout = p->maxms;
+ else
+ trans->autokilltimeout = global_autokilltimeout;
+}
+
+static int do_register_expire(void *data)
+{
+ struct dundi_peer *peer = data;
+ char eid_str[20];
+ /* Called with peerlock already held */
+ ast_log(LOG_DEBUG, "Register expired for '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ peer->registerexpire = -1;
+ peer->lastms = 0;
+ memset(&peer->addr, 0, sizeof(peer->addr));
+ return 0;
+}
+
+static int update_key(struct dundi_peer *peer)
+{
+ unsigned char key[16];
+ struct ast_key *ekey, *skey;
+ char eid_str[20];
+ int res;
+ if (!peer->keyexpire || (peer->keyexpire < time(NULL))) {
+ build_iv(key);
+ aes_encrypt_key128(key, &peer->us_ecx);
+ aes_decrypt_key128(key, &peer->us_dcx);
+ ekey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
+ if (!ekey) {
+ ast_log(LOG_NOTICE, "No such key '%s' for creating RSA encrypted shared key for '%s'!\n",
+ peer->inkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ return -1;
+ }
+ skey = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
+ if (!skey) {
+ ast_log(LOG_NOTICE, "No such key '%s' for signing RSA encrypted shared key for '%s'!\n",
+ peer->outkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ return -1;
+ }
+ if ((res = ast_encrypt_bin(peer->txenckey, key, sizeof(key), ekey)) != 128) {
+ ast_log(LOG_NOTICE, "Whoa, got a weird encrypt size (%d != %d)!\n", res, 128);
+ return -1;
+ }
+ if ((res = ast_sign_bin(skey, peer->txenckey, 128, peer->txenckey + 128))) {
+ ast_log(LOG_NOTICE, "Failed to sign key (%d)!\n", res);
+ return -1;
+ }
+ peer->us_keycrc32 = crc32(0L, peer->txenckey, 128);
+ peer->sentfullkey = 0;
+ /* Looks good */
+ time(&peer->keyexpire);
+ peer->keyexpire += dundi_key_ttl;
+ }
+ return 0;
+}
+
+static int encrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, aes_encrypt_ctx *ecx)
+{
+ unsigned char curblock[16];
+ int x;
+ memcpy(curblock, iv, sizeof(curblock));
+ while(len > 0) {
+ for (x=0;x<16;x++)
+ curblock[x] ^= src[x];
+ aes_encrypt(curblock, dst, ecx);
+ memcpy(curblock, dst, sizeof(curblock));
+ dst += 16;
+ src += 16;
+ len -= 16;
+ }
+ return 0;
+}
+static int decrypt_memcpy(unsigned char *dst, unsigned char *src, int len, unsigned char *iv, aes_decrypt_ctx *dcx)
+{
+ unsigned char lastblock[16];
+ int x;
+ memcpy(lastblock, iv, sizeof(lastblock));
+ while(len > 0) {
+ aes_decrypt(src, dst, dcx);
+ for (x=0;x<16;x++)
+ dst[x] ^= lastblock[x];
+ memcpy(lastblock, src, sizeof(lastblock));
+ dst += 16;
+ src += 16;
+ len -= 16;
+ }
+ return 0;
+}
+
+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)
+{
+ int space = *dstlen;
+ unsigned long bytes;
+ struct dundi_hdr *h;
+ unsigned char *decrypt_space;
+ decrypt_space = alloca(srclen);
+ if (!decrypt_space)
+ return NULL;
+ decrypt_memcpy(decrypt_space, src->encdata, srclen, src->iv, &trans->dcx);
+ /* Setup header */
+ h = (struct dundi_hdr *)dst;
+ *h = *ohdr;
+ bytes = space - 6;
+ if (uncompress(dst + 6, &bytes, decrypt_space, srclen) != Z_OK) {
+ ast_log(LOG_DEBUG, "Ouch, uncompress failed :(\n");
+ return NULL;
+ }
+ /* Update length */
+ *dstlen = bytes + 6;
+ /* Return new header */
+ return h;
+}
+
+static int dundi_encrypt(struct dundi_transaction *trans, struct dundi_packet *pack)
+{
+ unsigned char *compress_space;
+ int len;
+ int res;
+ unsigned long bytes;
+ struct dundi_ie_data ied;
+ struct dundi_peer *peer;
+ unsigned char iv[16];
+ len = pack->datalen + pack->datalen / 100 + 42;
+ compress_space = alloca(len);
+ if (compress_space) {
+ memset(compress_space, 0, len);
+ /* We care about everthing save the first 6 bytes of header */
+ bytes = len;
+ res = compress(compress_space, &bytes, pack->data + 6, pack->datalen - 6);
+ if (res != Z_OK) {
+ ast_log(LOG_DEBUG, "Ouch, compression failed!\n");
+ return -1;
+ }
+ memset(&ied, 0, sizeof(ied));
+ /* Say who we are */
+ if (!pack->h->iseqno && !pack->h->oseqno) {
+ /* Need the key in the first copy */
+ if (!(peer = find_peer(&trans->them_eid)))
+ return -1;
+ if (update_key(peer))
+ return -1;
+ if (!peer->sentfullkey)
+ trans->flags |= FLAG_SENDFULLKEY;
+ /* Append key data */
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
+ if (trans->flags & FLAG_SENDFULLKEY) {
+ dundi_ie_append_raw(&ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
+ dundi_ie_append_raw(&ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
+ } else {
+ dundi_ie_append_int(&ied, DUNDI_IE_KEYCRC32, peer->us_keycrc32);
+ }
+ /* Setup contexts */
+ trans->ecx = peer->us_ecx;
+ trans->dcx = peer->us_dcx;
+
+ /* We've sent the full key */
+ peer->sentfullkey = 1;
+ }
+ /* Build initialization vector */
+ build_iv(iv);
+ /* Add the field, rounded up to 16 bytes */
+ dundi_ie_append_encdata(&ied, DUNDI_IE_ENCDATA, iv, NULL, ((bytes + 15) / 16) * 16);
+ /* Copy the data */
+ if ((ied.pos + bytes) >= sizeof(ied.buf)) {
+ ast_log(LOG_NOTICE, "Final packet too large!\n");
+ return -1;
+ }
+ encrypt_memcpy(ied.buf + ied.pos, compress_space, bytes, iv, &trans->ecx);
+ ied.pos += ((bytes + 15) / 16) * 16;
+ /* Reconstruct header */
+ pack->datalen = sizeof(struct dundi_hdr);
+ pack->h->cmdresp = DUNDI_COMMAND_ENCRYPT;
+ pack->h->cmdflags = 0;
+ memcpy(pack->h->ies, ied.buf, ied.pos);
+ pack->datalen += ied.pos;
+ return 0;
+ }
+ return -1;
+}
+
+static int check_key(struct dundi_peer *peer, unsigned char *newkey, unsigned char *newsig, unsigned long keycrc32)
+{
+ unsigned char dst[128];
+ int res;
+ struct ast_key *key, *skey;
+ char eid_str[20];
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Expected '%08lx' got '%08lx'\n", peer->them_keycrc32, keycrc32);
+ if (peer->them_keycrc32 && (peer->them_keycrc32 == keycrc32)) {
+ /* A match */
+ return 1;
+ } else if (!newkey || !newsig)
+ return 0;
+ if (!memcmp(peer->rxenckey, newkey, 128) &&
+ !memcmp(peer->rxenckey + 128, newsig, 128)) {
+ /* By definition, a match */
+ return 1;
+ }
+ /* Decrypt key */
+ key = ast_key_get(peer->outkey, AST_KEY_PRIVATE);
+ if (!key) {
+ ast_log(LOG_NOTICE, "Unable to find key '%s' to decode shared key from '%s'\n",
+ peer->outkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ return -1;
+ }
+
+ skey = ast_key_get(peer->inkey, AST_KEY_PUBLIC);
+ if (!skey) {
+ ast_log(LOG_NOTICE, "Unable to find key '%s' to verify shared key from '%s'\n",
+ peer->inkey, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ return -1;
+ }
+
+ /* First check signature */
+ res = ast_check_signature_bin(skey, newkey, 128, newsig);
+ if (res)
+ return 0;
+
+ res = ast_decrypt_bin(dst, newkey, sizeof(dst), key);
+ if (res != 16) {
+ if (res >= 0)
+ ast_log(LOG_NOTICE, "Weird, key decoded to the wrong size (%d)\n", res);
+ return 0;
+ }
+ /* Decrypted, passes signature */
+ ast_log(LOG_DEBUG, "Wow, new key combo passed signature and decrypt!\n");
+ memcpy(peer->rxenckey, newkey, 128);
+ memcpy(peer->rxenckey + 128, newsig, 128);
+ peer->them_keycrc32 = crc32(0L, peer->rxenckey, 128);
+ aes_decrypt_key128(dst, &peer->them_dcx);
+ aes_encrypt_key128(dst, &peer->them_ecx);
+ return 1;
+}
+
+static int handle_command_response(struct dundi_transaction *trans, struct dundi_hdr *hdr, int datalen, int encrypted)
+{
+ /* Handle canonical command / response */
+ int final = hdr->cmdresp & 0x80;
+ int cmd = hdr->cmdresp & 0x7f;
+ int x,y,z;
+ int resp;
+ int res;
+ unsigned char *bufcpy;
+ struct dundi_ie_data ied;
+ struct dundi_ies ies;
+ struct dundi_peer *peer;
+ char eid_str[20];
+ char eid_str2[20];
+ memset(&ied, 0, sizeof(ied));
+ memset(&ies, 0, sizeof(ies));
+ if (datalen) {
+ bufcpy = alloca(datalen);
+ if (!bufcpy)
+ return -1;
+ /* Make a copy for parsing */
+ memcpy(bufcpy, hdr->ies, datalen);
+ ast_log(LOG_DEBUG, "Got canonical message %d (%d), %d bytes data%s\n", cmd, hdr->oseqno, datalen, final ? " (Final)" : "");
+ if (dundi_parse_ies(&ies, bufcpy, datalen) < 0) {
+ ast_log(LOG_WARNING, "Failed to parse DUNDI information elements!\n");
+ return -1;
+ }
+ }
+ switch(cmd) {
+ case DUNDI_COMMAND_DPDISCOVER:
+ case DUNDI_COMMAND_EIDQUERY:
+ if (cmd == DUNDI_COMMAND_EIDQUERY)
+ resp = DUNDI_COMMAND_EIDRESPONSE;
+ else
+ resp = DUNDI_COMMAND_DPRESPONSE;
+ /* A dialplan or entity discover -- qualify by highest level entity */
+ peer = find_peer(ies.eids[0]);
+ if (!peer) {
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
+ dundi_send(trans, resp, 0, 1, &ied);
+ } else {
+ int hasauth = 0;
+ trans->us_eid = peer->us_eid;
+ if (strlen(peer->inkey)) {
+ hasauth = encrypted;
+ } else
+ hasauth = 1;
+ if (hasauth) {
+ /* Okay we're authentiated and all, now we check if they're authorized */
+ if (!ies.called_context)
+ ies.called_context = "e164";
+ if (cmd == DUNDI_COMMAND_EIDQUERY) {
+ res = dundi_answer_entity(trans, &ies, ies.called_context);
+ } else {
+ if (!ies.called_number || ast_strlen_zero(ies.called_number)) {
+ /* They're not permitted to access that context */
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_GENERAL, "Invalid or missing number/entity");
+ dundi_send(trans, resp, 0, 1, &ied);
+ } else if (has_permission(peer->permit, ies.called_context)) {
+ res = dundi_answer_query(trans, &ies, ies.called_context);
+ if (res < 0) {
+ /* There is no such dundi context */
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unsupported DUNDI Context");
+ dundi_send(trans, resp, 0, 1, &ied);
+ }
+
+ } else {
+ /* They're not permitted to access that context */
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Permission to context denied");
+ dundi_send(trans, resp, 0, 1, &ied);
+ }
+ }
+ } else {
+ /* They're not permitted to access that context */
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Unencrypted responses not permitted");
+ dundi_send(trans, resp, 0, 1, &ied);
+ }
+ }
+ break;
+ case DUNDI_COMMAND_REGREQ:
+ /* A register request -- should only have one entity */
+ peer = find_peer(ies.eids[0]);
+ if (!peer || !peer->dynamic) {
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, NULL);
+ dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, &ied);
+ } else {
+ int hasauth = 0;
+ trans->us_eid = peer->us_eid;
+ if (!ast_strlen_zero(peer->inkey)) {
+ hasauth = encrypted;
+ } else
+ hasauth = 1;
+ if (hasauth) {
+ int expire = default_expiration;
+ char iabuf[INET_ADDRSTRLEN];
+ char data[256];
+ int needqual = 0;
+ if (peer->registerexpire > -1)
+ ast_sched_del(sched, peer->registerexpire);
+ peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
+ ast_inet_ntoa(iabuf, sizeof(iabuf), trans->addr.sin_addr);
+ snprintf(data, sizeof(data), "%s:%d:%d", iabuf, ntohs(trans->addr.sin_port), expire);
+ ast_db_put("dundi/dpeers", dundi_eid_to_str_short(eid_str, sizeof(eid_str), &peer->eid), data);
+ if (inaddrcmp(&peer->addr, &trans->addr)) {
+ if (option_verbose > 2)
+ ast_verbose(VERBOSE_PREFIX_3 "Registered DUNDi peer '%s' at '%s:%d'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), iabuf, ntohs(trans->addr.sin_port));
+ needqual = 1;
+ }
+
+ memcpy(&peer->addr, &trans->addr, sizeof(peer->addr));
+ dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, default_expiration);
+ dundi_send(trans, DUNDI_COMMAND_REGRESPONSE, 0, 1, &ied);
+ if (needqual)
+ qualify_peer(peer, 1);
+ }
+ }
+ break;
+ case DUNDI_COMMAND_DPRESPONSE:
+ /* A dialplan response, lets see what we got... */
+ if (ies.cause < 1) {
+ int authpass=0;
+ /* Success of some sort */
+ ast_log(LOG_DEBUG, "Looks like success of some sort (%d), %d answers\n", ies.cause, ies.anscount);
+ if (trans->flags & FLAG_ENCRYPT) {
+ authpass = encrypted;
+ } else
+ authpass = 1;
+ if (authpass) {
+ /* Pass back up answers */
+ if (trans->parent && trans->parent->dr) {
+ y = trans->parent->respcount;
+ for (x=0;x<ies.anscount;x++) {
+ if (trans->parent->respcount < trans->parent->maxcount) {
+ /* Make sure it's not already there */
+ for (z=0;z<trans->parent->respcount;z++) {
+ if ((trans->parent->dr[z].techint == ies.answers[x]->protocol) &&
+ !strcmp(trans->parent->dr[z].dest, ies.answers[x]->data))
+ break;
+ }
+ if (z == trans->parent->respcount) {
+ /* Copy into parent responses */
+ trans->parent->dr[trans->parent->respcount].flags = ntohs(ies.answers[x]->flags);
+ trans->parent->dr[trans->parent->respcount].techint = ies.answers[x]->protocol;
+ trans->parent->dr[trans->parent->respcount].weight = ntohs(ies.answers[x]->weight);
+ trans->parent->dr[trans->parent->respcount].eid = ies.answers[x]->eid;
+ if (ies.expiration > 0)
+ trans->parent->dr[trans->parent->respcount].expiration = ies.expiration;
+ else
+ trans->parent->dr[trans->parent->respcount].expiration = DUNDI_DEFAULT_CACHE_TIME;
+ dundi_eid_to_str(trans->parent->dr[trans->parent->respcount].eid_str,
+ sizeof(trans->parent->dr[trans->parent->respcount].eid_str),
+ &ies.answers[x]->eid);
+ strncpy(trans->parent->dr[trans->parent->respcount].dest, ies.answers[x]->data,
+ sizeof(trans->parent->dr[trans->parent->respcount].dest));
+ strncpy(trans->parent->dr[trans->parent->respcount].tech, tech2str(ies.answers[x]->protocol),
+ sizeof(trans->parent->dr[trans->parent->respcount].tech));
+ trans->parent->respcount++;
+ trans->parent->hmd->flags &= ~DUNDI_HINT_DONT_ASK;
+ } else if (trans->parent->dr[z].weight > ies.answers[x]->weight) {
+ /* Update weight if appropriate */
+ trans->parent->dr[z].weight = ies.answers[x]->weight;
+ }
+ } else
+ ast_log(LOG_NOTICE, "Dropping excessive answers to request for %s@%s\n",
+ trans->parent->number, trans->parent->dcontext);
+ }
+ /* Save all the results (if any) we had. Even if no results, still cache lookup. Let
+ the cache know if this request was unaffected by our entity list. */
+ cache_save(&trans->them_eid, trans->parent, y,
+ ies.hint ? ntohs(ies.hint->flags) & DUNDI_HINT_UNAFFECTED : 0, ies.expiration);
+ if (ies.hint) {
+ cache_save_hint(&trans->them_eid, trans->parent, ies.hint, ies.expiration);
+ if (ntohs(ies.hint->flags) & DUNDI_HINT_TTL_EXPIRED)
+ trans->parent->hmd->flags |= DUNDI_HINT_TTL_EXPIRED;
+ if (ntohs(ies.hint->flags) & DUNDI_HINT_DONT_ASK) {
+ if (strlen(ies.hint->data) > strlen(trans->parent->hmd->exten)) {
+ strncpy(trans->parent->hmd->exten, ies.hint->data,
+ sizeof(trans->parent->hmd->exten) - 1);
+ }
+ } else {
+ trans->parent->hmd->flags &= ~DUNDI_HINT_DONT_ASK;
+ }
+ }
+ if (ies.expiration > 0) {
+ if (trans->parent->expiration > ies.expiration) {
+ trans->parent->expiration = ies.expiration;
+ }
+ }
+ }
+ /* Close connection if not final */
+ if (!final)
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+
+ } else {
+ /* Auth failure, check for data */
+ if (!final) {
+ /* Cancel if they didn't already */
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+ }
+ break;
+ case DUNDI_COMMAND_EIDRESPONSE:
+ /* A dialplan response, lets see what we got... */
+ if (ies.cause < 1) {
+ int authpass=0;
+ /* Success of some sort */
+ ast_log(LOG_DEBUG, "Looks like success of some sort (%d)\n", ies.cause);
+ if (trans->flags & FLAG_ENCRYPT) {
+ authpass = encrypted;
+ } else
+ authpass = 1;
+ if (authpass) {
+ /* Pass back up answers */
+ if (trans->parent && trans->parent->dei && ies.q_org) {
+ if (!trans->parent->respcount) {
+ trans->parent->respcount++;
+ if (ies.q_dept)
+ strncpy(trans->parent->dei->orgunit, ies.q_dept, sizeof(trans->parent->dei->orgunit) - 1);
+ if (ies.q_org)
+ strncpy(trans->parent->dei->org, ies.q_org, sizeof(trans->parent->dei->org) - 1);
+ if (ies.q_locality)
+ strncpy(trans->parent->dei->locality, ies.q_locality, sizeof(trans->parent->dei->locality) - 1);
+ if (ies.q_stateprov)
+ strncpy(trans->parent->dei->stateprov, ies.q_stateprov, sizeof(trans->parent->dei->stateprov) - 1);
+ if (ies.q_country)
+ strncpy(trans->parent->dei->country, ies.q_country, sizeof(trans->parent->dei->country) - 1);
+ if (ies.q_email)
+ strncpy(trans->parent->dei->email, ies.q_email, sizeof(trans->parent->dei->email) - 1);
+ if (ies.q_phone)
+ strncpy(trans->parent->dei->phone, ies.q_phone, sizeof(trans->parent->dei->phone) - 1);
+ if (ies.q_ipaddr)
+ strncpy(trans->parent->dei->ipaddr, ies.q_ipaddr, sizeof(trans->parent->dei->ipaddr) - 1);
+ if (!dundi_eid_cmp(&trans->them_eid, &trans->parent->query_eid)) {
+ /* If it's them, update our address */
+ ast_inet_ntoa(trans->parent->dei->ipaddr, sizeof(trans->parent->dei->ipaddr),
+ trans->addr.sin_addr);
+ }
+ }
+ if (ies.hint) {
+ if (ntohs(ies.hint->flags) & DUNDI_HINT_TTL_EXPIRED)
+ trans->parent->hmd->flags |= DUNDI_HINT_TTL_EXPIRED;
+ }
+ }
+ /* Close connection if not final */
+ if (!final)
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+
+ } else {
+ /* Auth failure, check for data */
+ if (!final) {
+ /* Cancel if they didn't already */
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+ }
+ break;
+ case DUNDI_COMMAND_REGRESPONSE:
+ /* A dialplan response, lets see what we got... */
+ if (ies.cause < 1) {
+ int hasauth;
+ /* Success of some sort */
+ if (trans->flags & FLAG_ENCRYPT) {
+ hasauth = encrypted;
+ } else
+ hasauth = 1;
+
+ if (!hasauth) {
+ ast_log(LOG_NOTICE, "Reponse to register not authorized!\n");
+ if (!final) {
+ dundi_ie_append_cause(&ied, DUNDI_IE_CAUSE, DUNDI_CAUSE_NOAUTH, "Improper signature in answer");
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, &ied);
+ }
+ } else {
+ 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),
+ dundi_eid_to_str(eid_str2, sizeof(eid_str2), &trans->them_eid));
+ /* Close connection if not final */
+ if (!final)
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+ } else {
+ /* Auth failure, cancel if they didn't for some reason */
+ if (!final) {
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ }
+ }
+ break;
+ case DUNDI_COMMAND_INVALID:
+ case DUNDI_COMMAND_NULL:
+ /* Do nothing special */
+ if (!final)
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ break;
+ case DUNDI_COMMAND_ENCREJ:
+ if ((trans->flags & FLAG_SENDFULLKEY) || !trans->lasttrans || !(peer = find_peer(&trans->them_eid))) {
+ /* No really, it's over at this point */
+ if (!final)
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ } else {
+ /* Send with full key */
+ trans->flags |= FLAG_SENDFULLKEY;
+ if (final) {
+ /* Ooops, we got a final message, start by sending ACK... */
+ dundi_ack(trans, hdr->cmdresp & 0x80);
+ trans->aseqno = trans->iseqno;
+ /* Now, we gotta create a new transaction */
+ if (!reset_transaction(trans)) {
+ /* Make sure handle_frame doesn't destroy us */
+ hdr->cmdresp &= 0x7f;
+ /* Parse the message we transmitted */
+ memset(&ies, 0, sizeof(ies));
+ dundi_parse_ies(&ies, trans->lasttrans->h->ies, trans->lasttrans->datalen - sizeof(struct dundi_hdr));
+ /* Reconstruct outgoing encrypted packet */
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
+ dundi_ie_append_raw(&ied, DUNDI_IE_SHAREDKEY, peer->txenckey, 128);
+ dundi_ie_append_raw(&ied, DUNDI_IE_SIGNATURE, peer->txenckey + 128, 128);
+ if (ies.encblock)
+ dundi_ie_append_encdata(&ied, DUNDI_IE_ENCDATA, ies.encblock->iv, ies.encblock->encdata, ies.enclen);
+ dundi_send(trans, DUNDI_COMMAND_ENCRYPT, 0, trans->lasttrans->h->cmdresp & 0x80, &ied);
+ peer->sentfullkey = 1;
+ }
+ }
+ }
+ break;
+ case DUNDI_COMMAND_ENCRYPT:
+ if (!encrypted) {
+ /* No nested encryption! */
+ if ((trans->iseqno == 1) && !trans->oseqno) {
+ if (!ies.eids[0] || !(peer = find_peer(ies.eids[0])) ||
+ ((!ies.encsharedkey || !ies.encsig) && !ies.keycrc32) ||
+ (check_key(peer, ies.encsharedkey, ies.encsig, ies.keycrc32) < 1)) {
+ if (!final) {
+ dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
+ }
+ break;
+ }
+ apply_peer(trans, peer);
+ /* Key passed, use new contexts for this session */
+ trans->ecx = peer->them_ecx;
+ trans->dcx = peer->them_dcx;
+ }
+ if ((trans->flags & FLAG_ENCRYPT) && ies.encblock && ies.enclen) {
+ struct dundi_hdr *dhdr;
+ unsigned char decoded[MAX_PACKET_SIZE];
+ int ddatalen;
+ ddatalen = sizeof(decoded);
+ dhdr = dundi_decrypt(trans, decoded, &ddatalen, hdr, ies.encblock, ies.enclen);
+ if (dhdr) {
+ /* Handle decrypted response */
+ if (dundidebug)
+ dundi_showframe(dhdr, 3, &trans->addr, ddatalen - sizeof(struct dundi_hdr));
+ handle_command_response(trans, dhdr, ddatalen - sizeof(struct dundi_hdr), 1);
+ /* Carry back final flag */
+ hdr->cmdresp |= dhdr->cmdresp & 0x80;
+ break;
+ } else
+ ast_log(LOG_DEBUG, "Ouch, decrypt failed :(\n");
+ }
+ }
+ if (!final) {
+ /* Turn off encryption */
+ trans->flags &= ~FLAG_ENCRYPT;
+ dundi_send(trans, DUNDI_COMMAND_ENCREJ, 0, 1, NULL);
+ }
+ break;
+ default:
+ /* Send unknown command if we don't know it, with final flag IFF it's the
+ first command in the dialog and only if we haven't recieved final notification */
+ if (!final) {
+ dundi_ie_append_byte(&ied, DUNDI_IE_UNKNOWN, cmd);
+ dundi_send(trans, DUNDI_COMMAND_UNKNOWN, 0, !hdr->oseqno, &ied);
+ }
+ }
+ return 0;
+}
+
+static void destroy_packet(struct dundi_packet *pack, int needfree);
+static void destroy_packets(struct dundi_packet *p)
+{
+ struct dundi_packet *prev;
+ while(p) {
+ prev = p;
+ p = p->next;
+ if (prev->retransid > -1)
+ ast_sched_del(sched, prev->retransid);
+ free(prev);
+ }
+}
+
+
+static int ack_trans(struct dundi_transaction *trans, int iseqno)
+{
+ /* Ack transmitted packet corresponding to iseqno */
+ struct dundi_packet *pack;
+ pack = trans->packets;
+ while(pack) {
+ if ((pack->h->oseqno + 1) % 255 == iseqno) {
+ destroy_packet(pack, 0);
+ if (trans->lasttrans) {
+ ast_log(LOG_WARNING, "Whoa, there was still a last trans?\n");
+ destroy_packets(trans->lasttrans);
+ }
+ trans->lasttrans = pack;
+ if (trans->autokillid > -1)
+ ast_sched_del(sched, trans->autokillid);
+ trans->autokillid = -1;
+ return 1;
+ }
+ pack = pack->next;
+ }
+ return 0;
+}
+
+static int handle_frame(struct dundi_hdr *h, struct sockaddr_in *sin, int datalen)
+{
+ struct dundi_transaction *trans;
+ trans = find_transaction(h, sin);
+ if (!trans) {
+ dundi_reject(h, sin);
+ return 0;
+ }
+ /* Got a transaction, see where this header fits in */
+ if (h->oseqno == trans->iseqno) {
+ /* Just what we were looking for... Anything but ack increments iseqno */
+ if (ack_trans(trans, h->iseqno) && (trans->flags & FLAG_FINAL)) {
+ /* If final, we're done */
+ destroy_trans(trans, 0);
+ return 0;
+ }
+ if (h->cmdresp != DUNDI_COMMAND_ACK) {
+ trans->oiseqno = trans->iseqno;
+ trans->iseqno++;
+ handle_command_response(trans, h, datalen, 0);
+ }
+ if (trans->aseqno != trans->iseqno) {
+ dundi_ack(trans, h->cmdresp & 0x80);
+ trans->aseqno = trans->iseqno;
+ }
+ /* Delete any saved last transmissions */
+ destroy_packets(trans->lasttrans);
+ trans->lasttrans = NULL;
+ if (h->cmdresp & 0x80) {
+ /* Final -- destroy now */
+ destroy_trans(trans, 0);
+ }
+ } else if (h->oseqno == trans->oiseqno) {
+ /* Last incoming sequence number -- send ACK without processing */
+ dundi_ack(trans, 0);
+ } else {
+ /* Out of window -- simply drop */
+ ast_log(LOG_DEBUG, "Dropping packet out of window!\n");
+ }
+ return 0;
+}
+
+static int socket_read(int *id, int fd, short events, void *cbdata)
+{
+ struct sockaddr_in sin;
+ int res;
+ struct dundi_hdr *h;
+ unsigned char buf[MAX_PACKET_SIZE];
+ int len;
+ len = sizeof(sin);
+ res = recvfrom(netsocket, buf, sizeof(buf) - 1, 0,(struct sockaddr *) &sin, &len);
+ if (res < 0) {
+ if (errno != ECONNREFUSED)
+ ast_log(LOG_WARNING, "Error: %s\n", strerror(errno));
+ return 1;
+ }
+ if (res < sizeof(struct dundi_hdr)) {
+ ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, (int)sizeof(struct dundi_hdr));
+ return 1;
+ }
+ buf[res] = '\0';
+ h = (struct dundi_hdr *)buf;
+ if (dundidebug)
+ dundi_showframe(h, 1, &sin, res - sizeof(struct dundi_hdr));
+ ast_mutex_lock(&peerlock);
+ handle_frame(h, &sin, res - sizeof(struct dundi_hdr));
+ ast_mutex_unlock(&peerlock);
+ return 1;
+}
+
+static void build_secret(char *secret, int seclen)
+{
+ char tmp[16];
+ char *s;
+ build_iv(tmp);
+ secret[0] = '\0';
+ ast_base64encode(secret ,tmp, sizeof(tmp), seclen);
+ /* Eliminate potential bad characters */
+ while((s = strchr(secret, ';'))) *s = '+';
+ while((s = strchr(secret, '/'))) *s = '+';
+ while((s = strchr(secret, ':'))) *s = '+';
+ while((s = strchr(secret, '@'))) *s = '+';
+}
+
+
+static void save_secret(const char *newkey, const char *oldkey)
+{
+ char tmp[256];
+ if (oldkey)
+ snprintf(tmp, sizeof(tmp), "%s;%s", oldkey, newkey);
+ else
+ snprintf(tmp, sizeof(tmp), "%s", newkey);
+ rotatetime = time(NULL) + DUNDI_SECRET_TIME;
+ ast_db_put(secretpath, "secret", tmp);
+ snprintf(tmp, sizeof(tmp), "%ld", rotatetime);
+ ast_db_put(secretpath, "secretexpiry", tmp);
+}
+
+static void load_password(void)
+{
+ char *current=NULL;
+ char *last=NULL;
+ char tmp[256];
+ time_t expired;
+
+ ast_db_get(secretpath, "secretexpiry", tmp, sizeof(tmp));
+ if (sscanf(tmp, "%ld", &expired) == 1) {
+ ast_db_get(secretpath, "secret", tmp, sizeof(tmp));
+ current = strchr(tmp, ';');
+ if (!current)
+ current = tmp;
+ else {
+ *current = '\0';
+ current++;
+ };
+ if ((time(NULL) - expired) < 0) {
+ if ((expired - time(NULL)) > DUNDI_SECRET_TIME)
+ expired = time(NULL) + DUNDI_SECRET_TIME;
+ } else if ((time(NULL) - (expired + DUNDI_SECRET_TIME)) < 0) {
+ last = current;
+ current = NULL;
+ } else {
+ last = NULL;
+ current = NULL;
+ }
+ }
+ if (current) {
+ /* Current key is still valid, just setup rotatation properly */
+ strncpy(cursecret, current, sizeof(cursecret) - 1);
+ rotatetime = expired;
+ } else {
+ /* Current key is out of date, rotate or eliminate all together */
+ build_secret(cursecret, sizeof(cursecret));
+ save_secret(cursecret, last);
+ }
+}
+
+static void check_password(void)
+{
+ char oldsecret[80];
+ time_t now;
+
+ time(&now);
+#if 0
+ printf("%ld/%ld\n", now, rotatetime);
+#endif
+ if ((now - rotatetime) >= 0) {
+ /* Time to rotate keys */
+ strncpy(oldsecret, cursecret, sizeof(oldsecret) - 1);
+ build_secret(cursecret, sizeof(cursecret));
+ save_secret(cursecret, oldsecret);
+ }
+}
+
+static void *network_thread(void *ignore)
+{
+ /* Our job is simple: Send queued messages, retrying if necessary. Read frames
+ from the network, and queue them for delivery to the channels */
+ int res;
+ /* Establish I/O callback for socket read */
+ ast_io_add(io, netsocket, socket_read, AST_IO_IN, NULL);
+ for(;;) {
+ res = ast_sched_wait(sched);
+ if ((res > 1000) || (res < 0))
+ res = 1000;
+ res = ast_io_wait(io, res);
+ if (res >= 0) {
+ ast_mutex_lock(&peerlock);
+ ast_sched_runq(sched);
+ ast_mutex_unlock(&peerlock);
+ }
+ check_password();
+ }
+ return NULL;
+}
+
+static int start_network_thread(void)
+{
+ return ast_pthread_create(&netthreadid, NULL, network_thread, NULL);
+}
+
+static int dundi_do_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ dundidebug = 1;
+ ast_cli(fd, "DUNDi Debugging Enabled\n");
+ return RESULT_SUCCESS;
+}
+
+static int dundi_flush(int fd, int argc, char *argv[])
+{
+ if (argc != 2)
+ return RESULT_SHOWUSAGE;
+ ast_db_deltree("dundi/cache", NULL);
+ ast_cli(fd, "DUNDi Cache Flushed\n");
+ return RESULT_SUCCESS;
+}
+
+static int dundi_no_debug(int fd, int argc, char *argv[])
+{
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ dundidebug = 0;
+ ast_cli(fd, "DUNDi Debugging Disabled\n");
+ return RESULT_SUCCESS;
+}
+
+static char *model2str(int model)
+{
+ switch(model) {
+ case DUNDI_MODEL_INBOUND:
+ return "Inbound";
+ case DUNDI_MODEL_OUTBOUND:
+ return "Outbound";
+ case DUNDI_MODEL_SYMMETRIC:
+ return "Symmetric";
+ default:
+ return "Unknown";
+ }
+}
+
+static char *complete_peer_helper(char *line, char *word, int pos, int state, int rpos)
+{
+ int which=0;
+ char *ret;
+ struct dundi_peer *p;
+ char eid_str[20];
+ if (pos != rpos)
+ return NULL;
+ ast_mutex_lock(&peerlock);
+ p = peers;
+ while(p) {
+ if (!strncasecmp(word, dundi_eid_to_str(eid_str, sizeof(eid_str), &p->eid), strlen(word))) {
+ if (++which > state)
+ break;
+ }
+ p = p->next;
+ }
+ if (p) {
+ ret = strdup(dundi_eid_to_str(eid_str, sizeof(eid_str), &p->eid));
+ } else
+ ret = NULL;
+ ast_mutex_unlock(&peerlock);
+ return ret;
+}
+
+static char *complete_peer_4(char *line, char *word, int pos, int state)
+{
+ return complete_peer_helper(line, word, pos, state, 3);
+}
+
+static int rescomp(const void *a, const void *b)
+{
+ const struct dundi_result *resa, *resb;
+ resa = a;
+ resb = b;
+ if (resa->weight < resb->weight)
+ return -1;
+ if (resa->weight > resb->weight)
+ return 1;
+ return 0;
+}
+
+static void sort_results(struct dundi_result *results, int count)
+{
+ qsort(results, count, sizeof(results[0]), rescomp);
+}
+
+static int dundi_do_lookup(int fd, int argc, char *argv[])
+{
+ int res;
+ char tmp[256] = "";
+ char fs[80] = "";
+ char *context;
+ int x;
+ struct dundi_result dr[MAX_RESULTS];
+ if ((argc < 3) || (argc > 3))
+ return RESULT_SHOWUSAGE;
+ strncpy(tmp, argv[2], sizeof(tmp) - 1);
+ context = strchr(tmp, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ }
+ res = dundi_lookup(dr, MAX_RESULTS, NULL, context, tmp);
+ if (res < 0)
+ ast_cli(fd, "DUNDi lookup returned error.\n");
+ else if (!res)
+ ast_cli(fd, "DUNDi lookup returned no results.\n");
+ else
+ sort_results(dr, res);
+ for (x=0;x<res;x++) {
+ ast_cli(fd, "%d. %5d %s/%s (%s)\n", x + 1, dr[x].weight, dr[x].tech, dr[x].dest, dundi_flags2str(fs, sizeof(fs), dr[x].flags));
+ }
+ return RESULT_SUCCESS;
+}
+
+static int dundi_do_query(int fd, int argc, char *argv[])
+{
+ int res;
+ char tmp[256] = "";
+ char *context;
+ dundi_eid eid;
+ struct dundi_entity_info dei;
+ if ((argc < 3) || (argc > 3))
+ return RESULT_SHOWUSAGE;
+ if (dundi_str_to_eid(&eid, argv[2])) {
+ ast_cli(fd, "'%s' is not a valid EID!\n", argv[2]);
+ return RESULT_SHOWUSAGE;
+ }
+ strncpy(tmp, argv[2], sizeof(tmp) - 1);
+ context = strchr(tmp, '@');
+ if (context) {
+ *context = '\0';
+ context++;
+ }
+ res = dundi_query_eid(&dei, context, eid);
+ if (res < 0)
+ ast_cli(fd, "DUNDi Query EID returned error.\n");
+ else if (!res)
+ ast_cli(fd, "DUNDi Query EID returned no results.\n");
+ else {
+ ast_cli(fd, "DUNDi Query EID succeeded:\n");
+ ast_cli(fd, "Department: %s\n", dei.orgunit);
+ ast_cli(fd, "Organization: %s\n", dei.org);
+ ast_cli(fd, "City/Locality: %s\n", dei.locality);
+ ast_cli(fd, "State/Province: %s\n", dei.stateprov);
+ ast_cli(fd, "Country: %s\n", dei.country);
+ ast_cli(fd, "E-mail: %s\n", dei.email);
+ ast_cli(fd, "Phone: %s\n", dei.phone);
+ ast_cli(fd, "IP Address: %s\n", dei.ipaddr);
+ }
+ return RESULT_SUCCESS;
+}
+
+static int dundi_show_peer(int fd, int argc, char *argv[])
+{
+ struct dundi_peer *peer;
+ struct permission *p;
+ char *order;
+ char iabuf[INET_ADDRSTRLEN];
+ char eid_str[20];
+
+ if (argc != 4)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&peerlock);
+ peer = peers;
+ while(peer) {
+ if (!strcasecmp(dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), argv[3]))
+ break;
+ peer = peer->next;
+ }
+ if (peer) {
+ switch(peer->order) {
+ case 0:
+ order = "Primary";
+ break;
+ case 1:
+ order = "Secondary";
+ break;
+ case 2:
+ order = "Tertiary";
+ break;
+ case 3:
+ order = "Quartiary";
+ break;
+ default:
+ order = "Unknown";
+ }
+ ast_cli(fd, "Peer: %s\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ ast_cli(fd, "Model: %s\n", model2str(peer->model));
+ ast_cli(fd, "Host: %s\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "<Unspecified>");
+ ast_cli(fd, "Dynamic: %s\n", peer->dynamic ? "yes" : "no");
+ ast_cli(fd, "KeyPend: %s\n", peer->keypending ? "yes" : "no");
+ ast_cli(fd, "Reg: %s\n", peer->registerid < 0 ? "No" : "Yes");
+ ast_cli(fd, "In Key: %s\n", ast_strlen_zero(peer->inkey) ? "<None>" : peer->inkey);
+ ast_cli(fd, "Out Key: %s\n", ast_strlen_zero(peer->outkey) ? "<None>" : peer->outkey);
+ if (peer->include) {
+ ast_cli(fd, "Include logic%s:\n", peer->model & DUNDI_MODEL_OUTBOUND ? "" : " (IGNORED)");
+ }
+ p = peer->include;
+ while(p) {
+ ast_cli(fd, "-- %s %s\n", p->allow ? "include" : "do not include", p->name);
+ p = p->next;
+ }
+ if (peer->permit) {
+ ast_cli(fd, "Query logic%s:\n", peer->model & DUNDI_MODEL_INBOUND ? "" : " (IGNORED)");
+ }
+ p = peer->permit;
+ while(p) {
+ ast_cli(fd, "-- %s %s\n", p->allow ? "permit" : "deny", p->name);
+ p = p->next;
+ }
+
+ } else
+ ast_cli(fd, "No such peer '%s'\n", argv[3]);
+ ast_mutex_unlock(&peerlock);
+ return RESULT_SUCCESS;
+}
+
+static int dundi_show_peers(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-20.20s %-15.15s %-15.15s %-15.15s\n"
+#define FORMAT "%-20.20s %-15.15s %s %-15.15s %-15.15s\n"
+ struct dundi_peer *peer;
+ char iabuf[INET_ADDRSTRLEN];
+ int registeredonly=0;
+ char eid_str[20];
+ if ((argc != 3) && (argc != 4) && (argc != 5))
+ return RESULT_SHOWUSAGE;
+ if ((argc == 4)) {
+ if (!strcasecmp(argv[3], "registered")) {
+ registeredonly = 1;
+ } else
+ return RESULT_SHOWUSAGE;
+ }
+ ast_mutex_lock(&peerlock);
+ ast_cli(fd, FORMAT2, "EID", "Host", "Model", "Status");
+ for (peer = peers;peer;peer = peer->next) {
+ char status[20] = "";
+ int print_line = -1;
+ char srch[2000] = "";
+ if (registeredonly && !peer->addr.sin_addr.s_addr)
+ continue;
+ if (peer->maxms) {
+ if (peer->lastms < 0)
+ strncpy(status, "UNREACHABLE", sizeof(status) - 1);
+ else if (peer->lastms > peer->maxms)
+ snprintf(status, sizeof(status), "LAGGED (%d ms)", peer->lastms);
+ else if (peer->lastms)
+ snprintf(status, sizeof(status), "OK (%d ms)", peer->lastms);
+ else
+ strncpy(status, "UNKNOWN", sizeof(status) - 1);
+ } else
+ strncpy(status, "Unmonitored", sizeof(status) - 1);
+
+ snprintf(srch, sizeof(srch), FORMAT, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
+ peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)",
+ peer->dynamic ? "(D)" : "(S)", model2str(peer->model), status);
+
+ if (argc == 5) {
+ if (!strcasecmp(argv[3],"include") && strstr(srch,argv[4])) {
+ print_line = -1;
+ } else if (!strcasecmp(argv[3],"exclude") && !strstr(srch,argv[4])) {
+ print_line = 1;
+ } else if (!strcasecmp(argv[3],"begin") && !strncasecmp(srch,argv[4],strlen(argv[4]))) {
+ print_line = -1;
+ } else {
+ print_line = 0;
+ }
+ }
+
+ if (print_line) {
+ ast_cli(fd, FORMAT, dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid),
+ peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : "(Unspecified)",
+ peer->dynamic ? "(D)" : "(S)", model2str(peer->model), status);
+ }
+ }
+ ast_mutex_unlock(&peerlock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static int dundi_show_trans(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-22.22s %-5.5s %-5.5s %-3.3s %-3.3s %-3.3s\n"
+#define FORMAT "%-16.16s:%5d %-5.5d %-5.5d %-3.3d %-3.3d %-3.3d\n"
+ struct dundi_transaction *trans;
+ char iabuf[INET_ADDRSTRLEN];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&peerlock);
+ ast_cli(fd, FORMAT2, "Remote", "Src", "Dst", "Tx", "Rx", "Ack");
+ for (trans = alltrans;trans;trans = trans->allnext) {
+ ast_cli(fd, FORMAT, ast_inet_ntoa(iabuf, sizeof(iabuf), trans->addr.sin_addr),
+ ntohs(trans->addr.sin_port), trans->strans, trans->dtrans, trans->oseqno, trans->iseqno, trans->aseqno);
+ }
+ ast_mutex_unlock(&peerlock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static int dundi_show_entityid(int fd, int argc, char *argv[])
+{
+ char eid_str[20];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&peerlock);
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &global_eid);
+ ast_mutex_unlock(&peerlock);
+ ast_cli(fd, "Global EID for this system is '%s'\n", eid_str);
+ return RESULT_SUCCESS;
+}
+
+static int dundi_show_requests(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-15s %-15s %-15s %-3.3s %-3.3s\n"
+#define FORMAT "%-15s %-15s %-15s %-3.3d %-3.3d\n"
+ struct dundi_request *req;
+ char eidstr[20];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&peerlock);
+ ast_cli(fd, FORMAT2, "Number", "Context", "Root", "Max", "Rsp");
+ for (req = requests;req;req = req->next) {
+ ast_cli(fd, FORMAT, req->number, req->dcontext,
+ dundi_eid_zero(&req->root_eid) ? "<unspecified>" : dundi_eid_to_str(eidstr, sizeof(eidstr), &req->root_eid), req->maxcount, req->respcount);
+ }
+ ast_mutex_unlock(&peerlock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+/* Grok-a-dial DUNDi */
+
+static int dundi_show_mappings(int fd, int argc, char *argv[])
+{
+#define FORMAT2 "%-12.12s %-7.7s %-12.12s %-10.10s %-5.5s %-25.25s\n"
+#define FORMAT "%-12.12s %-7d %-12.12s %-10.10s %-5.5s %-25.25s\n"
+ struct dundi_mapping *map;
+ char fs[256];
+ if (argc != 3)
+ return RESULT_SHOWUSAGE;
+ ast_mutex_lock(&peerlock);
+ ast_cli(fd, FORMAT2, "DUNDi Cntxt", "Weight", "Local Cntxt", "Options", "Tech", "Destination");
+ for (map = mappings;map;map = map->next) {
+ ast_cli(fd, FORMAT, map->dcontext, map->weight,
+ ast_strlen_zero(map->lcontext) ? "<none>" : map->lcontext,
+ dundi_flags2str(fs, sizeof(fs), map->options), tech2str(map->tech), map->dest);
+ }
+ ast_mutex_unlock(&peerlock);
+ return RESULT_SUCCESS;
+#undef FORMAT
+#undef FORMAT2
+}
+
+static char debug_usage[] =
+"Usage: dundi debug\n"
+" Enables dumping of DUNDi packets for debugging purposes\n";
+
+static char no_debug_usage[] =
+"Usage: dundi no debug\n"
+" Disables dumping of DUNDi packets for debugging purposes\n";
+
+static char show_peers_usage[] =
+"Usage: dundi show peers\n"
+" Lists all known DUNDi peers.\n";
+
+static char show_trans_usage[] =
+"Usage: dundi show trans\n"
+" Lists all known DUNDi transactions.\n";
+
+static char show_mappings_usage[] =
+"Usage: dundi show mappings\n"
+" Lists all known DUNDi mappings.\n";
+
+static char show_entityid_usage[] =
+"Usage: dundi show entityid\n"
+" Displays the global entityid for this host.\n";
+
+static char show_peer_usage[] =
+"Usage: dundi show peer [peer]\n"
+" Provide a detailed description of a specifid DUNDi peer.\n";
+
+static char show_requests_usage[] =
+"Usage: dundi show requests\n"
+" Lists all known pending DUNDi requests.\n";
+
+static char lookup_usage[] =
+"Usage: dundi lookup <number>[@context]\n"
+" Lookup the given number within the given DUNDi context\n"
+"(or e164 if none is specified).\n"
+"if specified.\n";
+
+static char query_usage[] =
+"Usage: dundi query <entity>[@context]\n"
+" Attempts to retrieve contact information for a specific\n"
+"DUNDi entity identifier (EID) within a given DUNDi context (or\n"
+"e164 if none is specified).\n";
+
+static char flush_usage[] =
+"Usage: dundi flush\n"
+" Flushes DUNDi answer cache, used primarily for debug.\n";
+
+static struct ast_cli_entry cli_debug =
+ { { "dundi", "debug", NULL }, dundi_do_debug, "Enable DUNDi debugging", debug_usage };
+
+static struct ast_cli_entry cli_flush =
+ { { "dundi", "flush", NULL }, dundi_flush, "Flush DUNDi cache", flush_usage };
+
+static struct ast_cli_entry cli_no_debug =
+ { { "dundi", "no", "debug", NULL }, dundi_no_debug, "Disable DUNDi debugging", no_debug_usage };
+
+static struct ast_cli_entry cli_show_peers =
+ { { "dundi", "show", "peers", NULL }, dundi_show_peers, "Show defined DUNDi peers", show_peers_usage };
+
+static struct ast_cli_entry cli_show_trans =
+ { { "dundi", "show", "trans", NULL }, dundi_show_trans, "Show active DUNDi transactions", show_trans_usage };
+
+static struct ast_cli_entry cli_show_entityid =
+ { { "dundi", "show", "entityid", NULL }, dundi_show_entityid, "Display Global Entity ID", show_entityid_usage };
+
+static struct ast_cli_entry cli_show_mappings =
+ { { "dundi", "show", "mappings", NULL }, dundi_show_mappings, "Show DUNDi mappings", show_mappings_usage };
+
+static struct ast_cli_entry cli_show_requests =
+ { { "dundi", "show", "requests", NULL }, dundi_show_requests, "Show DUNDi requests", show_requests_usage };
+
+static struct ast_cli_entry cli_show_peer =
+ { { "dundi", "show", "peer", NULL }, dundi_show_peer, "Show info on a specific DUNDi peer", show_peer_usage, complete_peer_4 };
+
+static struct ast_cli_entry cli_lookup =
+ { { "dundi", "lookup", NULL }, dundi_do_lookup, "Lookup a number in DUNDi", lookup_usage };
+
+static struct ast_cli_entry cli_queryeid =
+ { { "dundi", "query", NULL }, dundi_do_query, "Query a DUNDi EID", query_usage };
+
+STANDARD_LOCAL_USER;
+
+LOCAL_USER_DECL;
+
+static struct dundi_transaction *create_transaction(struct dundi_peer *p)
+{
+ struct dundi_transaction *trans;
+ int tid;
+
+ /* Don't allow creation of transactions to non-registered peers */
+ if (p && !p->addr.sin_addr.s_addr)
+ return NULL;
+ tid = get_trans_id();
+ if (tid < 1)
+ return NULL;
+ trans = malloc(sizeof(struct dundi_transaction));
+ if (trans) {
+ memset(trans, 0, sizeof(struct dundi_transaction));
+ trans->autokillid = -1;
+ if (p) {
+ apply_peer(trans, p);
+ if (!p->sentfullkey)
+ trans->flags |= FLAG_SENDFULLKEY;
+ }
+ trans->strans = tid;
+ trans->allnext = alltrans;
+ alltrans = trans;
+ }
+ return trans;
+}
+
+static int dundi_xmit(struct dundi_packet *pack)
+{
+ int res;
+ char iabuf[INET_ADDRSTRLEN];
+ if (dundidebug)
+ dundi_showframe(pack->h, 0, &pack->parent->addr, pack->datalen - sizeof(struct dundi_hdr));
+ res = sendto(netsocket, pack->data, pack->datalen, 0, (struct sockaddr *)&pack->parent->addr, sizeof(pack->parent->addr));
+ if (res < 0) {
+ ast_log(LOG_WARNING, "Failed to transmit to '%s:%d': %s\n",
+ ast_inet_ntoa(iabuf, sizeof(iabuf), pack->parent->addr.sin_addr),
+ ntohs(pack->parent->addr.sin_port), strerror(errno));
+ }
+ if (res > 0)
+ res = 0;
+ return res;
+}
+
+static void destroy_packet(struct dundi_packet *pack, int needfree)
+{
+ struct dundi_packet *prev, *cur;
+ if (pack->parent) {
+ prev = NULL;
+ cur = pack->parent->packets;
+ while(cur) {
+ if (cur == pack) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ pack->parent->packets = cur->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ }
+ if (pack->retransid > -1)
+ ast_sched_del(sched, pack->retransid);
+ if (needfree)
+ free(pack);
+ else {
+ pack->retransid = -1;
+ pack->next = NULL;
+ }
+}
+
+static void destroy_trans(struct dundi_transaction *trans, int fromtimeout)
+{
+ struct dundi_transaction *cur, *prev;
+ struct dundi_peer *peer;
+ struct timeval tv;
+ int ms;
+ char eid_str[20];
+ if (trans->flags & (FLAG_ISREG | FLAG_ISQUAL)) {
+ peer = peers;
+ while (peer) {
+ if (peer->regtrans == trans)
+ peer->regtrans = NULL;
+ if (peer->keypending == trans)
+ peer->keypending = NULL;
+ if (peer->qualtrans == trans) {
+ if (fromtimeout) {
+ if (peer->lastms > -1)
+ ast_log(LOG_NOTICE, "Peer '%s' has become UNREACHABLE!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ peer->lastms = -1;
+ } else {
+ gettimeofday(&tv, NULL);
+ ms = (tv.tv_sec - peer->qualtx.tv_sec) * 1000 +
+ (tv.tv_usec - peer->qualtx.tv_usec) / 1000;
+ if (ms < 1)
+ ms = 1;
+ if (ms < peer->maxms) {
+ if ((peer->lastms >= peer->maxms) || (peer->lastms < 0))
+ ast_log(LOG_NOTICE, "Peer '%s' has become REACHABLE!\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ } else if (peer->lastms < peer->maxms) {
+ 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);
+ }
+ peer->lastms = ms;
+ }
+ peer->qualtrans = NULL;
+ }
+ peer = peer->next;
+ }
+ }
+ if (trans->parent) {
+ /* Unlink from parent if appropriate */
+ prev = NULL;
+ cur = trans->parent->trans;
+ while(cur) {
+ if (cur == trans) {
+ if (prev)
+ prev->next = trans->next;
+ else
+ trans->parent->trans = trans->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ }
+ /* Unlink from all trans */
+ prev = NULL;
+ cur = alltrans;
+ while(cur) {
+ if (cur == trans) {
+ if (prev)
+ prev->allnext = trans->allnext;
+ else
+ alltrans = trans->allnext;
+ break;
+ }
+ prev = cur;
+ cur = cur->allnext;
+ }
+ destroy_packets(trans->packets);
+ destroy_packets(trans->lasttrans);
+ trans->packets = NULL;
+ if (trans->autokillid > -1)
+ ast_sched_del(sched, trans->autokillid);
+ trans->autokillid = -1;
+ if (trans->thread) {
+ /* If used by a thread, mark as dead and be done */
+ trans->flags |= FLAG_DEAD;
+ } else
+ free(trans);
+}
+
+static int dundi_rexmit(void *data)
+{
+ struct dundi_packet *pack;
+ char iabuf[INET_ADDRSTRLEN];
+ int res;
+ ast_mutex_lock(&peerlock);
+ pack = data;
+ if (pack->retrans < 1) {
+ pack->retransid = -1;
+ if (!(pack->parent->flags & FLAG_ISQUAL))
+ ast_log(LOG_NOTICE, "Max retries exceeded to host '%s:%d' msg %d on call %d\n",
+ ast_inet_ntoa(iabuf, sizeof(iabuf), pack->parent->addr.sin_addr),
+ ntohs(pack->parent->addr.sin_port), pack->h->oseqno, ntohs(pack->h->strans));
+ destroy_trans(pack->parent, 1);
+ res = 0;
+ } else {
+ /* Decrement retransmission, try again */
+ pack->retrans--;
+ dundi_xmit(pack);
+ res = 1;
+ }
+ ast_mutex_unlock(&peerlock);
+ return res;
+}
+
+static int dundi_send(struct dundi_transaction *trans, int cmdresp, int flags, int final, struct dundi_ie_data *ied)
+{
+ struct dundi_packet *pack;
+ int res;
+ int len;
+ char eid_str[20];
+ len = sizeof(struct dundi_packet) + sizeof(struct dundi_hdr) + (ied ? ied->pos : 0);
+ /* Reserve enough space for encryption */
+ if (trans->flags & FLAG_ENCRYPT)
+ len += 384;
+ pack = malloc(len);
+ if (pack) {
+ memset(pack, 0, len);
+ pack->h = (struct dundi_hdr *)(pack->data);
+ if (cmdresp != DUNDI_COMMAND_ACK) {
+ pack->retransid = ast_sched_add(sched, DUNDI_DEFAULT_RETRANS_TIMER, dundi_rexmit, pack);
+ pack->retrans = DUNDI_DEFAULT_RETRANS - 1;
+ pack->next = trans->packets;
+ trans->packets = pack;
+ }
+ pack->parent = trans;
+ pack->h->strans = htons(trans->strans);
+ pack->h->dtrans = htons(trans->dtrans);
+ pack->h->iseqno = trans->iseqno;
+ pack->h->oseqno = trans->oseqno;
+ pack->h->cmdresp = cmdresp;
+ pack->datalen = sizeof(struct dundi_hdr);
+ if (ied) {
+ memcpy(pack->h->ies, ied->buf, ied->pos);
+ pack->datalen += ied->pos;
+ }
+ if (final) {
+ pack->h->cmdresp |= DUNDI_COMMAND_FINAL;
+ trans->flags |= FLAG_FINAL;
+ }
+ pack->h->cmdflags = flags;
+ if (cmdresp != DUNDI_COMMAND_ACK) {
+ trans->oseqno++;
+ trans->oseqno = trans->oseqno % 256;
+ }
+ trans->aseqno = trans->iseqno;
+ /* If we have their public key, encrypt */
+ if (trans->flags & FLAG_ENCRYPT) {
+ switch(cmdresp) {
+ case DUNDI_COMMAND_REGREQ:
+ case DUNDI_COMMAND_REGRESPONSE:
+ case DUNDI_COMMAND_DPDISCOVER:
+ case DUNDI_COMMAND_DPRESPONSE:
+ case DUNDI_COMMAND_EIDQUERY:
+ case DUNDI_COMMAND_EIDRESPONSE:
+ if (dundidebug)
+ dundi_showframe(pack->h, 2, &trans->addr, pack->datalen - sizeof(struct dundi_hdr));
+ res = dundi_encrypt(trans, pack);
+ break;
+ default:
+ res = 0;
+ }
+ } else
+ res = 0;
+ if (!res)
+ res = dundi_xmit(pack);
+ if (res)
+ ast_log(LOG_NOTICE, "Failed to send packet to '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));
+
+ if (cmdresp == DUNDI_COMMAND_ACK)
+ free(pack);
+ return res;
+ }
+ return -1;
+}
+
+static int do_autokill(void *data)
+{
+ struct dundi_transaction *trans = data;
+ char eid_str[20];
+ ast_log(LOG_NOTICE, "Transaction to '%s' took too long to ACK, destroying\n",
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &trans->them_eid));
+ trans->autokillid = -1;
+ destroy_trans(trans, 0); /* We could actually set it to 1 instead of 0, but we won't ;-) */
+ return 0;
+}
+
+static void dundi_ie_append_eid_appropriately(struct dundi_ie_data *ied, char *context, dundi_eid *eid, dundi_eid *us)
+{
+ struct dundi_peer *p;
+ if (!dundi_eid_cmp(eid, us)) {
+ dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
+ return;
+ }
+ ast_mutex_lock(&peerlock);
+ p = peers;
+ while(p) {
+ if (!dundi_eid_cmp(&p->eid, eid)) {
+ if (has_permission(p->include, context))
+ dundi_ie_append_eid(ied, DUNDI_IE_EID_DIRECT, eid);
+ else
+ dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
+ break;
+ }
+ p = p->next;
+ }
+ if (!p)
+ dundi_ie_append_eid(ied, DUNDI_IE_EID, eid);
+ ast_mutex_unlock(&peerlock);
+}
+
+static int dundi_discover(struct dundi_transaction *trans)
+{
+ struct dundi_ie_data ied;
+ int x;
+ if (!trans->parent) {
+ ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n");
+ return -1;
+ }
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
+ if (!dundi_eid_zero(&trans->us_eid))
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID_DIRECT, &trans->us_eid);
+ for (x=0;x<trans->eidcount;x++)
+ dundi_ie_append_eid_appropriately(&ied, trans->parent->dcontext, &trans->eids[x], &trans->us_eid);
+ dundi_ie_append_str(&ied, DUNDI_IE_CALLED_NUMBER, trans->parent->number);
+ dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
+ dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
+ if (trans->autokilltimeout)
+ trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
+ return dundi_send(trans, DUNDI_COMMAND_DPDISCOVER, 0, 0, &ied);
+}
+
+static int dundi_query(struct dundi_transaction *trans)
+{
+ struct dundi_ie_data ied;
+ int x;
+ if (!trans->parent) {
+ ast_log(LOG_WARNING, "Tried to query a transaction with no parent?!?\n");
+ return -1;
+ }
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
+ if (!dundi_eid_zero(&trans->us_eid))
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->us_eid);
+ for (x=0;x<trans->eidcount;x++)
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]);
+ dundi_ie_append_eid(&ied, DUNDI_IE_REQEID, &trans->parent->query_eid);
+ dundi_ie_append_str(&ied, DUNDI_IE_CALLED_CONTEXT, trans->parent->dcontext);
+ dundi_ie_append_short(&ied, DUNDI_IE_TTL, trans->ttl);
+ if (trans->autokilltimeout)
+ trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans);
+ return dundi_send(trans, DUNDI_COMMAND_EIDQUERY, 0, 0, &ied);
+}
+
+static int discover_transactions(struct dundi_request *dr)
+{
+ struct dundi_transaction *trans;
+ ast_mutex_lock(&peerlock);
+ trans = dr->trans;
+ while(trans) {
+ dundi_discover(trans);
+ trans = trans->next;
+ }
+ ast_mutex_unlock(&peerlock);
+ return 0;
+}
+
+static int query_transactions(struct dundi_request *dr)
+{
+ struct dundi_transaction *trans;
+ ast_mutex_lock(&peerlock);
+ trans = dr->trans;
+ while(trans) {
+ dundi_query(trans);
+ trans = trans->next;
+ }
+ ast_mutex_unlock(&peerlock);
+ return 0;
+}
+
+static int optimize_transactions(struct dundi_request *dr, int order)
+{
+ /* Minimize the message propagation through DUNDi by
+ alerting the network to hops which should be not be considered */
+ struct dundi_transaction *trans;
+ struct dundi_peer *peer;
+ dundi_eid tmp;
+ int x;
+ int needpush;
+ ast_mutex_lock(&peerlock);
+ trans = dr->trans;
+ while(trans) {
+ /* Pop off the true root */
+ if (trans->eidcount) {
+ tmp = trans->eids[--trans->eidcount];
+ needpush = 1;
+ } else {
+ tmp = trans->us_eid;
+ needpush = 0;
+ }
+
+ peer = peers;
+ while(peer) {
+ if (has_permission(peer->include, dr->dcontext) &&
+ dundi_eid_cmp(&peer->eid, &trans->them_eid) &&
+ (peer->order <= order)) {
+ /* For each other transaction, make sure we don't
+ ask this EID about the others if they're not
+ already in the list */
+ if (!dundi_eid_cmp(&tmp, &peer->eid))
+ x = -1;
+ else {
+ for (x=0;x<trans->eidcount;x++) {
+ if (!dundi_eid_cmp(&trans->eids[x], &peer->eid))
+ break;
+ }
+ }
+ if (x == trans->eidcount) {
+ /* Nope not in the list, if needed, add us at the end since we're the source */
+ if (trans->eidcount < DUNDI_MAX_STACK - needpush) {
+ trans->eids[trans->eidcount++] = peer->eid;
+ /* Need to insert the real root (or us) at the bottom now as
+ a requirement now. */
+ needpush = 1;
+ }
+ }
+ }
+ peer = peer->next;
+ }
+ /* If necessary, push the true root back on the end */
+ if (needpush)
+ trans->eids[trans->eidcount++] = tmp;
+ trans = trans->next;
+ }
+ ast_mutex_unlock(&peerlock);
+ return 0;
+}
+
+static int append_transaction(struct dundi_request *dr, struct dundi_peer *p, int ttl, dundi_eid *avoid[])
+{
+ struct dundi_transaction *trans;
+ int x;
+ char eid_str[20];
+ char eid_str2[20];
+ /* Ignore if not registered */
+ if (!p->addr.sin_addr.s_addr)
+ return 0;
+ if (p->maxms && ((p->lastms < 0) || (p->lastms >= p->maxms)))
+ return 0;
+ if (ast_strlen_zero(dr->number))
+ 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);
+ else
+ 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);
+ trans = create_transaction(p);
+ if (!trans)
+ return -1;
+ trans->next = dr->trans;
+ trans->parent = dr;
+ trans->ttl = ttl;
+ for (x=0;avoid[x] && (x <DUNDI_MAX_STACK);x++)
+ trans->eids[x] = *avoid[x];
+ trans->eidcount = x;
+ dr->trans = trans;
+ return 0;
+}
+
+static void cancel_request(struct dundi_request *dr)
+{
+ struct dundi_transaction *trans, *next;
+
+ ast_mutex_lock(&peerlock);
+ trans = dr->trans;
+
+ while(trans) {
+ next = trans->next;
+ /* Orphan transaction from request */
+ trans->parent = NULL;
+ trans->next = NULL;
+ /* Send final cancel */
+ dundi_send(trans, DUNDI_COMMAND_CANCEL, 0, 1, NULL);
+ trans = next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static void abort_request(struct dundi_request *dr)
+{
+ ast_mutex_lock(&peerlock);
+ while(dr->trans)
+ destroy_trans(dr->trans, 0);
+ ast_mutex_unlock(&peerlock);
+}
+
+static void build_transactions(struct dundi_request *dr, int ttl, int order, int *foundcache, int *skipped, int blockempty, dundi_eid *avoid[], int directs[])
+{
+ struct dundi_peer *p;
+ int x;
+ int res;
+ char eid_str[20];
+ ast_mutex_lock(&peerlock);
+ p = peers;
+ while(p) {
+ if (has_permission(p->include, dr->dcontext)) {
+ if (p->order <= order) {
+ /* Check order first, then check cache, regardless of
+ omissions, this gets us more likely to not have an
+ affected answer. */
+ if (!(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration))) {
+ /* Make sure we haven't already seen it and that it won't
+ affect our answer */
+ for (x=0;avoid[x];x++) {
+ if (!dundi_eid_cmp(avoid[x], &p->eid) || !dundi_eid_cmp(avoid[x], &p->us_eid)) {
+ /* If not a direct connection, it affects our answer */
+ if (directs && !directs[x])
+ dr->hmd->flags &= ~DUNDI_HINT_UNAFFECTED;
+ break;
+ }
+ }
+ /* Make sure we can ask */
+ if (!avoid[x] && (!blockempty || !dundi_eid_zero(&p->us_eid))) {
+ /* Check for a matching or 0 cache entry */
+ append_transaction(dr, p, ttl, avoid);
+ } else
+ ast_log(LOG_DEBUG, "Avoiding '%s' in transaction\n", dundi_eid_to_str(eid_str, sizeof(eid_str), avoid[x]));
+ }
+ *foundcache |= res;
+ } else if (!*skipped || (p->order < *skipped))
+ *skipped = p->order;
+ }
+ p = p->next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static int register_request(struct dundi_request *dr, struct dundi_request **pending)
+{
+ struct dundi_request *cur;
+ int res=0;
+ char eid_str[20];
+ ast_mutex_lock(&peerlock);
+ cur = requests;
+ while(cur) {
+ if (option_debug)
+ ast_log(LOG_DEBUG, "Checking '%s@%s' vs '%s@%s'\n", cur->dcontext, cur->number,
+ dr->dcontext, dr->number);
+ if (!strcasecmp(cur->dcontext, dr->dcontext) &&
+ !strcasecmp(cur->number, dr->number) &&
+ (!dundi_eid_cmp(&cur->root_eid, &dr->root_eid) || (cur->crc32 == dr->crc32))) {
+ ast_log(LOG_DEBUG, "Found existing query for '%s@%s' for '%s' crc '%08lx'\n",
+ cur->dcontext, cur->number, dundi_eid_to_str(eid_str, sizeof(eid_str), &cur->root_eid), cur->crc32);
+ *pending = cur;
+ res = 1;
+ break;
+ }
+ cur = cur->next;
+ }
+ if (!res) {
+ ast_log(LOG_DEBUG, "Registering request for '%s@%s' on behalf of '%s' crc '%08lx'\n",
+ dr->number, dr->dcontext, dundi_eid_to_str(eid_str, sizeof(eid_str), &dr->root_eid), dr->crc32);
+ /* Go ahead and link us in since nobody else is searching for this */
+ dr->next = requests;
+ requests = dr;
+ *pending = NULL;
+ }
+ ast_mutex_unlock(&peerlock);
+ return res;
+}
+
+static void unregister_request(struct dundi_request *dr)
+{
+ struct dundi_request *cur, *prev;
+ ast_mutex_lock(&peerlock);
+ prev = NULL;
+ cur = requests;
+ while(cur) {
+ if (cur == dr) {
+ if (prev)
+ prev->next = cur->next;
+ else
+ requests = cur->next;
+ break;
+ }
+ prev = cur;
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static int check_request(struct dundi_request *dr)
+{
+ struct dundi_request *cur;
+ int res = 0;
+ ast_mutex_lock(&peerlock);
+ cur = requests;
+ while(cur) {
+ if (cur == dr) {
+ res = 1;
+ break;
+ }
+ cur = cur->next;
+ }
+ ast_mutex_unlock(&peerlock);
+ return res;
+}
+
+static unsigned long avoid_crc32(dundi_eid *avoid[])
+{
+ /* Idea is that we're calculating a checksum which is independent of
+ the order that the EID's are listed in */
+ unsigned long acrc32 = 0;
+ int x;
+ for (x=0;avoid[x];x++) {
+ /* Order doesn't matter */
+ if (avoid[x+1]) {
+ acrc32 ^= crc32(0L, (unsigned char *)avoid[x], sizeof(dundi_eid));
+ }
+ }
+ return acrc32;
+}
+
+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, dundi_eid *avoid[], int direct[])
+{
+ int res;
+ struct dundi_request dr, *pending;
+ dundi_eid *rooteid=NULL;
+ int x;
+ int ttlms;
+ int foundcache;
+ int skipped=0;
+ int order=0;
+ char eid_str[20];
+ struct timeval start;
+
+ /* Don't do anthing for a hungup channel */
+ if (chan && chan->_softhangup)
+ return 0;
+
+ ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
+
+ for (x=0;avoid[x];x++)
+ rooteid = avoid[x];
+ /* Now perform real check */
+ memset(&dr, 0, sizeof(dr));
+ dr.dr = result;
+ dr.hmd = hmd;
+ dr.maxcount = maxret;
+ dr.expiration = *expiration;
+ dr.crc32 = avoid_crc32(avoid);
+ strncpy(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext) - 1);
+ strncpy(dr.number, number, sizeof(dr.number) - 1);
+ if (rooteid)
+ dr.root_eid = *rooteid;
+ res = register_request(&dr, &pending);
+ if (res) {
+ /* Already a request */
+ if (rooteid && !dundi_eid_cmp(&dr.root_eid, &pending->root_eid)) {
+ /* This is on behalf of someone else. Go ahead and close this out since
+ they'll get their answer anyway. */
+ ast_log(LOG_DEBUG, "Oooh, duplicate request for '%s@%s' for '%s'\n",
+ dr.number,dr.dcontext,dundi_eid_to_str(eid_str, sizeof(eid_str), &dr.root_eid));
+ return -2;
+ } else {
+ /* Wait for the cache to populate */
+ ast_log(LOG_DEBUG, "Waiting for similar request for '%s@%s' for '%s'\n",
+ dr.number,dr.dcontext,dundi_eid_to_str(eid_str, sizeof(eid_str), &pending->root_eid));
+ gettimeofday(&start, NULL);
+ while(check_request(pending) && (calc_ms(&start) < ttlms) && (!chan || !chan->_softhangup))
+ usleep(1);
+ /* Continue on as normal, our cache should kick in */
+ }
+ }
+ /* Create transactions */
+ do {
+ order = skipped;
+ skipped = 0;
+ foundcache = 0;
+ build_transactions(&dr, ttl, order, &foundcache, &skipped, blockempty, avoid, direct);
+ } while (skipped && !foundcache && !dr.trans);
+ /* If no TTL, abort and return 0 now after setting TTL expired hint. Couldn't
+ do this earlier because we didn't know if we were going to have transactions
+ or not. */
+ if (!ttl) {
+ hmd->flags |= DUNDI_HINT_TTL_EXPIRED;
+ abort_request(&dr);
+ unregister_request(&dr);
+ return 0;
+ }
+
+ /* Optimize transactions */
+ optimize_transactions(&dr, order);
+ /* Actually perform transactions */
+ discover_transactions(&dr);
+ /* Wait for transaction to come back */
+ gettimeofday(&start, NULL);
+ while(dr.trans && (calc_ms(&start) < ttlms) && (!chan || !chan->_softhangup))
+ usleep(1);
+ if (chan && chan->_softhangup)
+ ast_log(LOG_DEBUG, "Hrm, '%s' hungup before their query for %s@%s finished\n", chan->name, dr.number, dr.dcontext);
+ cancel_request(&dr);
+ unregister_request(&dr);
+ res = dr.respcount;
+ *expiration = dr.expiration;
+ return res;
+}
+
+int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number)
+{
+ struct dundi_hint_metadata hmd;
+ dundi_eid *avoid[1] = { NULL, };
+ int direct[1] = { 0, };
+ int expiration = DUNDI_DEFAULT_CACHE_TIME;
+ memset(&hmd, 0, sizeof(hmd));
+ hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED;
+ return dundi_lookup_internal(result, maxret, chan, dcontext, number, dundi_ttl, 0, &hmd, &expiration, avoid, direct);
+}
+
+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[])
+{
+ int res;
+ struct dundi_request dr;
+ dundi_eid *rooteid=NULL;
+ int x;
+ int ttlms;
+ int skipped=0;
+ int foundcache=0;
+ struct timeval start;
+
+ ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME;
+
+ for (x=0;avoid[x];x++)
+ rooteid = avoid[x];
+ /* Now perform real check */
+ memset(&dr, 0, sizeof(dr));
+ dr.hmd = hmd;
+ dr.dei = dei;
+ strncpy(dr.dcontext, dcontext ? dcontext : "e164", sizeof(dr.dcontext) - 1);
+ memcpy(&dr.query_eid, eid, sizeof(dr.query_eid));
+ if (rooteid)
+ dr.root_eid = *rooteid;
+ /* Create transactions */
+ build_transactions(&dr, ttl, 9999, &foundcache, &skipped, blockempty, avoid, NULL);
+
+ /* If no TTL, abort and return 0 now after setting TTL expired hint. Couldn't
+ do this earlier because we didn't know if we were going to have transactions
+ or not. */
+ if (!ttl) {
+ hmd->flags |= DUNDI_HINT_TTL_EXPIRED;
+ return 0;
+ }
+
+ /* Optimize transactions */
+ optimize_transactions(&dr, 9999);
+ /* Actually perform transactions */
+ query_transactions(&dr);
+ /* Wait for transaction to come back */
+ gettimeofday(&start, NULL);
+ while(dr.trans && (calc_ms(&start) < ttlms))
+ usleep(1);
+ res = dr.respcount;
+ return res;
+}
+
+int dundi_query_eid(struct dundi_entity_info *dei, const char *dcontext, dundi_eid eid)
+{
+ dundi_eid *avoid[1] = { NULL, };
+ struct dundi_hint_metadata hmd;
+ memset(&hmd, 0, sizeof(hmd));
+ return dundi_query_eid_internal(dei, dcontext, &eid, &hmd, dundi_ttl, 0, avoid);
+}
+
+static int dundi_lookup_exec(struct ast_channel *chan, void *data)
+{
+ char *tmp;
+ char *context;
+ char *opts;
+ int res = -1;
+ struct localuser *u;
+
+ if (!data || !strlen(data)) {
+ ast_log(LOG_WARNING, "DUNDiLookup requires an argument (number)\n");
+ return 0;
+ }
+ LOCAL_USER_ADD(u);
+ tmp = ast_strdupa(data);
+ if (tmp) {
+ context = strchr(tmp, '|');
+ if (context) {
+ *context = '\0';
+ context++;
+ opts = strchr(context, '|');
+ if (opts) {
+ *opts = '\0';
+ opts++;
+ }
+ } else
+ opts = NULL;
+ if (!context || !strlen(context))
+ context = "e164";
+ if (!opts)
+ opts = "";
+
+ }
+ LOCAL_USER_REMOVE(u);
+ return res;
+}
+
+
+static void mark_peers(void)
+{
+ struct dundi_peer *peer;
+ ast_mutex_lock(&peerlock);
+ peer = peers;
+ while(peer) {
+ peer->dead = 1;
+ peer = peer->next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static void mark_mappings(void)
+{
+ struct dundi_mapping *map;
+ ast_mutex_lock(&peerlock);
+ map = mappings;
+ while(map) {
+ map->dead = 1;
+ map = map->next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static void destroy_permissions(struct permission *p)
+{
+ struct permission *prev;
+ while(p) {
+ prev = p;
+ p = p->next;
+ free(prev);
+ }
+}
+
+static void destroy_peer(struct dundi_peer *peer)
+{
+ if (peer->registerid > -1)
+ ast_sched_del(sched, peer->registerid);
+ if (peer->regtrans)
+ destroy_trans(peer->regtrans, 0);
+ if (peer->keypending)
+ destroy_trans(peer->keypending, 0);
+ if (peer->qualifyid > -1)
+ ast_sched_del(sched, peer->qualifyid);
+ destroy_permissions(peer->permit);
+ destroy_permissions(peer->include);
+ free(peer);
+}
+
+static void destroy_map(struct dundi_mapping *map)
+{
+ free(map);
+}
+
+static void prune_peers(void)
+{
+ struct dundi_peer *peer, *prev, *next;
+ ast_mutex_lock(&peerlock);
+ peer = peers;
+ prev = NULL;
+ while(peer) {
+ next = peer->next;
+ if (peer->dead) {
+ if (prev)
+ prev->next = peer->next;
+ else
+ peers = peer->next;
+ destroy_peer(peer);
+ } else
+ prev = peer;
+ peer = next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static void prune_mappings(void)
+{
+ struct dundi_mapping *map, *prev, *next;
+ ast_mutex_lock(&peerlock);
+ map = mappings;
+ prev = NULL;
+ while(map) {
+ next = map->next;
+ if (map->dead) {
+ if (prev)
+ prev->next = map->next;
+ else
+ mappings = map->next;
+ destroy_map(map);
+ } else
+ prev = map;
+ map = next;
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static struct permission *append_permission(struct permission *p, char *s, int allow)
+{
+ struct permission *start;
+ start = p;
+ if (p) {
+ while(p->next)
+ p = p->next;
+ }
+ if (p) {
+ p->next = malloc(sizeof(struct permission) + strlen(s) + 1);
+ p = p->next;
+ } else {
+ p = malloc(sizeof(struct permission) + strlen(s) + 1);
+ }
+ if (p) {
+ memset(p, 0, sizeof(struct permission));
+ memcpy(p->name, s, strlen(s) + 1);
+ p->allow = allow;
+ }
+ return start ? start : p;
+}
+
+#define MAX_OPTS 128
+
+static void build_mapping(char *name, char *value)
+{
+ char *t, *fields[MAX_OPTS];
+ struct dundi_mapping *map;
+ int x;
+ int y;
+ t = strdupa(value);
+ if (t) {
+ map = mappings;
+ while(map) {
+ /* Find a double match */
+ if (!strcasecmp(map->dcontext, name) &&
+ (!strncasecmp(map->lcontext, value, strlen(map->lcontext)) &&
+ (!value[strlen(map->lcontext)] ||
+ (value[strlen(map->lcontext)] == ','))))
+ break;
+ map = map->next;
+ }
+ if (!map) {
+ map = malloc(sizeof(struct dundi_mapping));
+ if (map) {
+ memset(map, 0, sizeof(struct dundi_mapping));
+ map->next = mappings;
+ mappings = map;
+ map->dead = 1;
+ }
+ }
+ if (map) {
+ map->options = 0;
+ memset(fields, 0, sizeof(fields));
+ x = 0;
+ while(t && x < MAX_OPTS) {
+ fields[x++] = t;
+ t = strchr(t, ',');
+ if (t) {
+ *t = '\0';
+ t++;
+ }
+ } /* Russell was here, arrrr! */
+ if ((x == 1) && ast_strlen_zero(fields[0])) {
+ /* Placeholder mapping */
+ strncpy(map->dcontext, name, sizeof(map->dcontext) - 1);
+ map->dead = 0;
+ } else if (x >= 4) {
+ strncpy(map->dcontext, name, sizeof(map->dcontext) - 1);
+ strncpy(map->lcontext, fields[0], sizeof(map->lcontext) - 1);
+ if ((sscanf(fields[1], "%i", &map->weight) == 1) && (map->weight >= 0) && (map->weight < 60000)) {
+ strncpy(map->dest, fields[3], sizeof(map->dest) - 1);
+ if ((map->tech = str2tech(fields[2]))) {
+ map->dead = 0;
+ }
+ } else {
+ ast_log(LOG_WARNING, "Invalid weight '%s' specified, deleting entry '%s/%s'\n", fields[1], map->dcontext, map->lcontext);
+ }
+ for (y=4;y<x;y++) {
+ if (!strcasecmp(fields[y], "nounsolicited"))
+ map->options |= DUNDI_FLAG_NOUNSOLICITED;
+ else if (!strcasecmp(fields[y], "nocomunsolicit"))
+ map->options |= DUNDI_FLAG_NOCOMUNSOLICIT;
+ else if (!strcasecmp(fields[y], "residential"))
+ map->options |= DUNDI_FLAG_RESIDENTIAL;
+ else if (!strcasecmp(fields[y], "commercial"))
+ map->options |= DUNDI_FLAG_COMMERCIAL;
+ else if (!strcasecmp(fields[y], "mobile"))
+ map->options |= DUNDI_FLAG_MOBILE;
+ else if (!strcasecmp(fields[y], "nopartial"))
+ map->options |= DUNDI_FLAG_INTERNAL_NOPARTIAL;
+ else
+ ast_log(LOG_WARNING, "Don't know anything about option '%s'\n", fields[y]);
+ }
+ } else
+ ast_log(LOG_WARNING, "Expected at least %d arguments in map, but got only %d\n", 4, x);
+ }
+ }
+}
+
+static int do_register(void *data)
+{
+ struct dundi_ie_data ied;
+ struct dundi_peer *peer = data;
+ char eid_str[20];
+ char eid_str2[20];
+ /* Called with peerlock already held */
+ 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));
+ peer->registerid = ast_sched_add(sched, default_expiration * 1000, do_register, data);
+ /* Destroy old transaction if there is one */
+ if (peer->regtrans)
+ destroy_trans(peer->regtrans, 0);
+ peer->regtrans = create_transaction(peer);
+ if (peer->regtrans) {
+ peer->regtrans->flags |= FLAG_ISREG;
+ memset(&ied, 0, sizeof(ied));
+ dundi_ie_append_short(&ied, DUNDI_IE_VERSION, DUNDI_DEFAULT_VERSION);
+ dundi_ie_append_eid(&ied, DUNDI_IE_EID, &peer->regtrans->us_eid);
+ dundi_ie_append_short(&ied, DUNDI_IE_EXPIRATION, default_expiration);
+ dundi_send(peer->regtrans, DUNDI_COMMAND_REGREQ, 0, 0, &ied);
+
+ } else
+ 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));
+
+ return 0;
+}
+
+static int do_qualify(void *data)
+{
+ struct dundi_peer *peer;
+ peer = data;
+ peer->qualifyid = -1;
+ qualify_peer(peer, 0);
+ return 0;
+}
+
+static void qualify_peer(struct dundi_peer *peer, int schedonly)
+{
+ int when;
+ if (peer->qualifyid > -1)
+ ast_sched_del(sched, peer->qualifyid);
+ peer->qualifyid = -1;
+ if (peer->qualtrans)
+ destroy_trans(peer->qualtrans, 0);
+ peer->qualtrans = NULL;
+ if (peer->maxms > 0) {
+ when = 60000;
+ if (peer->lastms < 0)
+ when = 10000;
+ if (schedonly)
+ when = 5000;
+ peer->qualifyid = ast_sched_add(sched, when, do_qualify, peer);
+ if (!schedonly)
+ peer->qualtrans = create_transaction(peer);
+ if (peer->qualtrans) {
+ gettimeofday(&peer->qualtx, NULL);
+ peer->qualtrans->flags |= FLAG_ISQUAL;
+ dundi_send(peer->qualtrans, DUNDI_COMMAND_NULL, 0, 1, NULL);
+ }
+ }
+}
+static void populate_addr(struct dundi_peer *peer, dundi_eid *eid)
+{
+ char data[256];
+ char *c;
+ int port, expire;
+ char eid_str[20];
+ dundi_eid_to_str(eid_str, sizeof(eid_str), eid);
+ if (!ast_db_get("dundi/dpeers", eid_str, data, sizeof(data))) {
+ c = strchr(data, ':');
+ if (c) {
+ *c = '\0';
+ c++;
+ if (sscanf(c, "%d:%d", &port, &expire) == 2) {
+ /* Got it! */
+ inet_aton(data, &peer->addr.sin_addr);
+ peer->addr.sin_family = AF_INET;
+ peer->addr.sin_port = htons(port);
+ peer->registerexpire = ast_sched_add(sched, (expire + 10) * 1000, do_register_expire, peer);
+ }
+ }
+ }
+}
+
+
+static void build_peer(dundi_eid *eid, struct ast_variable *v)
+{
+ struct dundi_peer *peer;
+ struct ast_hostent he;
+ struct hostent *hp;
+ dundi_eid testeid;
+ int needregister=0;
+ char eid_str[20];
+
+ ast_mutex_lock(&peerlock);
+ peer = peers;
+ while(peer) {
+ if (!dundi_eid_cmp(&peer->eid, eid)) {
+ break;
+ }
+ peer = peer->next;
+ }
+ if (!peer) {
+ /* Add us into the list */
+ peer = malloc(sizeof(struct dundi_peer));
+ if (peer) {
+ memset(peer, 0, sizeof(struct dundi_peer));
+ peer->registerid = -1;
+ peer->registerexpire = -1;
+ peer->qualifyid = -1;
+ peer->addr.sin_family = AF_INET;
+ peer->addr.sin_port = htons(DUNDI_PORT);
+ populate_addr(peer, eid);
+ peer->next = peers;
+ peers = peer;
+ }
+ }
+ if (peer) {
+ peer->dead = 0;
+ peer->eid = *eid;
+ peer->us_eid = global_eid;
+ destroy_permissions(peer->permit);
+ destroy_permissions(peer->include);
+ peer->permit = NULL;
+ peer->include = NULL;
+ if (peer->registerid > -1)
+ ast_sched_del(sched, peer->registerid);
+ peer->registerid = -1;
+ while(v) {
+ if (!strcasecmp(v->name, "inkey")) {
+ strncpy(peer->inkey, v->value, sizeof(peer->inkey) - 1);
+ } else if (!strcasecmp(v->name, "outkey")) {
+ strncpy(peer->outkey, v->value, sizeof(peer->outkey) - 1);
+ } else if (!strcasecmp(v->name, "host")) {
+ if (!strcasecmp(v->value, "dynamic")) {
+ peer->dynamic = 1;
+ } else {
+ hp = ast_gethostbyname(v->value, &he);
+ if (hp) {
+ memcpy(&peer->addr.sin_addr, hp->h_addr, sizeof(peer->addr.sin_addr));
+ peer->dynamic = 0;
+ } else {
+ ast_log(LOG_WARNING, "Unable to find host '%s' at line %d\n", v->value, v->lineno);
+ peer->dead = 1;
+ }
+ }
+ } else if (!strcasecmp(v->name, "ustothem")) {
+ if (!dundi_str_to_eid(&testeid, v->value))
+ peer->us_eid = testeid;
+ else
+ ast_log(LOG_WARNING, "'%s' is not a valid DUNDi Entity Identifier at line %d\n", v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "include")) {
+ peer->include = append_permission(peer->include, v->value, 1);
+ } else if (!strcasecmp(v->name, "permit")) {
+ peer->permit = append_permission(peer->permit, v->value, 1);
+ } else if (!strcasecmp(v->name, "noinclude")) {
+ peer->include = append_permission(peer->include, v->value, 0);
+ } else if (!strcasecmp(v->name, "deny")) {
+ peer->permit = append_permission(peer->permit, v->value, 0);
+ } else if (!strcasecmp(v->name, "register")) {
+ needregister = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "order")) {
+ if (!strcasecmp(v->value, "primary"))
+ peer->order = 0;
+ else if (!strcasecmp(v->value, "secondary"))
+ peer->order = 1;
+ else if (!strcasecmp(v->value, "tertiary"))
+ peer->order = 2;
+ else if (!strcasecmp(v->value, "quartiary"))
+ peer->order = 3;
+ else {
+ 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);
+ }
+ } else if (!strcasecmp(v->name, "qualify")) {
+ if (!strcasecmp(v->value, "no")) {
+ peer->maxms = 0;
+ } else if (!strcasecmp(v->value, "yes")) {
+ peer->maxms = DEFAULT_MAXMS;
+ } else if (sscanf(v->value, "%d", &peer->maxms) != 1) {
+ ast_log(LOG_WARNING, "Qualification of peer '%s' should be 'yes', 'no', or a number of milliseconds at line %d of dundi.conf\n",
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid), v->lineno);
+ peer->maxms = 0;
+ }
+ } else if (!strcasecmp(v->name, "model")) {
+ if (!strcasecmp(v->value, "inbound"))
+ peer->model = DUNDI_MODEL_INBOUND;
+ else if (!strcasecmp(v->value, "outbound"))
+ peer->model = DUNDI_MODEL_OUTBOUND;
+ else if (!strcasecmp(v->value, "symmetric"))
+ peer->model = DUNDI_MODEL_SYMMETRIC;
+ else {
+ ast_log(LOG_WARNING, "Unknown model '%s', should be 'outbound', 'inbound', or 'symmetric' at line %d\n",
+ v->value, v->lineno);
+ }
+ }
+ v = v->next;
+ }
+ if (!peer->model) {
+ ast_log(LOG_WARNING, "Peer '%s' lacks a model, discarding!\n",
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ peer->dead = 1;
+ } else if (peer->include && !(peer->model & DUNDI_MODEL_OUTBOUND)) {
+ ast_log(LOG_WARNING, "Peer '%s' is supposed to be included in outbound searches but isn't an outbound peer!\n",
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ } else if (peer->permit && !(peer->model & DUNDI_MODEL_INBOUND)) {
+ ast_log(LOG_WARNING, "Peer '%s' is supposed to have permission for some inbound searches but isn't an inbound peer!\n",
+ dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid));
+ } else {
+ if (needregister) {
+ peer->registerid = ast_sched_add(sched, 2000, do_register, peer);
+ }
+ qualify_peer(peer, 1);
+ }
+ }
+ ast_mutex_unlock(&peerlock);
+}
+
+static int dundi_helper(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *data, int flag)
+{
+ struct dundi_result results[MAX_RESULTS];
+ int res;
+ int x;
+ int found = 0;
+ if (!strncasecmp(context, "macro-", 6)) {
+ if (!chan) {
+ ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
+ return -1;
+ }
+ /* If done as a macro, use macro extension */
+ if (!strcasecmp(exten, "s")) {
+ exten = pbx_builtin_getvar_helper(chan, "ARG1");
+ if (!exten || ast_strlen_zero(exten))
+ exten = chan->macroexten;
+ if (!exten || ast_strlen_zero(exten))
+ exten = chan->exten;
+ if (!exten || ast_strlen_zero(exten)) {
+ ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
+ return -1;
+ }
+ }
+ if (!data || ast_strlen_zero(data))
+ data = "e164";
+ } else {
+ if (!data || ast_strlen_zero(data))
+ data = context;
+ }
+ res = dundi_lookup(results, MAX_RESULTS, chan, data, exten);
+ for (x=0;x<res;x++) {
+ if (results[x].flags & flag)
+ found++;
+ }
+ if (found >= priority)
+ return 1;
+ return 0;
+}
+
+static int dundi_exists(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_EXISTS);
+}
+
+static int dundi_canmatch(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_CANMATCH);
+}
+
+static int dundi_exec(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, int newstack, const char *data)
+{
+ struct dundi_result results[MAX_RESULTS];
+ int res;
+ int x=0;
+ char req[1024];
+ struct ast_app *dial;
+
+ if (!strncasecmp(context, "macro-", 6)) {
+ if (!chan) {
+ ast_log(LOG_NOTICE, "Can't use macro mode without a channel!\n");
+ return -1;
+ }
+ /* If done as a macro, use macro extension */
+ if (!strcasecmp(exten, "s")) {
+ exten = pbx_builtin_getvar_helper(chan, "ARG1");
+ if (!exten || ast_strlen_zero(exten))
+ exten = chan->macroexten;
+ if (!exten || ast_strlen_zero(exten))
+ exten = chan->exten;
+ if (!exten || ast_strlen_zero(exten)) {
+ ast_log(LOG_WARNING, "Called in Macro mode with no ARG1 or MACRO_EXTEN?\n");
+ return -1;
+ }
+ }
+ if (!data || ast_strlen_zero(data))
+ data = "e164";
+ } else {
+ if (!data || ast_strlen_zero(data))
+ data = context;
+ }
+ res = dundi_lookup(results, MAX_RESULTS, chan, data, exten);
+ if (res > 0) {
+ sort_results(results, res);
+ for (x=0;x<res;x++) {
+ if (results[x].flags & DUNDI_FLAG_EXISTS) {
+ if (!--priority)
+ break;
+ }
+ }
+ }
+ if (x < res) {
+ /* Got a hit! */
+ snprintf(req, sizeof(req), "%s/%s", results[x].tech, results[x].dest);
+ dial = pbx_findapp("Dial");
+ if (dial)
+ res = pbx_exec(chan, dial, req, newstack);
+ } else
+ res = -1;
+ return res;
+}
+
+static int dundi_matchmore(struct ast_channel *chan, const char *context, const char *exten, int priority, const char *callerid, const char *data)
+{
+ return dundi_helper(chan, context, exten, priority, data, DUNDI_FLAG_MATCHMORE);
+}
+
+static struct ast_switch dundi_switch =
+{
+ name: "DUNDi",
+ description: "DUNDi Discovered Dialplan Switch",
+ exists: dundi_exists,
+ canmatch: dundi_canmatch,
+ exec: dundi_exec,
+ matchmore: dundi_matchmore,
+};
+
+static int set_config(char *config_file, struct sockaddr_in* sin)
+{
+ struct ast_config *cfg;
+ struct ast_variable *v;
+ char *cat;
+ int format;
+ int x;
+ char hn[256];
+ struct ast_hostent he;
+ struct hostent *hp;
+ struct sockaddr_in sin2;
+ static int last_port = 0;
+ dundi_eid testeid;
+
+ dundi_ttl = DUNDI_DEFAULT_TTL;
+ cfg = ast_load(config_file);
+
+
+ if (!cfg) {
+ ast_log(LOG_ERROR, "Unable to load config %s\n", config_file);
+ return -1;
+ }
+ ipaddr[0] = '\0';
+ if (!gethostname(hn, sizeof(hn))) {
+ hp = ast_gethostbyname(hn, &he);
+ if (hp) {
+ memcpy(&sin2.sin_addr, hp->h_addr, sizeof(sin2.sin_addr));
+ ast_inet_ntoa(ipaddr, sizeof(ipaddr), sin2.sin_addr);
+ } else
+ ast_log(LOG_WARNING, "Unable to look up host '%s'\n", hn);
+ } else
+ ast_log(LOG_WARNING, "Unable to get host name!\n");
+ ast_mutex_lock(&peerlock);
+ reset_global_eid();
+ strncpy(secretpath, "dundi", sizeof(secretpath) - 1);
+ v = ast_variable_browse(cfg, "general");
+ while(v) {
+ if (!strcasecmp(v->name, "port")){
+ sin->sin_port = ntohs(atoi(v->value));
+ if(last_port==0){
+ last_port=sin->sin_port;
+ } else if(sin->sin_port != last_port)
+ ast_log(LOG_WARNING, "change to port ignored until next asterisk re-start\n");
+ } else if (!strcasecmp(v->name, "bindaddr")) {
+ struct hostent *hp;
+ struct ast_hostent he;
+ hp = ast_gethostbyname(v->value, &he);
+ if (hp) {
+ memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr));
+ } else
+ ast_log(LOG_WARNING, "Invalid host/IP '%s'\n", v->value);
+ } else if (!strcasecmp(v->name, "authdebug")) {
+ authdebug = ast_true(v->value);
+ } else if (!strcasecmp(v->name, "ttl")) {
+ if ((sscanf(v->value, "%i", &x) == 1) && (x > 0) && (x < DUNDI_DEFAULT_TTL)) {
+ dundi_ttl = x;
+ } else {
+ ast_log(LOG_WARNING, "'%s' is not a valid TTL at line %d, must be number from 1 to %d\n",
+ v->value, v->lineno, DUNDI_DEFAULT_TTL);
+ }
+ } else if (!strcasecmp(v->name, "autokill")) {
+ if (sscanf(v->value, "%i", &x) == 1) {
+ if (x >= 0)
+ global_autokilltimeout = x;
+ else
+ ast_log(LOG_NOTICE, "Nice try, but autokill has to be >0 or 'yes' or 'no' at line %d\n", v->lineno);
+ } else if (ast_true(v->value)) {
+ global_autokilltimeout = DEFAULT_MAXMS;
+ } else {
+ global_autokilltimeout = 0;
+ }
+ } else if (!strcasecmp(v->name, "entityid")) {
+ if (!dundi_str_to_eid(&testeid, v->value))
+ global_eid = testeid;
+ else
+ ast_log(LOG_WARNING, "Invalid global endpoint identifier '%s' at line %d\n", v->value, v->lineno);
+ } else if (!strcasecmp(v->name, "tos")) {
+ if (sscanf(v->value, "%i", &format) == 1)
+ tos = format & 0xff;
+ else if (!strcasecmp(v->value, "lowdelay"))
+ tos = IPTOS_LOWDELAY;
+ else if (!strcasecmp(v->value, "throughput"))
+ tos = IPTOS_THROUGHPUT;
+ else if (!strcasecmp(v->value, "reliability"))
+ tos = IPTOS_RELIABILITY;
+ else if (!strcasecmp(v->value, "mincost"))
+ tos = IPTOS_MINCOST;
+ else if (!strcasecmp(v->value, "none"))
+ tos = 0;
+ else
+ ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\n", v->lineno);
+ } else if (!strcasecmp(v->name, "department")) {
+ strncpy(dept, v->value, sizeof(dept) - 1);
+ } else if (!strcasecmp(v->name, "organization")) {
+ strncpy(org, v->value, sizeof(org) - 1);
+ } else if (!strcasecmp(v->name, "locality")) {
+ strncpy(locality, v->value, sizeof(locality) - 1);
+ } else if (!strcasecmp(v->name, "stateprov")) {
+ strncpy(stateprov, v->value, sizeof(stateprov) - 1);
+ } else if (!strcasecmp(v->name, "country")) {
+ strncpy(country, v->value, sizeof(country) - 1);
+ } else if (!strcasecmp(v->name, "email")) {
+ strncpy(email, v->value, sizeof(email) - 1);
+ } else if (!strcasecmp(v->name, "phone")) {
+ strncpy(phone, v->value, sizeof(phone) - 1);
+ }
+ v = v->next;
+ }
+ ast_mutex_unlock(&peerlock);
+ mark_mappings();
+ v = ast_variable_browse(cfg, "mappings");
+ while(v) {
+ build_mapping(v->name, v->value);
+ v = v->next;
+ }
+ prune_mappings();
+ mark_peers();
+ cat = ast_category_browse(cfg, NULL);
+ while(cat) {
+ if (strcasecmp(cat, "general") && strcasecmp(cat, "mappings")) {
+ /* Entries */
+ if (!dundi_str_to_eid(&testeid, cat))
+ build_peer(&testeid, ast_variable_browse(cfg, cat));
+ else
+ ast_log(LOG_NOTICE, "Ignoring invalid EID entry '%s'\n", cat);
+ }
+ cat = ast_category_browse(cfg, cat);
+ }
+ prune_peers();
+ ast_destroy(cfg);
+ load_password();
+ return 0;
+}
+
+int unload_module(void)
+{
+ int res;
+ STANDARD_HANGUP_LOCALUSERS;
+ ast_cli_unregister(&cli_debug);
+ ast_cli_unregister(&cli_flush);
+ ast_cli_unregister(&cli_no_debug);
+ ast_cli_unregister(&cli_show_peers);
+ ast_cli_unregister(&cli_show_entityid);
+ ast_cli_unregister(&cli_show_trans);
+ ast_cli_unregister(&cli_show_requests);
+ ast_cli_unregister(&cli_show_mappings);
+ ast_cli_unregister(&cli_show_peer);
+ ast_cli_unregister(&cli_lookup);
+ ast_cli_unregister(&cli_queryeid);
+ ast_unregister_switch(&dundi_switch);
+ res = ast_unregister_application(app);
+ return res;
+}
+
+int reload(void)
+{
+ struct sockaddr_in sin;
+ set_config("dundi.conf",&sin);
+ return 0;
+}
+
+int load_module(void)
+{
+ int res=0;
+ struct sockaddr_in sin;
+ char iabuf[INET_ADDRSTRLEN];
+
+ dundi_set_output(dundi_debug_output);
+ dundi_set_error(dundi_error_output);
+
+ /* Seed random number generator */
+ srand(time(NULL));
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = ntohs(DUNDI_PORT);
+ sin.sin_addr.s_addr = INADDR_ANY;
+
+ /* Make a UDP socket */
+ io = io_context_create();
+ sched = sched_context_create();
+
+ if (!io || !sched) {
+ ast_log(LOG_ERROR, "Out of memory\n");
+ return -1;
+ }
+
+ ast_cli_register(&cli_debug);
+ ast_cli_register(&cli_flush);
+ ast_cli_register(&cli_no_debug);
+ ast_cli_register(&cli_show_peers);
+ ast_cli_register(&cli_show_entityid);
+ ast_cli_register(&cli_show_trans);
+ ast_cli_register(&cli_show_requests);
+ ast_cli_register(&cli_show_mappings);
+ ast_cli_register(&cli_show_peer);
+ ast_cli_register(&cli_lookup);
+ ast_cli_register(&cli_queryeid);
+ if (ast_register_switch(&dundi_switch))
+ ast_log(LOG_ERROR, "Unable to register DUNDi switch\n");
+
+ set_config("dundi.conf",&sin);
+
+ netsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+
+ if (netsocket < 0) {
+ ast_log(LOG_ERROR, "Unable to create network socket: %s\n", strerror(errno));
+ return -1;
+ }
+ if (bind(netsocket,(struct sockaddr *)&sin, sizeof(sin))) {
+ ast_log(LOG_ERROR, "Unable to bind to %s port %d: %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr), ntohs(sin.sin_port), strerror(errno));
+ return -1;
+ }
+
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "Using TOS bits %d\n", tos);
+
+ if (setsockopt(netsocket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)))
+ ast_log(LOG_WARNING, "Unable to set TOS to %d\n", tos);
+
+ if (!res) {
+ res = start_network_thread();
+ if (option_verbose > 1)
+ ast_verbose(VERBOSE_PREFIX_2 "DUNDi Ready and Listening on %s port %d\n", ast_inet_ntoa(iabuf, sizeof(iabuf), sin.sin_addr), ntohs(sin.sin_port));
+ } else {
+ ast_log(LOG_ERROR, "Unable to start network thread\n");
+ close(netsocket);
+ }
+ res = ast_register_application(app, dundi_lookup_exec, synopsis, descrip);
+ return 0;
+}
+
+char *description(void)
+{
+ return tdesc;
+}
+
+int usecount(void)
+{
+ int res;
+ /* XXX DUNDi cannot be unloaded XXX */
+ return 1;
+ STANDARD_USECOUNT(res);
+ return res;
+}
+
+char *key()
+{
+ return ASTERISK_GPL_KEY;
+}