diff -ruN asterisk-1.0.7-orig/channels/chan_iax2.c asterisk-1.0.7-pbx_dundi/channels/chan_iax2.c --- asterisk-1.0.7-orig/channels/chan_iax2.c 2005-03-18 18:30:05.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/channels/chan_iax2.c 2005-06-02 20:21:37.000000000 +0200 @@ -197,6 +197,7 @@ struct iax2_user { char name[80]; char secret[80]; + char dbsecret[80]; int authmethods; char accountcode[20]; char inkeys[80]; /* Key(s) this user can use to authenticate to us */ @@ -219,6 +220,7 @@ char name[80]; char username[80]; char secret[80]; + char dbsecret[80]; char outkey[80]; /* What key we use to talk to this peer */ char context[AST_MAX_EXTENSION]; /* Default context (for transfer really) */ char regexten[AST_MAX_EXTENSION]; /* Extension to register (if regcontext is used) */ @@ -2194,6 +2196,26 @@ *notransfer=p->notransfer; if (usejitterbuf) *usejitterbuf=p->usejitterbuf; + if (secret) { + if (!ast_strlen_zero(p->dbsecret)) { + char *family, *key=NULL; + family = ast_strdupa(p->dbsecret); + if (family) { + key = strchr(family, '/'); + if (key) { + *key = '\0'; + key++; + } + } + if (!family || !key || ast_db_get(family, key, secret, seclen)) { + ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", p->dbsecret); + if (p->temponly) + free(p); + p = NULL; + } + } else + strncpy(secret, p->secret, seclen); /* safe */ + } } else { if (p->temponly) free(p); @@ -3624,6 +3646,15 @@ return 0; } +static void free_context(struct iax2_context *con) +{ + struct iax2_context *conl; + while(con) { + conl = con; + con = con->next; + free(conl); + } +} static int check_access(int callno, struct sockaddr_in *sin, struct iax_ies *ies) { @@ -3769,6 +3800,28 @@ strncpy(iaxs[callno]->language, user->language, sizeof(iaxs[callno]->language)-1); iaxs[callno]->notransfer = user->notransfer; iaxs[callno]->usejitterbuf = user->usejitterbuf; + /* Keep this check last */ + if (!ast_strlen_zero(user->dbsecret)) { + char *family, *key=NULL; + family = ast_strdupa(user->dbsecret); + if (family) { + key = strchr(family, '/'); + if (key) { + *key = '\0'; + key++; + } + } + if (!family || !key || ast_db_get(family, key, iaxs[callno]->secret, sizeof(iaxs[callno]->secret))) { + ast_log(LOG_WARNING, "Unable to retrieve database password for family/key '%s'!\n", user->dbsecret); + if (user->temponly) { + ast_free_ha(user->ha); + free_context(user->contexts); + free(user); + user = NULL; + } + } + } else + strncpy(iaxs[callno]->secret, user->secret, sizeof(iaxs[callno]->secret) - 1); res = 0; } iaxs[callno]->trunk = iax2_getpeertrunk(*sin); @@ -3844,15 +3897,23 @@ } else if (p->authmethods & IAX_AUTH_MD5) { struct MD5Context md5; unsigned char digest[16]; - MD5Init(&md5); - MD5Update(&md5, p->challenge, strlen(p->challenge)); - MD5Update(&md5, p->secret, strlen(p->secret)); - MD5Final(digest, &md5); - /* If they support md5, authenticate with it. */ - for (x=0;x<16;x++) - sprintf(requeststr + (x << 1), "%2.2x", digest[x]); /* safe */ - if (!strcasecmp(requeststr, md5secret)) - res = 0; + char *tmppw, *stringp; + + tmppw = ast_strdupa(p->secret); + stringp = tmppw; + while((tmppw = strsep(&stringp, ";"))) { + MD5Init(&md5); + MD5Update(&md5, p->challenge, strlen(p->challenge)); + MD5Update(&md5, tmppw, strlen(tmppw)); + MD5Final(digest, &md5); + /* If they support md5, authenticate with it. */ + for (x=0;x<16;x++) + sprintf(requeststr + (x << 1), "%2.2x", digest[x]); /* safe */ + if (!strcasecmp(requeststr, md5secret)) { + res = 0; + break; + } + } } else if (p->authmethods & IAX_AUTH_PLAINTEXT) { if (!strcmp(secret, p->secret)) res = 0; @@ -6237,16 +6298,6 @@ return 0; } -static void free_context(struct iax2_context *con) -{ - struct iax2_context *conl; - while(con) { - conl = con; - con = con->next; - free(conl); - } -} - static struct ast_channel *iax2_request(char *type, int format, void *data) { int callno; @@ -6469,6 +6520,8 @@ strncpy(peer->secret, v->value, sizeof(peer->secret)-1); else if (!strcasecmp(v->name, "mailbox")) strncpy(peer->mailbox, v->value, sizeof(peer->mailbox) - 1); + else if (!strcasecmp(v->name, "dbsecret")) + strncpy(peer->dbsecret, v->value, sizeof(peer->dbsecret)-1); else if (!strcasecmp(v->name, "mailboxdetail")) peer->messagedetail = ast_true(v->value); else if (!strcasecmp(v->name, "trunk")) { @@ -6665,6 +6718,8 @@ user->notransfer = ast_true(v->value); } else if (!strcasecmp(v->name, "jitterbuffer")) { user->usejitterbuf = ast_true(v->value); + } else if (!strcasecmp(v->name, "dbsecret")) { + strncpy(user->dbsecret, v->value, sizeof(user->dbsecret)-1); } else if (!strcasecmp(v->name, "secret")) { strncpy(user->secret, v->value, sizeof(user->secret)-1); } else if (!strcasecmp(v->name, "callerid")) { diff -ruN asterisk-1.0.7-orig/configs/dundi.conf.sample asterisk-1.0.7-pbx_dundi/configs/dundi.conf.sample --- asterisk-1.0.7-orig/configs/dundi.conf.sample 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/configs/dundi.conf.sample 2005-06-02 20:21:37.000000000 +0200 @@ -0,0 +1,225 @@ +; +; DUNDi configuration file +; +; +[general] +; +; The "general" section contains general parameters relating +; to the operation of the dundi client and server. +; +; The first part should be your complete contact information +; should someone else in your peer group need to contact you. +; +;department=Your Department +;organization=Your Company, Inc. +;locality=Your City +;stateprov=ST +;country=US +;email=your@email.com +;phone=+12565551212 +; +; +; Specify bind address and port number. Default is +; 4520 +; +;bindaddr=0.0.0.0 +;port=4520 +; +; Our entity identifier (Should generally be the MAC address of the +; machine it's running on. Defaults to the first eth address, but you +; can override it here, as long as you set it to the MAC of *something* +; you own!) +; +;entityid=00:07:E9:3B:76:60 +; +; Define the max depth in which to search the DUNDi system (also max # of +; seconds to wait for a reply) +; +ttl=32 +; +; If we don't get ACK to our DPREQUEST within 2000ms, and autokill is set +; to yes, then we cancel the whole thing (that's enough time for one +; retransmission only). This is used to keep things from stalling for a long +; time for a host that is not available, but would be ill advised for bad +; connections. In addition to 'yes' or 'no' you can also specify a number +; of milliseconds. See 'qualify' for individual peers to turn on for just +; a specific peer. +; +autokill=yes +; +; pbx_dundi creates a rotating key called "secret", under the family +; 'secretpath'. The default family is dundi (resulting in +; the key being held at dundi/secret). +; +;secretpath=dundi +; +; The 'storehistory' option (also changeable at runtime with +; 'dundi store history' and 'dundi no store history') will +; cause the DUNDi engine to keep track of the last several +; queries and the amount of time each query took to execute +; for the purpose of tracking slow nodes. This option is +; off by default due to performance impacts. +; +;storehistory=yes + +[mappings] +; +; The "mappings" section maps DUNDi contexts +; to contexts on the local asterisk system. Remember +; that numbers that are made available under the e164 +; DUNDi context are regulated by the DUNDi General Peering +; Agreement (GPA) if you are a member of the DUNDi E.164 +; Peering System. +; +; dundi_context => local_context,weight,tech,dest[,options]] +; +; dundi_context is the name of the context being requested +; within the DUNDi request +; +; local_context is the name of the context on the local system +; in which numbers can be looked up for which responses shall be given. +; +; tech is the technology to use (IAX, SIP, H323) +; +; dest is the destination to supply for reaching that number. The +; following variables can be used in the destination string and will +; be automatically substituted: +; ${NUMBER}: The number being requested +; ${IPADDR}: The IP address to connect to +; ${SECRET}: The current rotating secret key to be used +; +; Further options may include: +; +; nounsolicited: No unsolicited calls of any type permitted via this +; route +; nocomunsolicit: No commercial unsolicited calls permitted via +; this route +; residential: This number is known to be a residence +; commercial: This number is known to be a business +; mobile: This number is known to be a mobile phone +; nocomunsolicit: No commercial unsolicited calls permitted via +; this route +; nopartial: Do not search for partial matches +; +; There *must* exist an entry in mappings for DUNDi to respond +; to any request, although it may be empty. +; +;e164 => dundi-e164-canonical,0,IAX2,dundi:${SECRET}@${IPADDR}/${NUMBER},nounsolicited,nocomunsolicit,nopartial +;e164 => dundi-e164-customers,100,IAX2,dundi:${SECRET}@${IPADDR}/${NUMBER},nounsolicited,nocomunsolicit,nopartial +;e164 => dundi-e164-via-pstn,400,IAX2,dundi:${SECRET}@${IPADDR}/${NUMBER},nounsolicited,nocomunsolicit,nopartial + +;digexten => default,0,IAX2,guest@lappy/${NUMBER} +;asdf => + + +; +; +; The remaining sections represent the peers +; that we fundamentally trust. The section name +; represents the name and optionally at a specific +; DUNDi context if you want the trust to be established +; for only a specific DUNDi context. +; +; inkey - What key they will be authenticating to us with +; +; outkey - What key we use to authenticate to them +; +; host - What their host is +; +; order - What search order to use. May be 'primary', 'secondary', +; 'tertiary' or 'quartiary'. In large systems, it is beneficial +; to only query one up-stream host in order to maximize caching +; value. Adding one with primary and one with secondary gives you +; redundancy without sacraficing performance. +; +; include - Includes this peer when searching a particular context +; for lookup (set "all" to perform all lookups with that +; host. This is also the context in which peers are permitted +; to precache. +; +; noinclude - Disincludes this peer when searching a particular context +; for lookup (set "all" to perform no lookups with that +; host. +; +; permit - Permits this peer to search a given DUNDi context on +; the local system. Set "all" to permit this host to +; lookup all contexts. This is also a context for which +; we will create/forward PRECACHE commands. +; +; deny - Denies this peer to search a given DUNDi context on +; the local system. Set "all" to deny this host to +; lookup all contexts. +; +; model - inbound, outbound, or symmetric for whether we receive +; requests only, transmit requests only, or do both. +; +; precache - Utilize/Permit precaching with this peer (to pre +; cache means to provide an answer when no request +; was made and is used so that machines with few +; routes can push those routes up a to a higher level). +; outgoing means we send precache routes to this peer, +; incoming means we permit this peer to send us +; precache routes. symmetric means we do both. +; +; Note: You cannot mix symmetric/outbound model with symmetric/inbound +; precache, nor can you mix symmetric/inbound model with symmetric/outbound +; precache. +; +; +; The '*' peer is special and matches an unspecified entity +; + +; +; Sample Primary e164 DUNDi peer +; +[00:50:8B:F3:75:BB] +model = symmetric +host = 64.215.96.114 +inkey = digium +outkey = misery +include = e164 +permit = e164 +qualify = yes + +; +; Sample Secondary e164 DUNDi peer +; +;[00:A0:C9:96:92:84] +;model = symmetric +;host = misery.digium.com +;inkey = misery +;outkey = ourkey +;include = e164 +;permit = e164 +;qualify = yes +;order = secondary + +; +; Sample "push mode" downstream host +; +;[00:0C:76:96:75:28] +;model = incoming +;host = dynamic +;precache = incoming +;inkey = littleguy +;outkey = ourkey +;include = e164 ; In this case used only for precaching +;permit = e164 +;qualify = yes + +; +; Sample "push mode" upstream host +; +;[00:07:E9:3B:76:60] +;model = outbound +;precache = outbound +;host = 216.207.245.34 +;register = yes +;inkey = dhcp34 +;permit = all ; In this case used only for precaching +;include = all +;qualify = yes +;outkey=foo + +;[*] +; diff -ruN asterisk-1.0.7-orig/include/asterisk/dundi.h asterisk-1.0.7-pbx_dundi/include/asterisk/dundi.h --- asterisk-1.0.7-orig/include/asterisk/dundi.h 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/include/asterisk/dundi.h 2005-06-02 20:21:37.000000000 +0200 @@ -0,0 +1,212 @@ +/* + * Distributed Universal Number Discovery (DUNDi) + * + * Copyright (C) 2004, Digium Inc. + * + * Written by Mark Spencer + * + * This program is Free Software distributed under the terms of + * of the GNU General Public License. + */ +#ifndef _ASTERISK_DUNDI_H +#define _ASTERISK_DUNDI_H + +#include + +#define DUNDI_PORT 4520 + +/* A DUNDi Entity ID is essentially a MAC address, brief and unique */ +struct _dundi_eid { + unsigned char eid[6]; +} __attribute__ ((__packed__)); + +typedef struct _dundi_eid dundi_eid; + +struct dundi_hdr { + unsigned short strans; /* Source transaction */ + unsigned short dtrans; /* Destination transaction */ + unsigned char iseqno; /* Next expected incoming sequence number */ + unsigned char oseqno; /* Outgoing sequence number */ + unsigned char cmdresp; /* Command / Response */ + unsigned char cmdflags; /* Command / Response specific flags*/ + unsigned char ies[0]; +} __attribute__ ((__packed__)); + +struct dundi_ie_hdr { + unsigned char ie; + unsigned char len; + unsigned char iedata[0]; +} __attribute__ ((__packed__)); + +#define DUNDI_FLAG_RETRANS (1 << 16) /* Applies to dtrans */ +#define DUNDI_FLAG_RESERVED (1 << 16) /* Applies to strans */ + +#define DUNDI_PROTO_NONE 0 /* No answer yet */ +#define DUNDI_PROTO_IAX 1 /* IAX version 2 */ +#define DUNDI_PROTO_SIP 2 /* Session Initiation Protocol */ +#define DUNDI_PROTO_H323 3 /* ITU H.323 */ + +#define DUNDI_FLAG_NONEXISTANT (0) /* Isn't and can't be a valid number */ +#define DUNDI_FLAG_EXISTS (1 << 0) /* Is a valid number */ +#define DUNDI_FLAG_MATCHMORE (1 << 1) /* Might be valid if you add more digits */ +#define DUNDI_FLAG_CANMATCH (1 << 2) /* Might be a match */ +#define DUNDI_FLAG_IGNOREPAT (1 << 3) /* Keep dialtone */ +#define DUNDI_FLAG_RESIDENTIAL (1 << 4) /* Destination known to be residential */ +#define DUNDI_FLAG_COMMERCIAL (1 << 5) /* Destination known to be commercial */ +#define DUNDI_FLAG_MOBILE (1 << 6) /* Destination known to be cellular/mobile */ +#define DUNDI_FLAG_NOUNSOLICITED (1 << 7) /* No unsolicited calls of any kind through this route */ +#define DUNDI_FLAG_NOCOMUNSOLICIT (1 << 8) /* No commercial unsolicited calls through this route */ + +#define DUNDI_HINT_NONE (0) +#define DUNDI_HINT_TTL_EXPIRED (1 << 0) /* TTL Expired */ +#define DUNDI_HINT_DONT_ASK (1 << 1) /* Don't ask for anything beginning with data */ +#define DUNDI_HINT_UNAFFECTED (1 << 2) /* Answer not affected by entity list */ + +struct dundi_encblock { /* AES-128 encrypted block */ + unsigned char iv[16]; /* Initialization vector of random data */ + unsigned char encdata[0]; /* Encrypted / compressed data */ +} __attribute__ ((__packed__)); + +struct dundi_answer { + dundi_eid eid; /* Original source of answer */ + unsigned char protocol; /* Protocol (DUNDI_PROTO_*) */ + unsigned short flags; /* Flags relating to answer */ + unsigned short weight; /* Weight of answers */ + unsigned char data[0]; /* Protocol specific URI */ +} __attribute__ ((__packed__)); + +struct dundi_hint { + unsigned short flags; /* Flags relating to answer */ + unsigned char data[0]; /* For data for hint */ +} __attribute__ ((__packed__)); + +#define DUNDI_CAUSE_SUCCESS 0 /* Success */ +#define DUNDI_CAUSE_GENERAL 1 /* General unspecified failure */ +#define DUNDI_CAUSE_DYNAMIC 2 /* Requested entity is dynamic */ +#define DUNDI_CAUSE_NOAUTH 3 /* No or improper authorization */ +#define DUNDI_CAUSE_DUPLICATE 4 /* Duplicate request */ +#define DUNDI_CAUSE_TTL_EXPIRED 5 /* Expired TTL */ +#define DUNDI_CAUSE_NEEDKEY 6 /* Need new session key to decode */ +#define DUNDI_CAUSE_BADENCRYPT 7 /* Badly encrypted data */ + +struct dundi_cause { + unsigned char causecode; /* Numerical cause (DUNDI_CAUSE_*) */ + char desc[0]; /* Textual description */ +} __attribute__ ((__packed__)); + +struct dundi_peer_status { + unsigned int flags; + unsigned short netlag; + unsigned short querylag; + dundi_eid peereid; +} __attribute__ ((__packed__)); + +#define DUNDI_PEER_PRIMARY (1 << 0) +#define DUNDI_PEER_SECONDARY (1 << 1) +#define DUNDI_PEER_UNAVAILABLE (1 << 2) +#define DUNDI_PEER_REGISTERED (1 << 3) +#define DUNDI_PEER_MOD_OUTBOUND (1 << 4) +#define DUNDI_PEER_MOD_INBOUND (1 << 5) +#define DUNDI_PEER_PCMOD_OUTBOUND (1 << 6) +#define DUNDI_PEER_PCMOD_INBOUND (1 << 7) + +#define DUNDI_COMMAND_FINAL (0x80) /* Or'd with other flags */ + +#define DUNDI_COMMAND_ACK (0 | 0x40) /* Ack a message */ +#define DUNDI_COMMAND_DPDISCOVER 1 /* Request discovery */ +#define DUNDI_COMMAND_DPRESPONSE (2 | 0x40) /* Respond to a discovery request */ +#define DUNDI_COMMAND_EIDQUERY 3 /* Request information for a peer */ +#define DUNDI_COMMAND_EIDRESPONSE (4 | 0x40) /* Response to a peer query */ +#define DUNDI_COMMAND_PRECACHERQ 5 /* Pre-cache Request */ +#define DUNDI_COMMAND_PRECACHERP (6 | 0x40) /* Pre-cache Response */ +#define DUNDI_COMMAND_INVALID (7 | 0x40) /* Invalid dialog state (does not require ack) */ +#define DUNDI_COMMAND_UNKNOWN (8 | 0x40) /* Unknown command */ +#define DUNDI_COMMAND_NULL 9 /* No-op */ +#define DUNDI_COMMAND_REGREQ (10) /* Register Request */ +#define DUNDI_COMMAND_REGRESPONSE (11 | 0x40) /* Register Response */ +#define DUNDI_COMMAND_CANCEL (12) /* Cancel transaction entirely */ +#define DUNDI_COMMAND_ENCRYPT (13) /* Send an encrypted message */ +#define DUNDI_COMMAND_ENCREJ (14 | 0x40) /* Reject an encrypted message */ + +#define DUNDI_COMMAND_STATUS 15 /* Status command */ + +/* + * Remember that some information elements may occur + * more than one time within a message + */ + +#define DUNDI_IE_EID 1 /* Entity identifier (dundi_eid) */ +#define DUNDI_IE_CALLED_CONTEXT 2 /* DUNDi Context (string) */ +#define DUNDI_IE_CALLED_NUMBER 3 /* Number of equivalent (string) */ +#define DUNDI_IE_EID_DIRECT 4 /* Entity identifier (dundi_eid), direct connect */ +#define DUNDI_IE_ANSWER 5 /* An answer (struct dundi_answer) */ +#define DUNDI_IE_TTL 6 /* Max TTL for this request / Remaining TTL for the response (short)*/ +#define DUNDI_IE_VERSION 10 /* DUNDi version (should be 1) (short) */ +#define DUNDI_IE_EXPIRATION 11 /* Recommended expiration (short) */ +#define DUNDI_IE_UNKNOWN 12 /* Unknown command (byte) */ +#define DUNDI_IE_CAUSE 14 /* Success or cause of failure */ +#define DUNDI_IE_REQEID 15 /* EID being requested for EIDQUERY*/ +#define DUNDI_IE_ENCDATA 16 /* AES-128 encrypted data */ +#define DUNDI_IE_SHAREDKEY 17 /* RSA encrypted AES-128 key */ +#define DUNDI_IE_SIGNATURE 18 /* RSA Signature of encrypted shared key */ +#define DUNDI_IE_KEYCRC32 19 /* CRC32 of encrypted key (int) */ +#define DUNDI_IE_HINT 20 /* Answer hints (struct ast_hint) */ + +#define DUNDI_IE_DEPARTMENT 21 /* Department, for EIDQUERY (string) */ +#define DUNDI_IE_ORGANIZATION 22 /* Organization, for EIDQUERY (string) */ +#define DUNDI_IE_LOCALITY 23 /* City/Locality, for EIDQUERY (string) */ +#define DUNDI_IE_STATE_PROV 24 /* State/Province, for EIDQUERY (string) */ +#define DUNDI_IE_COUNTRY 25 /* Country, for EIDQUERY (string) */ +#define DUNDI_IE_EMAIL 26 /* E-mail addy, for EIDQUERY (string) */ +#define DUNDI_IE_PHONE 27 /* Contact Phone, for EIDQUERY (string) */ +#define DUNDI_IE_IPADDR 28 /* IP Address, for EIDQUERY (string) */ +#define DUNDI_IE_CACHEBYPASS 29 /* Bypass cache (empty) */ + +#define DUNDI_IE_PEERSTATUS 30 /* Peer/peer status (struct dundi_peer_status) */ + +#define DUNDI_FLUFF_TIME 2000 /* Amount of time for answer */ +#define DUNDI_TTL_TIME 200 /* Incremental average time */ + +#define DUNDI_DEFAULT_RETRANS 5 +#define DUNDI_DEFAULT_RETRANS_TIMER 1000 +#define DUNDI_DEFAULT_TTL 120 /* In seconds/hops like TTL */ +#define DUNDI_DEFAULT_VERSION 1 +#define DUNDI_DEFAULT_CACHE_TIME 3600 /* In seconds */ +#define DUNDI_DEFAULT_KEY_EXPIRE 3600 /* Life of shared key In seconds */ +#define DUNDI_DEF_EMPTY_CACHE_TIME 60 /* In seconds, cache of empty answer */ +#define DUNDI_WINDOW 1 /* Max 1 message in window */ + +#define DEFAULT_MAXMS 2000 + +struct dundi_result { + int flags; + int weight; + int expiration; + int techint; + dundi_eid eid; + char eid_str[20]; + char tech[10]; + char dest[256]; +}; + +struct dundi_entity_info { + char country[80]; + char stateprov[80]; + char locality[80]; + char org[80]; + char orgunit[80]; + char email[80]; + char phone[80]; + char ipaddr[80]; +}; + +/* Lookup the given number in the given dundi context (or e164 if unspecified) using the given callerid (if specified) and return up to maxret results in the array specified. + returns the number of results found or -1 on a hangup of teh channel. */ +int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int nocache); + +/* Retrieve information on a specific EID */ +int dundi_query_eid(struct dundi_entity_info *dei, const char *dcontext, dundi_eid eid); + +/* Pre-cache to push upstream peers */ +int dundi_precache(const char *dcontext, const char *number); +#endif /* _ASTERISK_DUNDI_H */ diff -ruN asterisk-1.0.7-orig/pbx/Makefile asterisk-1.0.7-pbx_dundi/pbx/Makefile --- asterisk-1.0.7-orig/pbx/Makefile 2003-10-26 19:50:49.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/pbx/Makefile 2005-06-02 20:21:37.000000000 +0200 @@ -13,7 +13,7 @@ -PBX_LIBS=pbx_config.so pbx_wilcalu.so pbx_spool.so # pbx_gtkconsole.so pbx_kdeconsole.so +PBX_LIBS=pbx_config.so pbx_wilcalu.so pbx_spool.so pbx_dundi.so # pbx_gtkconsole.so pbx_kdeconsole.so # Add GTK console if appropriate PBX_LIBS+=$(shell gtk-config --cflags >/dev/null 2>/dev/null && echo "pbx_gtkconsole.so") @@ -51,6 +51,9 @@ 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 $(LDFLAGS_EXTRA) -lz + %.moc : %.h $(MOC) $< -o $@ diff -ruN asterisk-1.0.7-orig/pbx/dundi-parser.c asterisk-1.0.7-pbx_dundi/pbx/dundi-parser.c --- asterisk-1.0.7-orig/pbx/dundi-parser.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/pbx/dundi-parser.c 2005-06-02 20:21:37.000000000 +0200 @@ -0,0 +1,813 @@ +/* + * Distributed Universal Number Discovery (DUNDi) + * + * Copyright (C) 2004, Digium Inc. + * + * Written by Mark Spencer + * + * This program is Free Software distributed under the terms of + * of the GNU General Public License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dundi-parser.h" +#include + +static void internaloutput(const char *str) +{ + fputs(str, stdout); +} + +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;xeid) / 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_cbypass(char *output, int maxlen, void *value, int len) +{ + strncpy(output, "Bypass Caches", 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, "", 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, "", 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= 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 ", + "PRECACHERQ ", + "PRECACHERP ", + "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; + case DUNDI_IE_CACHEBYPASS: + ies->cbypass = 1; + 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 -ruN asterisk-1.0.7-orig/pbx/dundi-parser.h asterisk-1.0.7-pbx_dundi/pbx/dundi-parser.h --- asterisk-1.0.7-orig/pbx/dundi-parser.h 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/pbx/dundi-parser.h 2005-06-02 20:21:37.000000000 +0200 @@ -0,0 +1,88 @@ +/* + * Distributed Universal Number Discovery (DUNDi) + * + * Copyright (C) 2004, Digium Inc. + * + * Written by Mark Spencer + * + * 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 +#include + +#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; + int cbypass; +}; + +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 -ruN asterisk-1.0.7-orig/pbx/pbx_dundi.c asterisk-1.0.7-pbx_dundi/pbx/pbx_dundi.c --- asterisk-1.0.7-orig/pbx/pbx_dundi.c 1970-01-01 01:00:00.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/pbx/pbx_dundi.c 2005-06-02 20:21:37.000000000 +0200 @@ -0,0 +1,4881 @@ +/* + * Distributed Universal Number Discovery (DUNDi) + * + * Copyright (C) 2004, Digium Inc. + * + * Written by Mark Spencer + * + * This program is Free Software distributed under the terms of + * of the GNU General Public License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dundi-parser.h" +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) || defined(__NetBSD__) +#include +#include +#endif +#include +#include +#include +#include +#if defined(__FreeBSD__) || defined(__NetBSD__) +#include +#include +#endif +#include + +#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) + +/* Keep times of last 10 lookups */ +#define DUNDI_TIMING_HISTORY 10 + +#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 FLAG_STOREHIST (1 << 6) /* Record historic performance */ + +#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 pthread_t precachethreadid = 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 int global_storehistory = 0; +static int map_update_interval = 0; +static int map_updates_per_pkt = 45; +static int map_peering_sid = -1; +static int map_contact_sid = -1; +static char map_context[80]; +static struct sockaddr_in map_addr; +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_precache_queue { + struct dundi_precache_queue *next; + char *context; + time_t expiration; + char number[0]; +}; + +struct dundi_request; + +struct dundi_transaction { + struct sockaddr_in addr; /* Other end of transaction */ + struct timeval start; /* When this transaction was created */ + 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 retranstimer; /* How long to wait before retransmissions */ + 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; + int cbypass; + int pfds[2]; + 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; + struct permission *precachesend; + struct permission *precachereceive; + 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; + int lookuptimes[DUNDI_TIMING_HISTORY]; + char *lookups[DUNDI_TIMING_HISTORY]; + int avgms; + struct dundi_transaction *regtrans; /* Registration transaction */ + struct dundi_transaction *qualtrans; /* Qualify transaction */ + struct dundi_transaction *keypending; + int model; /* Pull model */ + int pcmodel; /* Push/precache 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; + +static struct dundi_precache_queue *pcq; + +AST_MUTEX_DEFINE_STATIC(peerlock); +AST_MUTEX_DEFINE_STATIC(pclock); + +static int dundi_xmit(struct dundi_packet *pack); + +static void dundi_debug_output(const char *data) +{ + if (dundidebug) + ast_verbose("%s", data); +} + +static void dundi_error_output(const char *data) +{ + ast_log(LOG_WARNING, "%s", 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, int cybpass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[]); +static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[]); +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_PRECACHERQ: + 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) +{ +#if defined(SIOCGIFHWADDR) + 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; + } + } + } +#else +#if defined(ifa_broadaddr) + char eid_str[20]; + struct ifaddrs *ifap; + + if (getifaddrs(&ifap) == 0) { + struct ifaddrs *p; + for (p = ifap; p; p = p->ifa_next) { + if (p->ifa_addr->sa_family == AF_LINK) { + struct sockaddr_dl* sdp = (struct sockaddr_dl*) p->ifa_addr; + memcpy( + &(global_eid.eid), + sdp->sdl_data + sdp->sdl_nlen, 6); + ast_log(LOG_DEBUG, "Seeding global EID '%s' from '%s'\n", dundi_eid_to_str(eid_str, sizeof(eid_str), &global_eid), ifap->ifa_name); + freeifaddrs(ifap); + return; + } + } + freeifaddrs(ifap); + } +#endif +#endif + 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; + int nocache; + 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; + 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) { + struct varshead headp; + struct ast_var_t *newvariable; + 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) { + AST_LIST_HEAD_INIT(&headp); + newvariable = ast_var_assign("NUMBER", called_number); + AST_LIST_INSERT_HEAD(&headp, newvariable, entries); + newvariable = ast_var_assign("EID", dr[anscnt].eid_str); + AST_LIST_INSERT_HEAD(&headp, newvariable, entries); + newvariable = ast_var_assign("SECRET", cursecret); + AST_LIST_INSERT_HEAD(&headp, newvariable, entries); + newvariable = ast_var_assign("IPADDR", ipaddr); + AST_LIST_INSERT_HEAD(&headp, newvariable, entries); + pbx_substitute_variables_varshead(&headp, map->dest, dr[anscnt].dest, sizeof(dr[anscnt].dest)); + while (!AST_LIST_EMPTY(&headp)) { /* List Deletion. */ + newvariable = AST_LIST_FIRST(&headp); + AST_LIST_REMOVE_HEAD(&headp, entries); + ast_var_delete(newvariable); + } + } 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;xlcontext, 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; + } + } + } + } + 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;xnummaps;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;xcalled_context, st->called_number, st->ttl, 1, &hmd, &expiration, st->nocache, 0, NULL, 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 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 void *dundi_precache_thread(void *data) +{ + struct dundi_query_state *st = data; + struct dundi_ie_data ied; + struct dundi_hint_metadata hmd; + char eid_str[20]; + + ast_log(LOG_DEBUG, "Whee, precaching '%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)); + + /* Now produce precache */ + dundi_precache_internal(st->called_context, st->called_number, st->ttl, st->eids); + + 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 { + dundi_send(st->trans, DUNDI_COMMAND_PRECACHERP, 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 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 push) +{ + 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; + + /* Keep pushes a little longer, cut pulls a little short */ + if (push) + expiration += 10; + else + expiration -= 10; + if (expiration < 1) + expiration = 1; + 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;xrespcount;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 dundi_prop_precache(struct dundi_transaction *trans, struct dundi_ies *ies, char *ccontext) +{ + struct dundi_query_state *st; + int totallen; + int x,z; + struct dundi_ie_data ied; + char *s; + struct dundi_result dr2[MAX_RESULTS]; + struct dundi_request dr; + struct dundi_hint_metadata hmd; + + struct dundi_mapping *cur; + int mapcount; + int skipfirst = 0; + + pthread_t lookupthread; + pthread_attr_t attr; + + memset(&dr2, 0, sizeof(dr2)); + memset(&dr, 0, sizeof(dr)); + memset(&hmd, 0, sizeof(hmd)); + + /* Forge request structure to hold answers for cache */ + hmd.flags = DUNDI_HINT_DONT_ASK | DUNDI_HINT_UNAFFECTED; + dr.dr = dr2; + dr.maxcount = MAX_RESULTS; + dr.expiration = DUNDI_DEFAULT_CACHE_TIME; + dr.hmd = &hmd; + dr.pfds[0] = dr.pfds[1] = -1; + trans->parent = &dr; + strncpy(dr.dcontext, ies->called_context ? ies->called_context : "e164", sizeof(dr.dcontext)); + strncpy(dr.number, ies->called_number, sizeof(dr.number) - 1); + + for (x=0;xanscount;x++) { + if (trans->parent->respcount < trans->parent->maxcount) { + /* Make sure it's not already there */ + for (z=0;zparent->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 in precache 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. */ + cache_save(&trans->them_eid, trans->parent, 0, 0, ies->expiration, 1); + if (ies->hint) + cache_save_hint(&trans->them_eid, trans->parent, ies->hint, ies->expiration); + + 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; + } + + /* Prepare to run a query and then propagate that as necessary */ + 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; + st->nocache = ies->cbypass; + 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, "Forwarding precache 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_precache_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_PRECACHERP, 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_PRECACHERP, 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; + st->nocache = ies->cbypass; + 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_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;zrespcount;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); + dundi_eid_to_str(req->dr[req->respcount].eid_str, + sizeof(req->dr[req->respcount].eid_str), &req->dr[req->respcount].eid); + 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; + trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER; + if (p->lastms > 1) { + trans->retranstimer = p->lastms * 2; + /* Keep it from being silly */ + if (trans->retranstimer < 150) + trans->retranstimer = 150; + } + if (trans->retranstimer > DUNDI_DEFAULT_RETRANS_TIMER) + trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER; + } 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; + int authpass=0; + 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: + case DUNDI_COMMAND_PRECACHERQ: + if (cmd == DUNDI_COMMAND_EIDQUERY) + resp = DUNDI_COMMAND_EIDRESPONSE; + else if (cmd == DUNDI_COMMAND_PRECACHERQ) + resp = DUNDI_COMMAND_PRECACHERP; + 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 ((cmd == DUNDI_COMMAND_DPDISCOVER) && + (peer->model & DUNDI_MODEL_INBOUND) && + 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 if ((cmd = DUNDI_COMMAND_PRECACHERQ) && + (peer->pcmodel & DUNDI_MODEL_INBOUND) && + has_permission(peer->include, ies.called_context)) { + res = dundi_prop_precache(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) { + /* 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;xparent->respcount < trans->parent->maxcount) { + /* Make sure it's not already there */ + for (z=0;zparent->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, 0); + 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) { + /* 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: + case DUNDI_COMMAND_PRECACHERP: + /* 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 void *process_precache(void *ign) +{ + struct dundi_precache_queue *qe; + time_t now; + char context[256]=""; + char number[256]=""; + int run; + for (;;) { + time(&now); + run = 0; + ast_mutex_lock(&pclock); + if (pcq) { + if (!pcq->expiration) { + /* Gone... Remove... */ + qe = pcq; + pcq = pcq->next; + free(qe); + } else if (pcq->expiration < now) { + /* Process this entry */ + pcq->expiration = 0; + strncpy(context, pcq->context, sizeof(context) - 1); + strncpy(number, pcq->number, sizeof(number) - 1); + run = 1; + } + } + ast_mutex_unlock(&pclock); + if (run) { + dundi_precache(context, number); + } else + sleep(1); + } + return NULL; +} + +static int start_network_thread(void) +{ + ast_pthread_create(&netthreadid, NULL, network_thread, NULL); + ast_pthread_create(&precachethreadid, NULL, process_precache, NULL); + return 0; +} + +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_do_store_history(int fd, int argc, char *argv[]) +{ + if (argc != 3) + return RESULT_SHOWUSAGE; + global_storehistory = 1; + ast_cli(fd, "DUNDi History Storage Enabled\n"); + return RESULT_SUCCESS; +} + +static int dundi_flush(int fd, int argc, char *argv[]) +{ + int stats=0; + if ((argc < 2) || (argc > 3)) + return RESULT_SHOWUSAGE; + if (argc > 2) { + if (!strcasecmp(argv[2], "stats")) + stats = 1; + else + return RESULT_SHOWUSAGE; + } + if (stats) { + /* Flush statistics */ + struct dundi_peer *p; + int x; + ast_mutex_lock(&peerlock); + p = peers; + while(p) { + for (x=0;xlookups[x]) + free(p->lookups[x]); + p->lookups[x] = NULL; + p->lookuptimes[x] = 0; + } + p->avgms = 0; + p = p->next; + } + ast_mutex_unlock(&peerlock); + } else { + 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 int dundi_no_store_history(int fd, int argc, char *argv[]) +{ + if (argc != 4) + return RESULT_SHOWUSAGE; + global_storehistory = 0; + ast_cli(fd, "DUNDi History Storage 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; + int bypass = 0; + struct dundi_result dr[MAX_RESULTS]; + struct timeval start; + if ((argc < 3) || (argc > 4)) + return RESULT_SHOWUSAGE; + if (argc > 3) { + if (!strcasecmp(argv[3], "bypass")) + bypass=1; + else + return RESULT_SHOWUSAGE; + } + strncpy(tmp, argv[2], sizeof(tmp) - 1); + context = strchr(tmp, '@'); + if (context) { + *context = '\0'; + context++; + } + gettimeofday(&start, NULL); + res = dundi_lookup(dr, MAX_RESULTS, NULL, context, tmp, bypass); + + 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 3)) + return RESULT_SHOWUSAGE; + strncpy(tmp, argv[2], sizeof(tmp) - 1); + context = strchr(tmp, '@'); + if (context) { + *context = '\0'; + context++; + } + gettimeofday(&start, NULL); + res = dundi_precache(context, tmp); + + if (res < 0) + ast_cli(fd, "DUNDi precache returned error.\n"); + else if (!res) + ast_cli(fd, "DUNDi precache returned no error.\n"); + ast_cli(fd, "DUNDi lookup completed in %d ms\n", calc_ms(&start)); + 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]; + int x, cnt; + + 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, "Order: %s\n", order); + ast_cli(fd, "Host: %s\n", peer->addr.sin_addr.s_addr ? ast_inet_ntoa(iabuf, sizeof(iabuf), peer->addr.sin_addr) : ""); + 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) ? "" : peer->inkey); + ast_cli(fd, "Out Key: %s\n", ast_strlen_zero(peer->outkey) ? "" : 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; + } + cnt = 0; + for (x=0;xlookups[x]) { + if (!cnt) + ast_cli(fd, "Last few query times:\n"); + ast_cli(fd, "-- %d. %s (%d ms)\n", x + 1, peer->lookups[x], peer->lookuptimes[x]); + cnt++; + } + } + if (cnt) + ast_cli(fd, "Average query time: %d ms\n", peer->avgms); + } 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 %-10.10s %-10.10s %-8.8s %-15.15s\n" +#define FORMAT "%-20.20s %-15.15s %s %-10.10s %-10.10s %-8.8s %-15.15s\n" + struct dundi_peer *peer; + char iabuf[INET_ADDRSTRLEN]; + int registeredonly=0; + char avgms[20]; + char eid_str[20]; + char *order = NULL; + + 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", "Order", "AvgTime", "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); + if (peer->avgms) + snprintf(avgms, sizeof(avgms), "%d ms", peer->avgms); + else + strcpy(avgms, "Unavail"); + 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"; + } + 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), order, avgms, 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), order, avgms, 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) ? "" : 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) ? "" : 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 int dundi_show_precache(int fd, int argc, char *argv[]) +{ +#define FORMAT2 "%-12.12s %-12.12s %-10.10s\n" +#define FORMAT "%-12.12s %-12.12s %02d:%02d:%02d\n" + struct dundi_precache_queue *qe; + int h,m,s; + time_t now; + + if (argc != 3) + return RESULT_SHOWUSAGE; + time(&now); + ast_mutex_lock(&pclock); + ast_cli(fd, FORMAT2, "Number", "Context", "Expiration"); + for (qe = pcq;qe;qe = qe->next) { + s = qe->expiration - now; + h = s / 3600; + s = s % 3600; + m = s / 60; + s = s % 60; + ast_cli(fd, FORMAT, qe->number, qe->context, h,m,s); + } + ast_mutex_unlock(&pclock); + 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 store_history_usage[] = +"Usage: dundi store history\n" +" Enables storing of DUNDi requests and times for debugging\n" +"purposes\n"; + +static char no_store_history_usage[] = +"Usage: dundi no store history\n" +" Disables storing of DUNDi requests and times for debugging\n" +"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_precache_usage[] = +"Usage: dundi show precache\n" +" Lists all known DUNDi scheduled precache updates.\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 [@context] [bypass]\n" +" Lookup the given number within the given DUNDi context\n" +"(or e164 if none is specified). Bypasses cache if 'bypass'\n" +"keyword is specified.\n"; + +static char precache_usage[] = +"Usage: dundi precache [@context]\n" +" Lookup the given number within the given DUNDi context\n" +"(or e164 if none is specified) and precaches the results to any\n" +"upstream DUNDi push servers.\n"; + +static char query_usage[] = +"Usage: dundi query [@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 [stats]\n" +" Flushes DUNDi answer cache, used primarily for debug. If\n" +"'stats' is present, clears timer statistics instead of normal\n" +"operation.\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_store_history = + { { "dundi", "store", "history", NULL }, dundi_do_store_history, "Enable DUNDi historic records", store_history_usage }; + +static struct ast_cli_entry cli_no_store_history = + { { "dundi", "no", "store", "history", NULL }, dundi_no_store_history, "Disable DUNDi historic records", no_store_history_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_precache = + { { "dundi", "show", "precache", NULL }, dundi_show_precache, "Show DUNDi precache", show_precache_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_precache = + { { "dundi", "precache", NULL }, dundi_do_precache, "Precache a number in DUNDi", precache_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)); + if (global_storehistory) { + gettimeofday(&trans->start, NULL); + trans->flags |= FLAG_STOREHIST; + } + trans->retranstimer = DUNDI_DEFAULT_RETRANS_TIMER; + 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 inline char *dstatus_append_long(char *buf, long val) { + val = htonl(val); + memcpy(buf, &val, sizeof(long)); + return (buf + sizeof(long)); +} + +static inline char *dstatus_append_short(char *buf, short val) { + val = htons(val); + memcpy(buf, &val, sizeof(short)); + return (buf + sizeof(short)); +} + +static inline char *dstatus_append_string(char *buf, char *str) { + unsigned short len = (unsigned short)strlen(str); + buf = dstatus_append_short(buf, len); + memcpy(buf, str, len); + return (buf + len); +} + +/* Create a packet for the DUNDi Status Updates */ +static char *dstatus_v2_pkt_header(char *buf, char pkt_type, time_t ts, short seq, short totseq) { + *buf++ = '\003'; + *buf++ = pkt_type; + + /* Timestamp this sequence */ + buf = dstatus_append_long(buf, ts); + + /* Setup sequence headers */ + buf = dstatus_append_short(buf, seq); + buf = dstatus_append_short(buf, totseq); + + /* Append our global EID */ + memcpy(buf, &global_eid, sizeof(global_eid)); + buf += sizeof(global_eid); + + buf = dstatus_append_string(buf, map_context); + + return buf; +} + +static int dundi_xmit_peering(void *data) +{ + char buf[1500]; + char *ptr = buf; + short seq = 1, totseq = 0; + int need_send = 0, peer_count = 0; + struct dundi_peer *peer; + time_t send_t = time(NULL); + + /* If we have no where to send, don't bother */ + if(!map_addr.sin_addr.s_addr) { + if(option_verbose) + ast_verbose(VERBOSE_PREFIX_1 "No server to send mapping update to...\n"); + map_peering_sid = ast_sched_add(sched, map_update_interval, dundi_xmit_peering, 0); + return 0; + } + + /* Provide a sequence number for the packet and totals */ + ast_mutex_lock(&peerlock); + for (peer = peers;peer;peer = peer->next) { + if(has_permission(peer->include, map_context) || has_permission(peer->permit, map_context)) { + peer_count++; + need_send = 1; + if(peer_count == map_updates_per_pkt) { + peer_count = 0; + need_send = 0; + totseq++; + } + } + } + totseq += need_send; + + /* Initialize the packet header */ + ptr = dstatus_v2_pkt_header(buf, 1, send_t, seq, totseq); + + /* Include our update interval, in seconds */ + ptr = dstatus_append_short(ptr, map_update_interval/1000); + + /* Include peers/packet configured */ + *ptr++ = (unsigned char)map_updates_per_pkt; + + peer_count = 0; + need_send = 0; + for (peer = peers;peer;peer = peer->next) { + if(!has_permission(peer->include, map_context) && !has_permission(peer->permit, map_context)) + continue; + + peer_count++; + need_send = 1; + + /* Copy the peers EID */ + memcpy(ptr, &peer->eid, sizeof(peer->eid)); + ptr += sizeof(peer->eid); + + /* This is the remote peer */ + *ptr++ = (peer->dynamic?0:1); + memcpy(ptr, &peer->addr.sin_addr.s_addr, sizeof(peer->addr.sin_addr.s_addr)); + ptr += sizeof(peer->addr.sin_addr.s_addr); + + /* Append the model and order */ + *ptr++ = peer->model; + *ptr++ = peer->order; + + /* Do some long encoding of the timings */ + ptr = dstatus_append_long(ptr, peer->maxms); + ptr = dstatus_append_long(ptr, peer->lastms); + ptr = dstatus_append_long(ptr, peer->avgms); + + if(peer_count == map_updates_per_pkt) { /* Okay, it's actually arbitrary */ + peer_count = 0; + need_send = 0; + sendto(netsocket, buf, ptr - buf, 0, (struct sockaddr *)&map_addr, sizeof(map_addr)); + seq++; /* We sent one, so move on */ + + ptr = dstatus_v2_pkt_header(buf, 1, send_t, seq, totseq); + /* Include our update interval, in seconds */ + ptr = dstatus_append_short(ptr, map_update_interval/1000); + + /* Include peers/packet configured */ + *ptr++ = (unsigned char)map_updates_per_pkt; + } + } + + /* If we get here, and haven't sent the packet, send it now */ + if(need_send) + sendto(netsocket, buf, ptr - buf, 0, (struct sockaddr *)&map_addr, sizeof(map_addr)); + + /* Unlock */ + ast_mutex_unlock(&peerlock); + + /* Reschedule yourselves */ + map_peering_sid = ast_sched_add(sched, map_update_interval, dundi_xmit_peering, 0); + return RESULT_SUCCESS; +} + +static int dundi_xmit_contact(void *data) { + char buf[1500]; + char *ptr = buf; + + /* Packet type 2, sent now, 1/1 packets */ + ptr = dstatus_v2_pkt_header(buf, 2, time(NULL), 1, 1); + + ptr = dstatus_append_string(ptr, dept); + ptr = dstatus_append_string(ptr, org); + ptr = dstatus_append_string(ptr, locality); + ptr = dstatus_append_string(ptr, stateprov); + ptr = dstatus_append_string(ptr, country); + ptr = dstatus_append_string(ptr, email); + ptr = dstatus_append_string(ptr, phone); + + sendto(netsocket, buf, ptr - buf, 0, (struct sockaddr *)&map_addr, sizeof(map_addr)); + + map_contact_sid = ast_sched_add(sched, map_update_interval * 5, dundi_xmit_contact, 0); + return RESULT_SUCCESS; +} + +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; + int x; + int cnt; + char eid_str[20]; + if (trans->flags & (FLAG_ISREG | FLAG_ISQUAL | FLAG_STOREHIST)) { + 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; + } + if (trans->flags & FLAG_STOREHIST) { + if (trans->parent && !ast_strlen_zero(trans->parent->number)) { + if (!dundi_eid_cmp(&trans->them_eid, &peer->eid)) { + peer->avgms = 0; + cnt = 0; + if (peer->lookups[DUNDI_TIMING_HISTORY-1]) + free(peer->lookups[DUNDI_TIMING_HISTORY-1]); + for (x=DUNDI_TIMING_HISTORY-1;x>0;x--) { + peer->lookuptimes[x] = peer->lookuptimes[x-1]; + peer->lookups[x] = peer->lookups[x-1]; + if (peer->lookups[x]) { + peer->avgms += peer->lookuptimes[x]; + cnt++; + } + } + peer->lookuptimes[0] = calc_ms(&trans->start); + peer->lookups[0] = malloc(strlen(trans->parent->number) + strlen(trans->parent->dcontext) + 2); + if (peer->lookups[0]) { + sprintf(peer->lookups[0], "%s@%s", trans->parent->number, trans->parent->dcontext); + peer->avgms += peer->lookuptimes[0]; + cnt++; + } + if (cnt) + peer->avgms /= cnt; + } + } + } + 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; + } + if (!trans->parent->trans) { + /* Wake up sleeper */ + if (trans->parent->pfds[1] > -1) { + write(trans->parent->pfds[1], "killa!", 6); + } + } + } + /* 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, trans->retranstimer, 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: + case DUNDI_COMMAND_PRECACHERQ: + case DUNDI_COMMAND_PRECACHERP: + 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;xeidcount;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->parent->cbypass) + dundi_ie_append(&ied, DUNDI_IE_CACHEBYPASS); + 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 precache_trans(struct dundi_transaction *trans, struct dundi_mapping *maps, int mapcount, int *minexp, int *foundanswers) +{ + struct dundi_ie_data ied; + int x, res; + int max = 999999; + int expiration = DUNDI_DEFAULT_CACHE_TIME; + int ouranswers=0; + dundi_eid *avoid[1] = { NULL, }; + int direct[1] = { 0, }; + struct dundi_result dr[MAX_RESULTS]; + struct dundi_hint_metadata hmd; + if (!trans->parent) { + ast_log(LOG_WARNING, "Tried to discover a transaction with no parent?!?\n"); + return -1; + } + memset(&hmd, 0, sizeof(hmd)); + memset(&dr, 0, sizeof(dr)); + /* Look up the answers we're going to include */ + for (x=0;xparent->number, &trans->us_eid, ouranswers, &hmd); + if (ouranswers < 0) + ouranswers = 0; + for (x=0;xparent->dcontext, trans->parent->number, trans->ttl, 1, &hmd, &expiration, 0, 1, &trans->them_eid, avoid, direct); + if (res > 0) { + /* Append answer in result */ + ouranswers += res; + } + } + + if (ouranswers > 0) { + *foundanswers += ouranswers; + 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;xeidcount;x++) + dundi_ie_append_eid(&ied, DUNDI_IE_EID, &trans->eids[x]); + 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); + for (x=0;x 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); + if (trans->autokilltimeout) + trans->autokillid = ast_sched_add(sched, trans->autokilltimeout, do_autokill, trans); + if (expiration < *minexp) + *minexp = expiration; + return dundi_send(trans, DUNDI_COMMAND_PRECACHERQ, 0, 0, &ied); + } else { + /* Oops, nothing to send... */ + destroy_trans(trans, 0); + return 0; + } +} + +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;xeidcount;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; + trans = dr->trans; + while(trans) { + dundi_discover(trans); + trans = trans->next; + } + return 0; +} + +static int precache_transactions(struct dundi_request *dr, struct dundi_mapping *maps, int mapcount, int *expiration, int *foundanswers) +{ + struct dundi_transaction *trans; + trans = dr->trans; + while(trans) { + precache_trans(trans, maps, mapcount, expiration, foundanswers); + trans = trans->next; + } + 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;xeidcount;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 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, int nocache, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int directs[]) +{ + struct dundi_peer *p; + int x; + int res; + int pass; + int allowconnect; + char eid_str[20]; + ast_mutex_lock(&peerlock); + p = peers; + while(p) { + if (modeselect == 1) { + /* Send the precache to push upstreams only! */ + pass = has_permission(p->permit, dr->dcontext) && (p->pcmodel & DUNDI_MODEL_OUTBOUND); + allowconnect = 1; + } else { + /* Normal lookup / EID query */ + pass = has_permission(p->include, dr->dcontext); + allowconnect = p->model & DUNDI_MODEL_OUTBOUND; + } + if (skip) { + if (!dundi_eid_cmp(skip, &p->eid)) + pass = 0; + } + if (pass) { + 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((nocache || !(res = cache_lookup(dr, &p->eid, dr->crc32, &dr->expiration)))) { + res = 0; + /* 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 (allowconnect) { + 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, int cbypass, int modeselect, dundi_eid *skip, dundi_eid *avoid[], int direct[]) +{ + int res; + struct dundi_request dr, *pending; + dundi_eid *rooteid=NULL; + int x; + int ttlms; + int ms; + 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)); + if (pipe(dr.pfds)) { + ast_log(LOG_WARNING, "pipe failed: %s\n" , strerror(errno)); + return -1; + } + dr.dr = result; + dr.hmd = hmd; + dr.maxcount = maxret; + dr.expiration = *expiration; + dr.cbypass = cbypass; + 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)); + close(dr.pfds[0]); + close(dr.pfds[1]); + 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)) { + /* XXX Would be nice to have a way to poll/select here XXX */ + 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, cbypass, modeselect, skip, 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); + close(dr.pfds[0]); + close(dr.pfds[1]); + 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)) { + ms = 100; + ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL); + } + 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; + close(dr.pfds[0]); + close(dr.pfds[1]); + return res; +} + +int dundi_lookup(struct dundi_result *result, int maxret, struct ast_channel *chan, const char *dcontext, const char *number, int cbypass) +{ + 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, cbypass, 0, NULL, avoid, direct); +} + +static void reschedule_precache(const char *number, const char *context, int expiration) +{ + int len; + struct dundi_precache_queue *qe, *prev=NULL; + ast_mutex_lock(&pclock); + qe = pcq; + while(qe) { + if (!strcmp(number, qe->number) && !strcasecmp(context, qe->context)) { + if (prev) + prev->next = qe->next; + else + pcq = qe->next; + qe->next = NULL; + break; + } + prev = qe; + qe = qe->next; + }; + if (!qe) { + len = sizeof(struct dundi_precache_queue); + len += strlen(number) + 1; + len += strlen(context) + 1; + qe = malloc(len); + if (qe) { + memset(qe, 0, len); + strcpy(qe->number, number); + qe->context = qe->number + strlen(number) + 1; + strcpy(qe->context, context); + } + } + time(&qe->expiration); + qe->expiration += expiration; + prev = pcq; + if (prev) { + while(prev->next && (prev->next->expiration <= qe->expiration)) + prev = prev->next; + qe->next = prev->next; + prev->next = qe; + } else + pcq = qe; + ast_mutex_unlock(&pclock); + +} + +static void dundi_precache_full(void) +{ + struct dundi_mapping *cur; + struct ast_context *con; + struct ast_exten *e; + cur = mappings; + while(cur) { + ast_log(LOG_NOTICE, "Should precache context '%s'\n", cur->dcontext); + ast_lock_contexts(); + con = ast_walk_contexts(NULL); + while(con) { + if (!strcasecmp(cur->lcontext, ast_get_context_name(con))) { + /* Found the match, now queue them all up */ + ast_lock_context(con); + e = ast_walk_context_extensions(con, NULL); + while(e) { + reschedule_precache(ast_get_extension_name(e), cur->dcontext, 0); + e = ast_walk_context_extensions(con, e); + } + ast_unlock_context(con); + } + con = ast_walk_contexts(con); + } + ast_unlock_contexts(); + cur = cur->next; + } +} + +static int dundi_precache_internal(const char *context, const char *number, int ttl, dundi_eid *avoids[]) +{ + struct dundi_request dr; + struct dundi_hint_metadata hmd; + struct dundi_result dr2[MAX_RESULTS]; + struct timeval start; + struct dundi_mapping *maps=NULL, *cur; + int nummaps; + int foundanswers; + int foundcache, skipped, ttlms, ms; + if (!context) + context = "e164"; + ast_log(LOG_DEBUG, "Precache internal (%s@%s)!\n", number, context); + + ast_mutex_lock(&peerlock); + nummaps = 0; + cur = mappings; + while(cur) { + if (!strcasecmp(cur->dcontext, context)) + nummaps++; + cur = cur->next; + } + if (nummaps) { + maps = alloca(nummaps * sizeof(struct dundi_mapping)); + nummaps = 0; + if (maps) { + cur = mappings; + while(cur) { + if (!strcasecmp(cur->dcontext, context)) + maps[nummaps++] = *cur; + cur = cur->next; + } + } + } + ast_mutex_unlock(&peerlock); + if (!nummaps || !maps) + return -1; + ttlms = DUNDI_FLUFF_TIME + ttl * DUNDI_TTL_TIME; + memset(&dr2, 0, sizeof(dr2)); + memset(&dr, 0, sizeof(dr)); + memset(&hmd, 0, sizeof(hmd)); + dr.dr = dr2; + strncpy(dr.number, number, sizeof(dr.number) - 1); + strncpy(dr.dcontext, context ? context : "e164", sizeof(dr.dcontext) - 1); + dr.maxcount = MAX_RESULTS; + dr.expiration = DUNDI_DEFAULT_CACHE_TIME; + dr.hmd = &hmd; + pipe(dr.pfds); + dr.pfds[0] = dr.pfds[1] = -1; + build_transactions(&dr, ttl, 0, &foundcache, &skipped, 0, 1, 1, NULL, avoids, NULL); + optimize_transactions(&dr, 0); + foundanswers = 0; + precache_transactions(&dr, maps, nummaps, &dr.expiration, &foundanswers); + if (foundanswers) { + if (dr.expiration > 0) + reschedule_precache(dr.number, dr.dcontext, dr.expiration); + else + ast_log(LOG_NOTICE, "Weird, expiration = %d, but need to precache for %s@%s?!\n", dr.expiration, dr.number, dr.dcontext); + } + gettimeofday(&start, NULL); + while(dr.trans && (calc_ms(&start) < ttlms)) { + if (dr.pfds[0] > -1) { + ms = 100; + ast_waitfor_n_fd(dr.pfds, 1, &ms, NULL); + } else + usleep(1); + } + cancel_request(&dr); + if (dr.pfds[0] > -1) { + close(dr.pfds[0]); + close(dr.pfds[1]); + } + return 0; +} + +int dundi_precache(const char *context, const char *number) +{ + dundi_eid *avoid[1] = { NULL, }; + return dundi_precache_internal(context, number, dundi_ttl, avoid); +} + +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, 0, 0, NULL, 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 = ast_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;yoptions |= 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, int *globalpcmode) +{ + 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 if (!strcasecmp(v->value, "none")) + peer->model = 0; + else { + ast_log(LOG_WARNING, "Unknown model '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n", + v->value, v->lineno); + } + } else if (!strcasecmp(v->name, "precache")) { + if (!strcasecmp(v->value, "inbound")) + peer->pcmodel = DUNDI_MODEL_INBOUND; + else if (!strcasecmp(v->value, "outbound")) + peer->pcmodel = DUNDI_MODEL_OUTBOUND; + else if (!strcasecmp(v->value, "symmetric")) + peer->pcmodel = DUNDI_MODEL_SYMMETRIC; + else if (!strcasecmp(v->value, "none")) + peer->pcmodel = 0; + else { + ast_log(LOG_WARNING, "Unknown pcmodel '%s', should be 'none', 'outbound', 'inbound', or 'symmetric' at line %d\n", + v->value, v->lineno); + } + } + v = v->next; + } + (*globalpcmode) |= peer->pcmodel; + if (!peer->model && !peer->pcmodel) { + ast_log(LOG_WARNING, "Peer '%s' lacks a model or pcmodel, discarding!\n", + dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid)); + peer->dead = 1; + } else if ((peer->model & DUNDI_MODEL_INBOUND) && (peer->pcmodel & DUNDI_MODEL_OUTBOUND)) { + ast_log(LOG_WARNING, "Peer '%s' may not be both inbound/symmetric model and outbound/symmetric precache model, discarding!\n", + dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid)); + peer->dead = 1; + } else if ((peer->model & DUNDI_MODEL_OUTBOUND) && (peer->pcmodel & DUNDI_MODEL_INBOUND)) { + ast_log(LOG_WARNING, "Peer '%s' may not be both outbound/symmetric model and inbound/symmetric precache 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) && !(peer->pcmodel & DUNDI_MODEL_INBOUND)) { + ast_log(LOG_WARNING, "Peer '%s' is supposed to be included in outbound searches but isn't an outbound peer or inbound precache!\n", + dundi_eid_to_str(eid_str, sizeof(eid_str), &peer->eid)); + } else if (peer->permit && !(peer->model & DUNDI_MODEL_INBOUND) && !(peer->pcmodel & DUNDI_MODEL_OUTBOUND)) { + ast_log(LOG_WARNING, "Peer '%s' is supposed to have permission for some inbound searches but isn't an inbound peer or outbound precache!\n", + 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, 0); + for (x=0;x= 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, 0); + if (res > 0) { + sort_results(results, res); + for (x=0;xh_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(); + global_storehistory = 0; + 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; +#if !defined(__NetBSD__) + else if (!strcasecmp(v->value, "mincost")) + tos = IPTOS_MINCOST; +#endif + else if (!strcasecmp(v->value, "none")) + tos = 0; + else +#if defined(__NetBSD__) + ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\n", v->lineno); +#else + ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', or 'none'\n", v->lineno); +#endif + } 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); + } else if (!strcasecmp(v->name, "storehistory")) { + global_storehistory = ast_true(v->value); + } else if (!strcasecmp(v->name, "mapserver")) { + hp = ast_gethostbyname(v->value, &he); + if (hp) { + memcpy(&map_addr.sin_addr, hp->h_addr, sizeof(map_addr.sin_addr)); + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Using mapping server at %s\n", v->value); + } else { + memset(&map_addr.sin_addr, 0, sizeof(map_addr.sin_addr)); + ast_log(LOG_WARNING, "Could not find host '%s' for mapping host\n", v->value); + } + } else if (!strcasecmp(v->name, "mappeers_per_pkt")) { + map_updates_per_pkt = atoi(v->value); + if(map_updates_per_pkt < 1 || map_updates_per_pkt > 45) { + ast_log(LOG_WARNING, "Map updates must be between 1 and 45 inclusive. Setting to 45.\n"); + map_updates_per_pkt = 45; + } + } else if (!strcasecmp(v->name, "mapport")) { + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Using mapping server at port %d\n", atoi(v->value)); + map_addr.sin_port = htons(atoi(v->value)); + } else if (!strcasecmp(v->name, "mapinterval")) { + map_update_interval = atoi(v->value) * 1000; + if(map_update_interval < 5000 && map_update_interval != 0) + map_update_interval = 5000; + if (option_verbose > 1) + ast_verbose(VERBOSE_PREFIX_2 "Using mapping update interval of %d ms\n", map_update_interval); + } else if(!strcasecmp(v->name, "mapcontext")) { + strncpy(map_context, v->value, sizeof(map_context) - 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), &globalpcmodel); + 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(); + + /* Schedule updates */ + if(map_peering_sid > -1) + ast_sched_del(sched, map_peering_sid); + if(map_contact_sid > -1) + ast_sched_del(sched, map_contact_sid); + if(map_update_interval) { + map_peering_sid = ast_sched_add(sched, 1000, dundi_xmit_peering, 0); + map_contact_sid = ast_sched_add(sched, 1000, dundi_xmit_contact, 0); + } + + if (globalpcmodel & DUNDI_MODEL_OUTBOUND) + dundi_precache_full(); + + return 0; +} + +int unload_module(void) +{ + int res; + STANDARD_HANGUP_LOCALUSERS; + ast_cli_unregister(&cli_debug); + ast_cli_unregister(&cli_store_history); + ast_cli_unregister(&cli_flush); + ast_cli_unregister(&cli_no_debug); + ast_cli_unregister(&cli_no_store_history); + 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_precache); + ast_cli_unregister(&cli_show_peer); + ast_cli_unregister(&cli_lookup); + ast_cli_unregister(&cli_precache); + 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; + + /* INADDR_ANY should be 0.0.0.0 */ + map_addr.sin_family = AF_INET; + map_addr.sin_port = ntohs(4525); + map_addr.sin_addr.s_addr = INADDR_ANY; + strncpy(map_context, "open-e164", sizeof(map_context) - 1); + + /* 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_store_history); + ast_cli_register(&cli_flush); + ast_cli_register(&cli_no_debug); + ast_cli_register(&cli_no_store_history); + 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_precache); + ast_cli_register(&cli_show_peer); + ast_cli_register(&cli_lookup); + ast_cli_register(&cli_precache); + 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; +} diff -ruN asterisk-1.0.7-orig/pbx.c asterisk-1.0.7-pbx_dundi/pbx.c --- asterisk-1.0.7-orig/pbx.c 2005-02-19 01:27:52.000000000 +0100 +++ asterisk-1.0.7-pbx_dundi/pbx.c 2005-06-02 20:21:37.000000000 +0200 @@ -820,7 +820,7 @@ /*--- pbx_retrieve_variable: Support for Asterisk built-in variables and functions in the dialplan ---*/ -static void pbx_substitute_variables_temp(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen) +static void pbx_substitute_variables_temp(struct ast_channel *c, const char *var, char **ret, char *workspace, int workspacelen, struct varshead *headp) { char *first,*second; char tmpvar[80] = ""; @@ -829,7 +829,6 @@ int offset,offset2; struct ast_var_t *variables; char *name, *num; /* for callerid name + num variables */ - struct varshead *headp=NULL; if (c) headp=&c->varshead; @@ -854,7 +853,7 @@ if (!first) first = tmpvar + strlen(tmpvar); *first='\0'; - pbx_substitute_variables_temp(c,tmpvar,ret,workspace,workspacelen - 1); + pbx_substitute_variables_temp(c,tmpvar,ret,workspace,workspacelen - 1, headp); if (!(*ret)) return; offset=atoi(first+1); /* The number of characters, @@ -993,7 +992,7 @@ strncpy(workspace, c->language, workspacelen - 1); *ret = workspace; } else { - if (c) { + if (headp) { AST_LIST_TRAVERSE(headp,variables,entries) { #if 0 ast_log(LOG_WARNING,"Comparing variable '%s' with '%s'\n",var,ast_var_name(variables)); @@ -1040,7 +1039,7 @@ } } -void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count) +static void pbx_substitute_variables_helper_full(struct ast_channel *c, const char *cp1, char *cp2, int count, struct varshead *headp) { char *cp4; const char *tmp, *whereweare; @@ -1131,7 +1130,7 @@ /* Retrieve variable value */ workspace[0] = '\0'; - pbx_substitute_variables_temp(c,vars,&cp4, workspace, sizeof(workspace)); + pbx_substitute_variables_temp(c,vars,&cp4, workspace, sizeof(workspace), headp); if (cp4) { length = strlen(cp4); if (length > count) @@ -1206,6 +1205,16 @@ } } +void pbx_substitute_variables_helper(struct ast_channel *c, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(c, cp1, cp2, count, NULL); +} + +void pbx_substitute_variables_varshead(struct varshead *headp, const char *cp1, char *cp2, int count) +{ + pbx_substitute_variables_helper_full(NULL, cp1, cp2, count, headp); +} + static void pbx_substitute_variables(char *passdata, int datalen, struct ast_channel *c, struct ast_exten *e) { memset(passdata, 0, datalen);