/* * Asterisk -- A telephony toolkit for Linux. * * Implementation of Inter-Asterisk eXchange * * Copyright (C) 1999, Mark Spencer * * Mark Spencer * * This program is free software, distributed under the terms 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 #include #include #include #include #include #include #include #include #include "iax.h" /* * Uncomment to try experimental IAX bridge optimization, * designed to reduce latency when IAX calls cannot * be trasnferred */ #define BRIDGE_OPTIMIZATION #define DEFAULT_RETRY_TIME 1000 #define MEMORY_SIZE 100 #define DEFAULT_DROP 3 #define DEBUG_SUPPORT /* Sample over last 100 units to determine historic jitter */ #define GAMMA (0.01) static char *desc = "Inter Asterisk eXchange"; static char *tdesc = "Inter Asterisk eXchange Drver"; static char *type = "IAX"; static char context[80] = "default"; static int max_retries = 4; static int ping_time = 20; static int lagrq_time = 10; static int nextcallno = 0; static int maxjitterbuffer=3000; static int iaxdefaultdpcache=30 * 60; /* Cache dialplan entries for 30 minutes by default */ static int iaxdefaulttimeout = 5; /* Default to wait no more than 5 seconds for a reply to come back */ static int netsocket = -1; static int tos = 0; static int expirey = AST_DEFAULT_REG_EXPIRE; static int usecnt; static pthread_mutex_t usecnt_lock = AST_MUTEX_INITIALIZER; static pthread_mutex_t iaxs_lock = AST_MUTEX_INITIALIZER; int (*regfunk)(char *username, int onoff) = NULL; /* Ethernet, etc */ #define IAX_CAPABILITY_FULLBANDWIDTH 0xFFFF /* T1, maybe ISDN */ #define IAX_CAPABILITY_MEDBANDWIDTH (IAX_CAPABILITY_FULLBANDWIDTH & \ ~AST_FORMAT_SLINEAR & \ ~AST_FORMAT_ULAW & \ ~AST_FORMAT_ALAW) /* A modem */ #define IAX_CAPABILITY_LOWBANDWIDTH (IAX_CAPABILITY_MEDBANDWIDTH & \ ~AST_FORMAT_MP3 & \ ~AST_FORMAT_ADPCM) #define IAX_CAPABILITY_LOWFREE (IAX_CAPABILITY_LOWBANDWIDTH & \ ~AST_FORMAT_G723_1) static struct io_context *io; static struct sched_context *sched; static int iax_capability = IAX_CAPABILITY_FULLBANDWIDTH; static int iax_dropcount = DEFAULT_DROP; static int use_jitterbuffer = 1; static int iaxdebug = 0; static char accountcode[20]; static int amaflags = 0; static pthread_t netthreadid; #define IAX_STATE_STARTED (1 << 0) #define IAX_STATE_AUTHENTICATED (1 << 1) #define IAX_STATE_TBD (1 << 2) struct iax_context { char context[AST_MAX_EXTENSION]; struct iax_context *next; }; struct iax_user { char name[80]; char secret[80]; char methods[80]; char accountcode[20]; char inkeys[80]; /* Key(s) this user can use to authenticate to us */ int amaflags; int hascallerid; char callerid[AST_MAX_EXTENSION]; struct ast_ha *ha; struct iax_context *contexts; struct iax_user *next; }; struct iax_peer { char name[80]; char username[80]; char secret[80]; char outkey[80]; /* What key we use to talk to this peer */ char context[AST_MAX_EXTENSION]; /* Default context (for transfer really) */ struct sockaddr_in addr; int formats; struct in_addr mask; /* Dynamic Registration fields */ int dynamic; /* If this is a dynamic peer */ struct sockaddr_in defaddr; /* Default address if there is one */ char challenge[80]; /* Challenge used to authenticate the secret */ char methods[80]; char inkeys[80]; /* Key(s) this peer can use to authenticate to us */ int hascallerid; /* Suggested caller id if registering */ char callerid[AST_MAX_EXTENSION]; /* Whether or not to send ANI */ int sendani; int expire; /* Schedule entry for expirey */ int expirey; /* How soon to expire */ int capability; /* Capability */ int delme; /* I need to be deleted */ struct ast_ha *ha; struct iax_peer *next; }; #define REG_STATE_UNREGISTERED 0 #define REG_STATE_REGSENT 1 #define REG_STATE_AUTHSENT 2 #define REG_STATE_REGISTERED 3 #define REG_STATE_REJECTED 4 #define REG_STATE_TIMEOUT 5 #define REG_STATE_NOAUTH 6 #define TRANSFER_NONE 0 #define TRANSFER_BEGIN 1 #define TRANSFER_READY 2 #define TRANSFER_RELEASED 3 #define TRANSFER_PASSTHROUGH 4 struct iax_registry { struct sockaddr_in addr; /* Who we connect to for registration purposes */ char username[80]; char secret[80]; /* Password or key name in []'s */ char random[80]; int expire; /* Sched ID of expiration */ int refresh; /* How often to refresh */ int regstate; int callno; /* Associated call number if applicable */ struct sockaddr_in us; /* Who the server thinks we are */ struct iax_registry *next; }; struct iax_registry *registrations; /* Don't retry more frequently than every 10 ms, or less frequently than every 5 seconds */ #define MIN_RETRY_TIME 10 #define MAX_RETRY_TIME 10000 #define MAX_JITTER_BUFFER 50 /* If we have more than this much excess real jitter buffer, srhink it. */ static int max_jitter_buffer = MAX_JITTER_BUFFER; struct chan_iax_pvt { /* Pipes for communication. pipe[1] belongs to the network thread (write), and pipe[0] belongs to the individual channel (read) */ /* Whether or not we Quelch audio */ int quelch; /* Last received voice format */ int voiceformat; /* Last sent voice format */ int svoiceformat; /* What we are capable of sending */ int capability; /* Last received timestamp */ unsigned int last; /* Last sent timestamp - never send the same timestamp twice in a single call */ unsigned int lastsent; /* Ping time */ unsigned int pingtime; /* Peer Address */ struct sockaddr_in addr; /* Our call number */ int callno; /* Peer callno */ int peercallno; /* Peer selected format */ int peerformat; /* Peer capability */ int peercapability; /* timeval that we base our transmission on */ struct timeval offset; /* timeval that we base our delivery on */ struct timeval rxcore; /* Historical delivery time */ int history[MEMORY_SIZE]; /* Current base jitterbuffer */ int jitterbuffer; /* Current jitter measure */ int jitter; /* Historic jitter value */ int historicjitter; /* LAG */ int lag; /* Error, as discovered by the manager */ int error; /* Owner if we have one */ struct ast_channel *owner; /* What's our state? */ int state; /* Expirey (optional) */ int expirey; /* Next outgoing sequence number */ unsigned short oseqno; /* Next incoming sequence number */ unsigned short iseqno; /* Peer name */ char peer[80]; /* Default Context */ char context[80]; /* Caller ID if available */ char callerid[80]; /* Hidden Caller ID (i.e. ANI) if appropriate */ char ani[80]; /* Whether or not ani should be transmitted in addition to Caller*ID */ int sendani; /* DNID */ char dnid[80]; /* Requested Extension */ char exten[AST_MAX_EXTENSION]; /* Expected Username */ char username[80]; /* Expected Secret */ char secret[80]; /* permitted authentication methods */ char methods[80]; /* MD5 challenge */ char challenge[10]; /* Public keys permitted keys for incoming authentication */ char inkeys[80]; /* Private key for outgoing authentication */ char outkey[80]; /* Preferred language */ char language[80]; /* Associated registry */ struct iax_registry *reg; /* Transferring status */ int transferring; /* Already disconnected */ int alreadygone; /* Who we are IAX transfering to */ struct sockaddr_in transfer; /* What's the new call number for the transfer */ int transfercallno; /* Status of knowledge of peer ADSI capability */ int peeradsicpe; /* Who we are bridged to */ int bridgecallno; int pingid; int lagid; int autoid; char dproot[AST_MAX_EXTENSION]; char accountcode[20]; int amaflags; struct iax_dpcache *dpentries; }; #define DIRECTION_INGRESS 1 #define DIRECTION_OUTGRESS 2 struct ast_iax_frame { /* Actual, isolated frame */ struct ast_frame *f; /* /Our/ call number */ short callno; /* Start of raw frame (outgoing only) */ void *data; /* Length of frame (outgoing only) */ int datalen; /* How many retries so far? */ int retries; /* Outgoing relative timestamp (ms) */ unsigned int ts; /* How long to wait before retrying */ int retrytime; /* Are we received out of order? */ int outoforder; /* Have we been sent at all yet? */ int sentyet; /* Packet sequence number */ int seqno; /* Non-zero if should be sent to transfer peer */ int transfer; /* Non-zero if this is the final message */ int final; /* Ingress or outgres */ int direction; /* Retransmission ID */ int retrans; /* Easy linking */ struct ast_iax_frame *next; struct ast_iax_frame *prev; }; static struct ast_iax_queue { struct ast_iax_frame *head; struct ast_iax_frame *tail; int count; pthread_mutex_t lock; } iaxq; static struct ast_user_list { struct iax_user *users; pthread_mutex_t lock; } userl; static struct ast_peer_list { struct iax_peer *peers; pthread_mutex_t lock; } peerl; /* Extension exists */ #define CACHE_FLAG_EXISTS (1 << 0) /* Extension is non-existant */ #define CACHE_FLAG_NONEXISTANT (1 << 1) /* Extension can exist */ #define CACHE_FLAG_CANEXIST (1 << 2) /* Waiting to hear back response */ #define CACHE_FLAG_PENDING (1 << 3) /* Timed out */ #define CACHE_FLAG_TIMEOUT (1 << 4) /* Request transmitted */ #define CACHE_FLAG_TRANSMITTED (1 << 5) /* Timeout */ #define CACHE_FLAG_UNKNOWN (1 << 6) static struct iax_dpcache { char peercontext[AST_MAX_EXTENSION]; char exten[AST_MAX_EXTENSION]; struct timeval orig; struct timeval expirey; int flags; int callno; int waiters[256]; struct iax_dpcache *next; struct iax_dpcache *peer; /* For linking in peers */ } *dpcache; pthread_mutex_t dpcache_lock; #ifdef DEBUG_SUPPORT void showframe(struct ast_iax_frame *f, struct ast_iax_full_hdr *fhi, int rx, struct sockaddr_in *sin) { char *frames[] = { "(0?)", "DTMF ", "VOICE ", "VIDEO ", "CONTROL", "NULL ", "IAX ", "TEXT ", "IMAGE " }; char *iaxs[] = { "(0?)", "NEW ", "PING ", "PONG ", "ACK ", "HANGUP ", "REJECT ", "ACCEPT ", "AUTHREQ", "AUTHREP", "INVAL ", "LAGRQ ", "LAGRP ", "REGREQ ", "REGAUTH", "REGACK ", "REGREJ ", "REGREL ", "VNAK ", "DPREQ ", "DPREP ", "DIAL ", "TXREQ ", "TXCNT ", "TXACC ", "TXREADY", "TXREL ", "TXREJ ", }; char *cmds[] = { "(0?)", "HANGUP ", "RING ", "RINGING", "ANSWER ", "BUSY ", "TKOFFHK ", "OFFHOOK" }; struct ast_iax_full_hdr *fh; char retries[20]; char class2[20]; char subclass2[20]; char *class; char *subclass; if (f) { fh = f->data; snprintf(retries, sizeof(retries), "%03d", f->retries); } else { strcpy(retries, "N/A"); fh = fhi; } if (!(ntohs(fh->callno) & AST_FLAG_FULL)) { /* Don't mess with mini-frames */ return; } if (fh->type > sizeof(frames)/sizeof(char *)) { snprintf(class2, sizeof(class2), "(%d?)", fh->type); class = class2; } else { class = frames[(int)fh->type]; } if (fh->type == AST_FRAME_DTMF) { sprintf(subclass2, "%c", fh->csub); subclass = subclass2; } else if (fh->type == AST_FRAME_IAX) { if (fh->csub >= sizeof(iaxs)/sizeof(iaxs[0])) { snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub); subclass = subclass2; } else { subclass = iaxs[(int)fh->csub]; } } else if (fh->type == AST_FRAME_CONTROL) { if (fh->csub > sizeof(cmds)/sizeof(char *)) { snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->csub); subclass = subclass2; } else { subclass = cmds[(int)fh->csub]; } } else { snprintf(subclass2, sizeof(subclass2), "%d", fh->csub); subclass = subclass2; } ast_verbose( "%s-Frame Retry[%s] -- Seqno: %2.2d Type: %s Subclass: %s\n", (rx ? "Rx" : "Tx"), retries, ntohs(fh->seqno), class, subclass); fprintf(stderr, " Timestamp: %05dms Callno: %5.5d DCall: %5.5d [%s:%d]\n", ntohl(fh->ts), (short)(ntohs(fh->callno) & ~AST_FLAG_FULL), (short) ntohs(fh->dcallno), inet_ntoa(sin->sin_addr), ntohs(sin->sin_port)); } #endif /* XXX We probably should use a mutex when working with this XXX */ static struct chan_iax_pvt *iaxs[AST_IAX_MAX_CALLS]; static int send_command(struct chan_iax_pvt *, char, int, unsigned int, char *, int, int); static int send_command_immediate(struct chan_iax_pvt *, char, int, unsigned int, char *, int, int); static int send_command_final(struct chan_iax_pvt *, char, int, unsigned int, char *, int, int); static int send_command_transfer(struct chan_iax_pvt *, char, int, unsigned int, char *, int); static unsigned int calc_timestamp(struct chan_iax_pvt *p, unsigned int ts); static int send_ping(void *data) { int callno = (long)data; /* Ping only if it's real, not if it's bridged */ if (iaxs[callno]) { #ifdef BRIDGE_OPTIMIZATION if (iaxs[callno]->bridgecallno < 0) #endif send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_PING, 0, NULL, 0, -1); return 1; } else return 0; } static int send_lagrq(void *data) { int callno = (long)data; /* Ping only if it's real not if it's bridged */ if (iaxs[callno]) { #ifdef BRIDGE_OPTIMIZATION if (iaxs[callno]->bridgecallno < 0) #endif send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_LAGRQ, 0, NULL, 0, -1); return 1; } else return 0; } static unsigned char compress_subclass(int subclass) { int x; int power=-1; /* If it's 128 or smaller, just return it */ if (subclass < AST_FLAG_SC_LOG) return subclass; /* Otherwise find its power */ for (x = 0; x < AST_MAX_SHIFT; x++) { if (subclass & (1 << x)) { if (power > -1) { ast_log(LOG_WARNING, "Can't compress subclass %d\n", subclass); return 0; } else power = x; } } return power | AST_FLAG_SC_LOG; } static int uncompress_subclass(unsigned char csub) { /* If the SC_LOG flag is set, return 2^csub otherwise csub */ if (csub & AST_FLAG_SC_LOG) return 1 << (csub & ~AST_FLAG_SC_LOG & AST_MAX_SHIFT); else return csub; } static struct chan_iax_pvt *new_iax(void) { struct chan_iax_pvt *tmp; tmp = malloc(sizeof(struct chan_iax_pvt)); if (tmp) { memset(tmp, 0, sizeof(struct chan_iax_pvt)); tmp->callno = -1; tmp->peercallno = -1; tmp->transfercallno = -1; tmp->bridgecallno = -1; tmp->pingid = -1; tmp->lagid = -1; tmp->autoid = -1; /* strncpy(tmp->context, context, sizeof(tmp->context)-1); */ strncpy(tmp->exten, "s", sizeof(tmp->exten)-1); } return tmp; } static int get_timelen(struct ast_frame *f) { int timelen=0; switch(f->subclass) { case AST_FORMAT_G723_1: timelen = 30 /* XXX Not necessarily true XXX */; break; case AST_FORMAT_GSM: timelen = 20 * (f->datalen / 20); break; case AST_FORMAT_SLINEAR: timelen = f->datalen / 16; break; case AST_FORMAT_LPC10: timelen = 22; timelen += ((char *)(f->data))[7] & 0x1; break; case AST_FORMAT_ULAW: timelen = f->datalen / 8; break; case AST_FORMAT_ADPCM: timelen = f->datalen / 4; break; default: ast_log(LOG_WARNING, "Don't know how to calculate timelen on %d packets\n", f->subclass); } return timelen; } static int frames = 0; static int iframes = 0; static int oframes = 0; static struct ast_iax_frame *ast_iax_frame_new(int direction) { struct ast_iax_frame *fr; fr = malloc(sizeof(struct ast_iax_frame)); if (fr) { fr->direction = direction; fr->retrans = -1; frames++; if (fr->direction == DIRECTION_INGRESS) iframes++; else oframes++; } return fr; } static void ast_iax_frame_free(struct ast_iax_frame *fr) { if (fr->retrans > -1) ast_sched_del(sched, fr->retrans); if (fr->direction == DIRECTION_INGRESS) iframes--; else if (fr->direction == DIRECTION_OUTGRESS) oframes--; else { ast_log(LOG_WARNING, "Attempt to double free frame detected\n"); CRASH; return; } fr->direction = 0; free(fr); frames--; } static struct ast_iax_frame *iaxfrdup2(struct ast_iax_frame *fr, int ch) { /* Malloc() a copy of a frame */ struct ast_iax_frame *new = ast_iax_frame_new(DIRECTION_INGRESS); if (new) { memcpy(new, fr, sizeof(struct ast_iax_frame)); new->f = ast_frdup(fr->f); /* Copy full header */ if (ch) { memcpy(new->f->data - sizeof(struct ast_iax_full_hdr), fr->f->data - sizeof(struct ast_iax_full_hdr), sizeof(struct ast_iax_full_hdr)); /* Grab new data pointer */ new->data = new->f->data - (fr->f->data - fr->data); } else { new->data = NULL; new->datalen = 0; } new->direction = DIRECTION_INGRESS; new->retrans = -1; } return new; } #define NEW_PREVENT 0 #define NEW_ALLOW 1 #define NEW_FORCE 2 static int match(struct sockaddr_in *sin, short callno, short dcallno, struct chan_iax_pvt *cur) { if ((cur->addr.sin_addr.s_addr == sin->sin_addr.s_addr) && (cur->addr.sin_port == sin->sin_port)) { /* This is the main host */ if ((cur->peercallno == callno) || ((dcallno == cur->callno) && (cur->peercallno) == -1)) { /* That's us. Be sure we keep track of the peer call number */ return 1; } } if ((cur->transfer.sin_addr.s_addr == sin->sin_addr.s_addr) && (cur->transfer.sin_port == sin->sin_port) && (cur->transferring)) { /* We're transferring */ if (dcallno == cur->callno) return 1; } return 0; } static int find_callno(short callno, short dcallno ,struct sockaddr_in *sin, int new) { int res = -1; int x; int start; if (new <= NEW_ALLOW) { /* Look for an existing connection first */ for (x=0;x= NEW_ALLOW)) { /* Create a new one */ start = nextcallno; for (x = (nextcallno + 1) % AST_IAX_MAX_CALLS; iaxs[x] && (x != start); x = (x + 1) % AST_IAX_MAX_CALLS) if (x == start) { ast_log(LOG_WARNING, "Unable to accept more calls\n"); return -1; } iaxs[x] = new_iax(); if (iaxs[x]) { if (option_debug) ast_log(LOG_DEBUG, "Creating new call structure %d\n", x); iaxs[x]->addr.sin_port = sin->sin_port; iaxs[x]->addr.sin_family = sin->sin_family; iaxs[x]->addr.sin_addr.s_addr = sin->sin_addr.s_addr; iaxs[x]->peercallno = callno; iaxs[x]->callno = x; iaxs[x]->pingtime = DEFAULT_RETRY_TIME; iaxs[x]->expirey = expirey; iaxs[x]->pingid = ast_sched_add(sched, ping_time * 1000, send_ping, (void *)x); iaxs[x]->lagid = ast_sched_add(sched, lagrq_time * 1000, send_lagrq, (void *)x); iaxs[x]->amaflags = amaflags; strncpy(iaxs[x]->accountcode, accountcode, sizeof(iaxs[x]->accountcode)-1); } else { ast_log(LOG_WARNING, "Out of resources\n"); return -1; } res = x; nextcallno = x; } return res; } static int iax_send(struct chan_iax_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final); static int do_deliver(void *data) { /* Just deliver the packet by using queueing */ struct ast_iax_frame *fr = data; unsigned int ts; fr->retrans = -1; if (iaxs[fr->callno] && !iaxs[fr->callno]->alreadygone) { if (fr->f->frametype == AST_FRAME_IAX) { /* We have to treat some of these packets specially because they're LAG measurement packets */ if (fr->f->subclass == AST_IAX_COMMAND_LAGRQ) { /* If we got a queued request, build a reply and send it */ fr->f->subclass = AST_IAX_COMMAND_LAGRP; iax_send(iaxs[fr->callno], fr->f, fr->ts, -1, 0, 0, 0); } else if (fr->f->subclass == AST_IAX_COMMAND_LAGRP) { /* This is a reply we've been given, actually measure the difference */ ts = calc_timestamp(iaxs[fr->callno], 0); iaxs[fr->callno]->lag = ts - fr->ts; } } else { if (iaxs[fr->callno]->owner) { ast_queue_frame(iaxs[fr->callno]->owner, fr->f, 1); } } } /* Free the packet */ ast_frfree(fr->f); /* And our iax frame */ ast_iax_frame_free(fr); /* And don't run again */ return 0; } static int handle_error() { /* XXX Ideally we should figure out why an error occured and then abort those rather than continuing to try. Unfortunately, the published interface does not seem to work XXX */ #if 0 struct sockaddr_in *sin; int res; struct msghdr m; struct sock_extended_err e; m.msg_name = NULL; m.msg_namelen = 0; m.msg_iov = NULL; m.msg_control = &e; m.msg_controllen = sizeof(e); m.msg_flags = 0; res = recvmsg(netsocket, &m, MSG_ERRQUEUE); if (res < 0) ast_log(LOG_WARNING, "Error detected, but unable to read error: %s\n", strerror(errno)); else { if (m.msg_controllen) { sin = (struct sockaddr_in *)SO_EE_OFFENDER(&e); if (sin) ast_log(LOG_WARNING, "Receive error from %s\n", inet_ntoa(sin->sin_addr)); else ast_log(LOG_WARNING, "No address detected??\n"); } else { ast_log(LOG_WARNING, "Local error: %s\n", strerror(e.ee_errno)); } } #endif return 0; } static int send_packet(struct ast_iax_frame *f) { int res; if (option_debug) ast_log(LOG_DEBUG, "Sending %d on %d/%d to %s:%d\n", f->ts, f->callno, iaxs[f->callno]->peercallno, inet_ntoa(iaxs[f->callno]->addr.sin_addr), ntohs(iaxs[f->callno]->addr.sin_port)); /* Don't send if there was an error, but return error instead */ if (f->callno < 0) { ast_log(LOG_WARNING, "Call number = %d\n", f->callno); return -1; } if (!iaxs[f->callno]) return -1; if (iaxs[f->callno]->error) return -1; if (f->transfer) { #ifdef DEBUG_SUPPORT if (iaxdebug) showframe(f, NULL, 0, &iaxs[f->callno]->transfer); #endif res = sendto(netsocket, f->data, f->datalen, 0,(struct sockaddr *)&iaxs[f->callno]->transfer, sizeof(iaxs[f->callno]->transfer)); } else { #ifdef DEBUG_SUPPORT if (iaxdebug) showframe(f, NULL, 0, &iaxs[f->callno]->addr); #endif res = sendto(netsocket, f->data, f->datalen, 0,(struct sockaddr *)&iaxs[f->callno]->addr, sizeof(iaxs[f->callno]->addr)); } if (res < 0) { if (option_debug) ast_log(LOG_DEBUG, "Received error: %s\n", strerror(errno)); handle_error(); } else res = 0; return res; } static void iax_destroy(int callno) { struct chan_iax_pvt *pvt = iaxs[callno]; struct ast_iax_frame *cur; if (pvt) { /* No more pings or lagrq's */ if (pvt->pingid > -1) ast_sched_del(sched, pvt->pingid); if (pvt->lagid > -1) ast_sched_del(sched, pvt->lagid); if (pvt->autoid > -1) ast_sched_del(sched, pvt->autoid); pvt->pingid = -1; pvt->lagid = -1; pvt->autoid = -1; /* Already gone */ pvt->alreadygone = 1; if (pvt->owner) { if (ast_pthread_mutex_lock(&pvt->owner->lock)) { ast_log(LOG_WARNING, "Unable to lock channel %s\n", pvt->owner->name); } else if (!pvt->owner || (pvt->owner->pvt->pvt != pvt)) { ast_log(LOG_WARNING, "Something very strange happened...\n"); } else { /* If there's an owner, prod it to give up */ pvt->owner->pvt->pvt = NULL; pvt->owner->_softhangup |= AST_SOFTHANGUP_DEV; ast_queue_hangup(pvt->owner, 0); ast_pthread_mutex_unlock(&pvt->owner->lock); } } iaxs[callno] = NULL; for (cur = iaxq.head; cur ; cur = cur->next) { /* Cancel any pending transmissions */ if (cur->callno == pvt->callno) cur->retries = -1; } if (pvt->reg) { pvt->reg->callno = -1; } free(pvt); } } static int attempt_transmit(void *data) { /* Attempt to transmit the frame to the remote peer */ struct ast_iax_frame *f = data; int freeme=0; /* Make sure this call is still active */ if ((f->callno > -1) && iaxs[f->callno]) { if ((f->retries < 0) /* Already ACK'd */ || (f->retries >= max_retries) /* Too many attempts */) { /* Record an error if we've transmitted too many times */ if (f->retries >= max_retries) { if (f->transfer) { /* Transfer timeout */ send_command(iaxs[f->callno], AST_FRAME_IAX, AST_IAX_COMMAND_TXREJ, 0, NULL, 0, -1); } else if (f->final) { if (f->final) iax_destroy(f->callno); } else { if (iaxs[f->callno]->owner) ast_log(LOG_WARNING, "Max retries exceeded to host %s on %s (type = %d, subclass = %d, ts=%d, seqno=%d)\n", inet_ntoa(iaxs[f->callno]->addr.sin_addr),iaxs[f->callno]->owner->name , f->f->frametype, f->f->subclass, f->ts, f->seqno); iaxs[f->callno]->error = ETIMEDOUT; if (iaxs[f->callno]->owner) { /* Hangup the fd */ ast_softhangup(iaxs[f->callno]->owner, AST_SOFTHANGUP_DEV); ast_queue_hangup(iaxs[f->callno]->owner, 1); } else { if (iaxs[f->callno]->reg) { memset(&iaxs[f->callno]->reg->us, 0, sizeof(iaxs[f->callno]->reg->us)); iaxs[f->callno]->reg->regstate = REG_STATE_TIMEOUT; iaxs[f->callno]->reg->refresh = AST_DEFAULT_REG_EXPIRE; } iax_destroy(f->callno); } } } freeme++; } else { /* Attempt transmission */ send_packet(f); f->retries++; /* Try again later after 10 times as long */ f->retrytime *= 10; if (f->retrytime > MAX_RETRY_TIME) f->retrytime = MAX_RETRY_TIME; /* Transfer messages max out at one second */ if (f->transfer && (f->retrytime > 1000)) f->retrytime = 1000; f->retrans = ast_sched_add(sched, f->retrytime, attempt_transmit, f); } } else { /* Make sure it gets freed */ f->retries = -1; freeme++; } /* Do not try again */ if (freeme) { /* Don't attempt delivery, just remove it from the queue */ ast_pthread_mutex_lock(&iaxq.lock); if (f->prev) f->prev->next = f->next; else iaxq.head = f->next; if (f->next) f->next->prev = f->prev; else iaxq.tail = f->prev; iaxq.count--; ast_pthread_mutex_unlock(&iaxq.lock); /* Free the frame */ ast_frfree(f->f); f->retrans = -1; ast_iax_frame_free(f); } return 0; } static int iax_set_jitter(int fd, int argc, char *argv[]) { if ((argc != 4) && (argc != 5)) return RESULT_SHOWUSAGE; if (argc == 4) { max_jitter_buffer = atoi(argv[3]); if (max_jitter_buffer < 0) max_jitter_buffer = 0; } else { if (argc == 5) { ast_pthread_mutex_lock(&iaxs_lock); if ((atoi(argv[3]) >= 0) && (atoi(argv[3]) < AST_IAX_MAX_CALLS)) { if (iaxs[atoi(argv[3])]) { iaxs[atoi(argv[3])]->jitterbuffer = atoi(argv[4]); if (iaxs[atoi(argv[3])]->jitterbuffer < 0) iaxs[atoi(argv[3])]->jitterbuffer = 0; } else ast_cli(fd, "No such call '%d'\n", atoi(argv[3])); } else ast_cli(fd, "%d is not a valid call number\n", atoi(argv[3])); ast_pthread_mutex_unlock(&iaxs_lock); } } return RESULT_SUCCESS; } static char jitter_usage[] = "Usage: iax set jitter [callid] \n" " If used with a callid, it sets the jitter buffer to the given static\n" "value (until its next calculation). If used without a callid, the value is used\n" "to establish the maximum excess jitter buffer that is permitted before the jitter\n" "buffer size is reduced."; static int iax_show_stats(int fd, int argc, char *argv[]) { struct ast_iax_frame *cur; int cnt = 0, dead=0, final=0; if (argc != 3) return RESULT_SHOWUSAGE; for (cur = iaxq.head; cur ; cur = cur->next) { if (cur->retries < 0) dead++; if (cur->final) final++; cnt++; } ast_cli(fd, " IAX Statistics\n"); ast_cli(fd, "---------------------\n"); ast_cli(fd, "Outstanding frames: %d (%d ingress, %d outgress)\n", frames, iframes, oframes); ast_cli(fd, "Packets in transmit queue: %d dead, %d final, %d total\n", dead, final, cnt); return RESULT_SUCCESS; } static int iax_show_cache(int fd, int argc, char *argv[]) { struct iax_dpcache *dp; char tmp[1024], *pc; int s; int x,y; struct timeval tv; gettimeofday(&tv, NULL); ast_pthread_mutex_lock(&dpcache_lock); dp = dpcache; ast_cli(fd, "%-20.20s %-12.12s %-9.9s %-8.8s %s\n", "Peer/Context", "Exten", "Exp.", "Wait.", "Flags"); while(dp) { s = dp->expirey.tv_sec - tv.tv_sec; strcpy(tmp, ""); if (dp->flags & CACHE_FLAG_EXISTS) strcat(tmp, "EXISTS|"); if (dp->flags & CACHE_FLAG_NONEXISTANT) strcat(tmp, "NONEXISTANT|"); if (dp->flags & CACHE_FLAG_CANEXIST) strcat(tmp, "CANEXIST|"); if (dp->flags & CACHE_FLAG_PENDING) strcat(tmp, "PENDING|"); if (dp->flags & CACHE_FLAG_TIMEOUT) strcat(tmp, "TIMEOUT|"); if (dp->flags & CACHE_FLAG_TRANSMITTED) strcat(tmp, "TRANSMITTED|"); if (dp->flags & CACHE_FLAG_UNKNOWN) strcat(tmp, "UNKNOWN|"); /* Trim trailing pipe */ if (strlen(tmp)) tmp[strlen(tmp) - 1] = '\0'; else strcpy(tmp, "(none)"); y=0; pc = strchr(dp->peercontext, '@'); if (!pc) pc = dp->peercontext; else pc++; for (x=0;xwaiters) / sizeof(dp->waiters[0]); x++) if (dp->waiters[x] > -1) y++; if (s > 0) ast_cli(fd, "%-20.20s %-12.12s %-9d %-8d %s\n", pc, dp->exten, s, y, tmp); else ast_cli(fd, "%-20.20s %-12.12s %-9.9s %-8d %s\n", pc, dp->exten, "(expired)", y, tmp); dp = dp->next; } ast_pthread_mutex_unlock(&dpcache_lock); return RESULT_SUCCESS; } static char show_stats_usage[] = "Usage: iax show stats\n" " Display statistics on IAX channel driver.\n"; static char show_cache_usage[] = "Usage: iax show cache\n" " Display currently cached IAX Dialplan results.\n"; static struct ast_cli_entry cli_set_jitter = { { "iax", "set", "jitter", NULL }, iax_set_jitter, "Sets IAX jitter buffer", jitter_usage }; static struct ast_cli_entry cli_show_stats = { { "iax", "show", "stats", NULL }, iax_show_stats, "Display IAX statistics", show_stats_usage }; static struct ast_cli_entry cli_show_cache = { { "iax", "show", "cache", NULL }, iax_show_cache, "Display IAX cached dialplan", show_cache_usage }; static unsigned int calc_rxstamp(struct chan_iax_pvt *p); #ifdef BRIDGE_OPTIMIZATION static unsigned int calc_fakestamp(struct chan_iax_pvt *from, struct chan_iax_pvt *to, unsigned int ts); static int forward_delivery(struct ast_iax_frame *fr) { struct chan_iax_pvt *p1, *p2; p1 = iaxs[fr->callno]; p2 = iaxs[p1->bridgecallno]; if (!p1) return -1; if (!p2) return -1; /* Fix relative timestamp */ fr->ts = calc_fakestamp(p1, p2, fr->ts); /* Now just send it send on the 2nd one with adjusted timestamp */ return iax_send(p2, fr->f, fr->ts, -1, 0, 0, 0); } #endif static int schedule_delivery(struct ast_iax_frame *fr, int reallydeliver) { int ms,x; int drops[MEMORY_SIZE]; int min, max=0, maxone=0,y,z, match; /* ms is a measure of the "lateness" of the packet relative to the first packet we received, which always has a lateness of 1. */ ms = calc_rxstamp(iaxs[fr->callno]) - fr->ts; if (ms > 32767) { /* What likely happened here is that our counter has circled but we haven't gotten the update from the main packet. We'll just pretend that we did, and update the timestamp appropriately. */ ms -= 65536; } if (ms < -32768) { /* We got this packet out of order. Lets add 65536 to it to bring it into our new time frame */ ms += 65536; } /* Rotate our history queue of "lateness". Don't worry about those initial zeros because the first entry will always be zero */ for (x=0;xcallno]->history[x] = iaxs[fr->callno]->history[x+1]; /* Add a history entry for this one */ iaxs[fr->callno]->history[x] = ms; /* Initialize the minimum to reasonable values. It's too much work to do the same for the maximum, repeatedly */ min=iaxs[fr->callno]->history[0]; for (z=0;z < iax_dropcount + 1;z++) { /* Start very optimistic ;-) */ max=-999999999; for (x=0;xcallno]->history[x]) { /* We have a candidate new maximum value. Make sure it's not in our drop list */ match = 0; for (y=0;!match && (ycallno]->history[x]; maxone = x; } } if (!z) { /* On our first pass, find the minimum too */ if (min > iaxs[fr->callno]->history[x]) min = iaxs[fr->callno]->history[x]; } } #if 1 drops[z] = maxone; #endif } /* Just for reference, keep the "jitter" value, the difference between the earliest and the latest. */ iaxs[fr->callno]->jitter = max - min; /* IIR filter for keeping track of historic jitter, but always increase historic jitter immediately for increase */ if (iaxs[fr->callno]->jitter > iaxs[fr->callno]->historicjitter ) iaxs[fr->callno]->historicjitter = iaxs[fr->callno]->jitter; else iaxs[fr->callno]->historicjitter = GAMMA * (double)iaxs[fr->callno]->jitter + (1-GAMMA) * iaxs[fr->callno]->historicjitter; /* If our jitter buffer is too big (by a significant margin), then we slowly shrink it by about 1 ms each time to avoid letting the change be perceived */ if (max < iaxs[fr->callno]->jitterbuffer - max_jitter_buffer) iaxs[fr->callno]->jitterbuffer -= 2; #if 1 /* Constrain our maximum jitter buffer appropriately */ if (max > min + maxjitterbuffer) { if (option_debug) ast_log(LOG_DEBUG, "Constraining buffer from %d to %d + %d\n", max, min , maxjitterbuffer); max = min + maxjitterbuffer; } #endif /* If our jitter buffer is smaller than our maximum delay, grow the jitter buffer immediately to accomodate it (and a little more). */ if (max > iaxs[fr->callno]->jitterbuffer) iaxs[fr->callno]->jitterbuffer = max /* + ((float)iaxs[fr->callno]->jitter) * 0.1 */; if (option_debug) ast_log(LOG_DEBUG, "min = %d, max = %d, jb = %d, lateness = %d\n", min, max, iaxs[fr->callno]->jitterbuffer, ms); /* Subtract the lateness from our jitter buffer to know how long to wait before sending our packet. */ ms = iaxs[fr->callno]->jitterbuffer - ms; if (!use_jitterbuffer) ms = 0; /* If the caller just wanted us to update, return now */ if (!reallydeliver) return 0; if (ms < 1) { if (option_debug) ast_log(LOG_DEBUG, "Calculated ms is %d\n", ms); /* Don't deliver it more than 4 ms late */ if ((ms > -4) || (fr->f->frametype != AST_FRAME_VOICE)) { do_deliver(fr); } else { if (option_debug) ast_log(LOG_DEBUG, "Dropping voice packet since %d ms is, too old\n", ms); /* Free the packet */ ast_frfree(fr->f); /* And our iax frame */ ast_iax_frame_free(fr); } } else { if (option_debug) ast_log(LOG_DEBUG, "Scheduling delivery in %d ms\n", ms); fr->retrans = ast_sched_add(sched, ms, do_deliver, fr); } return 0; } static int iax_transmit(struct ast_iax_frame *fr) { /* Lock the queue and place this packet at the end */ fr->next = NULL; fr->prev = NULL; /* By setting this to 0, the network thread will send it for us, and queue retransmission if necessary */ fr->sentyet = 0; ast_pthread_mutex_lock(&iaxq.lock); if (!iaxq.head) { /* Empty queue */ iaxq.head = fr; iaxq.tail = fr; } else { /* Double link */ iaxq.tail->next = fr; fr->prev = iaxq.tail; iaxq.tail = fr; } iaxq.count++; ast_pthread_mutex_unlock(&iaxq.lock); /* Wake up the network thread */ pthread_kill(netthreadid, SIGURG); return 0; } static int iax_digit(struct ast_channel *c, char digit) { return send_command(c->pvt->pvt, AST_FRAME_DTMF, digit, 0, NULL, 0, -1); } static int iax_sendtext(struct ast_channel *c, char *text) { return send_command(c->pvt->pvt, AST_FRAME_TEXT, 0, 0, text, strlen(text) + 1, -1); } static int iax_sendimage(struct ast_channel *c, struct ast_frame *img) { return send_command(c->pvt->pvt, AST_FRAME_IMAGE, img->subclass, 0, img->data, img->datalen, -1); } static int iax_sendhtml(struct ast_channel *c, int subclass, char *data, int datalen) { return send_command(c->pvt->pvt, AST_FRAME_HTML, subclass, 0, data, datalen, -1); } static int iax_fixup(struct ast_channel *oldchannel, struct ast_channel *newchan) { struct chan_iax_pvt *pvt = newchan->pvt->pvt; pvt->owner = newchan; return 0; } static int create_addr(struct sockaddr_in *sin, int *capability, int *sendani, char *peer, char *context) { struct hostent *hp; struct iax_peer *p; int found=0; if (sendani) *sendani = 0; sin->sin_family = AF_INET; ast_pthread_mutex_lock(&peerl.lock); p = peerl.peers; while(p) { if (!strcasecmp(p->name, peer)) { found++; if (capability) *capability = p->capability; if (p->addr.sin_addr.s_addr || p->defaddr.sin_addr.s_addr) { if (sendani) *sendani = p->sendani; if (context) strncpy(context, p->context, AST_MAX_EXTENSION - 1); if (p->addr.sin_addr.s_addr) { sin->sin_addr = p->addr.sin_addr; sin->sin_port = p->addr.sin_port; } else { sin->sin_addr = p->defaddr.sin_addr; sin->sin_port = p->defaddr.sin_port; } break; } } p = p->next; } ast_pthread_mutex_unlock(&peerl.lock); if (!p && !found) { hp = gethostbyname(peer); if (hp) { memcpy(&sin->sin_addr, hp->h_addr, sizeof(sin->sin_addr)); sin->sin_port = htons(AST_DEFAULT_IAX_PORTNO); return 0; } else { ast_log(LOG_WARNING, "No such host: %s\n", peer); return -1; } } else if (!p) return -1; else return 0; } static int iax_call(struct ast_channel *c, char *dest, int timeout) { struct sockaddr_in sin; char host[256]; char *rdest; char *rcontext; char *username; char *secret = NULL; char *hname; char requeststr[256] = ""; char myrdest [5] = "s"; char context[AST_MAX_EXTENSION] =""; char *portno = NULL; struct chan_iax_pvt *p = c->pvt->pvt; if ((c->_state != AST_STATE_DOWN) && (c->_state != AST_STATE_RESERVED)) { ast_log(LOG_WARNING, "Line is already in use (%s)?\n", c->name); return -1; } strncpy(host, dest, sizeof(host)-1); strtok(host, "/"); /* If no destination extension specified, use 's' */ rdest = strtok(NULL, "/"); if (!rdest) rdest = myrdest; strtok(rdest, "@"); rcontext = strtok(NULL, "@"); strtok(host, "@"); username = strtok(NULL, "@"); if (username) { /* Really the second argument is the host, not the username */ hname = username; username = host; } else { hname = host; } if (username) { username = strtok(username, ":"); secret = strtok(NULL, ":"); } if (strtok(hname, ":")) { strtok(hname, ":"); portno = strtok(hname, ":"); } if (create_addr(&sin, NULL, NULL, hname, context)) { ast_log(LOG_WARNING, "No address associated with '%s'\n", hname); return -1; } /* Keep track of the context for outgoing calls too */ strncpy(c->context, context, sizeof(c->context) - 1); if (portno) { sin.sin_port = htons(atoi(portno)); } /* Now we build our request string */ #define MYSNPRINTF snprintf(requeststr + strlen(requeststr), sizeof(requeststr) - strlen(requeststr), #define MYSNPRINTF2 snprintf(requeststr + strlen(requeststr), reqsize - strlen(requeststr), MYSNPRINTF "exten=%s;", rdest); if (c->callerid) MYSNPRINTF "callerid=%s;", c->callerid); if (p->sendani && c->ani) MYSNPRINTF "ani=%s;", c->ani); if (c->language && strlen(c->language)) MYSNPRINTF "language=%s;", c->language); if (c->dnid) MYSNPRINTF "dnid=%s;", c->dnid); if (rcontext) MYSNPRINTF "context=%s;", rcontext); if (username) MYSNPRINTF "username=%s;", username); if (secret) { if (secret[0] == '[') { /* This is an RSA key, not a normal secret */ strncpy(p->outkey, secret + 1, sizeof(p->secret)-1); if (strlen(p->outkey)) { p->outkey[strlen(p->outkey) - 1] = '\0'; } } else strncpy(p->secret, secret, sizeof(p->secret)-1); } MYSNPRINTF "formats=%d;", c->nativeformats); MYSNPRINTF "capability=%d;", p->capability); MYSNPRINTF "version=%d;", AST_IAX_PROTO_VERSION); MYSNPRINTF "adsicpe=%d;", c->adsicpe); /* Trim the trailing ";" */ if (strlen(requeststr)) requeststr[strlen(requeststr) - 1] = '\0'; /* Transmit the string in a "NEW" request */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Calling using options '%s'\n", requeststr); send_command((struct chan_iax_pvt *)c->pvt->pvt, AST_FRAME_IAX, AST_IAX_COMMAND_NEW, 0, requeststr, strlen(requeststr) + 1, -1); ast_setstate(c, AST_STATE_RINGING); return 0; } static int iax_predestroy(struct chan_iax_pvt *pvt) { struct ast_channel *c; if (!pvt->alreadygone) { /* No more pings or lagrq's */ if (pvt->pingid > -1) ast_sched_del(sched, pvt->pingid); if (pvt->lagid > -1) ast_sched_del(sched, pvt->lagid); if (pvt->autoid > -1) ast_sched_del(sched, pvt->autoid); pvt->pingid = -1; pvt->lagid = -1; pvt->autoid = -1; pvt->alreadygone = 1; } c = pvt->owner; if (c) { c->_softhangup |= AST_SOFTHANGUP_DEV; c->pvt->pvt = NULL; pvt->owner = NULL; ast_pthread_mutex_lock(&usecnt_lock); usecnt--; if (usecnt < 0) ast_log(LOG_WARNING, "Usecnt < 0???\n"); ast_pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); } return 0; } static int iax_hangup(struct ast_channel *c) { struct chan_iax_pvt *pvt = c->pvt->pvt; int alreadygone; if (pvt) { ast_log(LOG_DEBUG, "We're hanging up %s now...\n", c->name); alreadygone = pvt->alreadygone; /* Send the hangup unless we have had a transmission error or are already gone */ if (!pvt->error && !alreadygone) send_command_final(pvt, AST_FRAME_IAX, AST_IAX_COMMAND_HANGUP, 0, NULL, 0, -1); /* Explicitly predestroy it */ iax_predestroy(pvt); /* If we were already gone to begin with, destroy us now */ if (alreadygone) { iax_destroy(pvt->callno); } } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Hungup '%s'\n", c->name); return 0; } static int iax_setoption(struct ast_channel *c, int option, void *data, int datalen) { struct ast_option_header *h; int res; h = malloc(datalen + sizeof(struct ast_option_header)); if (h) { h->flag = AST_OPTION_FLAG_REQUEST; h->option = htons(option); memcpy(h->data, data, datalen); res = send_command((struct chan_iax_pvt *)c->pvt->pvt, AST_FRAME_CONTROL, AST_CONTROL_OPTION, 0, (char *)h, datalen + sizeof(struct ast_option_header), -1); free(h); return res; } else ast_log(LOG_WARNING, "Out of memory\n"); return -1; } static struct ast_frame *iax_read(struct ast_channel *c) { static struct ast_frame f = { AST_FRAME_NULL, }; ast_log(LOG_NOTICE, "I should never be called!\n"); return &f; } static int iax_start_transfer(struct ast_channel *c0, struct ast_channel *c1) { int res; char req0[256]; char req1[256]; struct chan_iax_pvt *p0 = c0->pvt->pvt; struct chan_iax_pvt *p1 = c1->pvt->pvt; snprintf(req0, sizeof(req0), "remip=%s;remport=%d;remcall=%d;", inet_ntoa(p1->addr.sin_addr), ntohs(p1->addr.sin_port), p1->peercallno); snprintf(req1, sizeof(req1), "remip=%s;remport=%d;remcall=%d;", inet_ntoa(p0->addr.sin_addr), ntohs(p0->addr.sin_port), p0->peercallno); res = send_command(p0, AST_FRAME_IAX, AST_IAX_COMMAND_TXREQ, 0, req0, strlen(req0) + 1, -1); if (res) return -1; res = send_command(p1, AST_FRAME_IAX, AST_IAX_COMMAND_TXREQ, 0, req1, strlen(req1) + 1, -1); if (res) return -1; p0->transferring = TRANSFER_BEGIN; p1->transferring = TRANSFER_BEGIN; return 0; } static int iax_bridge(struct ast_channel *c0, struct ast_channel *c1, int flags, struct ast_frame **fo, struct ast_channel **rc) { struct ast_channel *cs[3]; struct ast_channel *who; int to = -1; int res; int transferstarted=0; struct ast_frame *f; struct chan_iax_pvt *p0 = c0->pvt->pvt; struct chan_iax_pvt *p1 = c1->pvt->pvt; /* Put them in native bridge mode */ p0->bridgecallno = p1->callno; p1->bridgecallno = p0->callno; /* If not, try to bridge until we can execute a transfer, if we can */ cs[0] = c0; cs[1] = c1; for (/* ever */;;) { /* Check in case we got masqueraded into */ if ((c0->type != type) || (c1->type != type)) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Can't masquerade, we're different...\n"); return -2; } if (!transferstarted) { /* Try the transfer */ if (iax_start_transfer(c0, c1)) ast_log(LOG_WARNING, "Unable to start the transfer\n"); transferstarted = 1; } if ((p0->transferring == TRANSFER_RELEASED) && (p1->transferring == TRANSFER_RELEASED)) { /* Call has been transferred. We're no longer involved */ sleep(1); c0->_softhangup |= AST_SOFTHANGUP_DEV; c1->_softhangup |= AST_SOFTHANGUP_DEV; *fo = NULL; *rc = c0; res = 0; break; } to = 1000; who = ast_waitfor_n(cs, 2, &to); if (!who) { continue; } f = ast_read(who); if (!f) { *fo = NULL; *rc = who; res = 0; break; } if ((f->frametype == AST_FRAME_CONTROL) && !(flags & AST_BRIDGE_IGNORE_SIGS)) { *fo = f; *rc = who; res = 0; break; } if ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_TEXT) || (f->frametype == AST_FRAME_VIDEO) || (f->frametype == AST_FRAME_IMAGE) || (f->frametype == AST_FRAME_DTMF)) { if ((f->frametype == AST_FRAME_DTMF) && (flags & (AST_BRIDGE_DTMF_CHANNEL_0 | AST_BRIDGE_DTMF_CHANNEL_1))) { if ((who == c0)) { if ((flags & AST_BRIDGE_DTMF_CHANNEL_0)) { *rc = c0; *fo = f; /* Take out of conference mode */ res = 0; break; } else goto tackygoto; } else if ((who == c1)) { if (flags & AST_BRIDGE_DTMF_CHANNEL_1) { *rc = c1; *fo = f; res = 0; break; } else goto tackygoto; } } else { #if 0 ast_log(LOG_DEBUG, "Read from %s\n", who->name); if (who == last) ast_log(LOG_DEBUG, "Servicing channel %s twice in a row?\n", last->name); last = who; #endif tackygoto: if (who == c0) ast_write(c1, f); else ast_write(c0, f); } ast_frfree(f); } else ast_frfree(f); /* Swap who gets priority */ cs[2] = cs[0]; cs[0] = cs[1]; cs[1] = cs[2]; } p0->bridgecallno = -1; p1->bridgecallno = -1; return res; } static int iax_answer(struct ast_channel *c) { struct chan_iax_pvt *pvt = c->pvt->pvt; if (option_debug) ast_log(LOG_DEBUG, "Answering\n"); return send_command(pvt, AST_FRAME_CONTROL, AST_CONTROL_ANSWER, 0, NULL, 0, -1); } static int iax_indicate(struct ast_channel *c, int condition) { struct chan_iax_pvt *pvt = c->pvt->pvt; if (option_debug) ast_log(LOG_DEBUG, "Indicating condition %d\n", condition); return send_command(pvt, AST_FRAME_CONTROL, condition, 0, NULL, 0, -1); } static int iax_write(struct ast_channel *c, struct ast_frame *f); static int iax_getpeername(struct sockaddr_in sin, char *host, int len) { struct iax_peer *peer; int res = 0; ast_pthread_mutex_lock(&peerl.lock); peer = peerl.peers; while(peer) { if ((peer->addr.sin_addr.s_addr == sin.sin_addr.s_addr) && (peer->addr.sin_port == sin.sin_port)) { strncpy(host, peer->name, len-1); res = 1; break; } peer = peer->next; } ast_pthread_mutex_unlock(&peerl.lock); return res; } static struct ast_channel *ast_iax_new(struct chan_iax_pvt *i, int state, int capability) { char host[256]; struct ast_channel *tmp; tmp = ast_channel_alloc(1); if (tmp) { if (!iax_getpeername(i->addr, host, sizeof(host))) snprintf(host, sizeof(host), "%s:%d", inet_ntoa(i->addr.sin_addr), ntohs(i->addr.sin_port)); if (strlen(i->username)) snprintf(tmp->name, sizeof(tmp->name), "IAX[%s@%s]/%d", i->username, host, i->callno); else snprintf(tmp->name, sizeof(tmp->name), "IAX[%s]/%d", host, i->callno); tmp->type = type; /* We can support any format by default, until we get restricted */ tmp->nativeformats = capability; tmp->readformat = 0; tmp->writeformat = 0; tmp->pvt->pvt = i; tmp->pvt->send_digit = iax_digit; tmp->pvt->send_text = iax_sendtext; tmp->pvt->send_image = iax_sendimage; tmp->pvt->send_html = iax_sendhtml; tmp->pvt->call = iax_call; tmp->pvt->hangup = iax_hangup; tmp->pvt->answer = iax_answer; tmp->pvt->read = iax_read; tmp->pvt->write = iax_write; tmp->pvt->indicate = iax_indicate; tmp->pvt->setoption = iax_setoption; tmp->pvt->bridge = iax_bridge; if (strlen(i->callerid)) tmp->callerid = strdup(i->callerid); if (strlen(i->ani)) tmp->ani = strdup(i->ani); if (strlen(i->language)) strncpy(tmp->language, i->language, sizeof(tmp->language)-1); if (strlen(i->dnid)) tmp->dnid = strdup(i->dnid); if (strlen(i->accountcode)) strncpy(tmp->accountcode, i->accountcode, sizeof(tmp->accountcode)-1); if (i->amaflags) tmp->amaflags = i->amaflags; strncpy(tmp->context, i->context, sizeof(tmp->context)-1); strncpy(tmp->exten, i->exten, sizeof(tmp->exten)-1); tmp->adsicpe = i->peeradsicpe; tmp->pvt->fixup = iax_fixup; i->owner = tmp; i->capability = capability; ast_setstate(tmp, state); ast_pthread_mutex_lock(&usecnt_lock); usecnt++; ast_pthread_mutex_unlock(&usecnt_lock); ast_update_use_count(); if (state != AST_STATE_DOWN) { if (ast_pbx_start(tmp)) { ast_log(LOG_WARNING, "Unable to start PBX on %s\n", tmp->name); ast_hangup(tmp); tmp = NULL; } } } return tmp; } static unsigned int calc_timestamp(struct chan_iax_pvt *p, unsigned int ts) { struct timeval tv; unsigned int ms; if (!p->offset.tv_sec && !p->offset.tv_usec) gettimeofday(&p->offset, NULL); /* If the timestamp is specified, just send it as is */ if (ts) return ts; gettimeofday(&tv, NULL); ms = (tv.tv_sec - p->offset.tv_sec) * 1000 + (tv.tv_usec - p->offset.tv_usec) / 1000; /* We never send the same timestamp twice, so fudge a little if we must */ if (ms <= p->lastsent) ms = p->lastsent + 1; p->lastsent = ms; return ms; } #ifdef BRIDGE_OPTIMIZATION static unsigned int calc_fakestamp(struct chan_iax_pvt *p1, struct chan_iax_pvt *p2, unsigned int fakets) { int ms; /* Receive from p1, send to p2 */ /* Setup rxcore if necessary on outgoing channel */ if (!p1->rxcore.tv_sec && !p1->rxcore.tv_usec) gettimeofday(&p1->rxcore, NULL); /* Setup txcore if necessary on outgoing channel */ if (!p2->offset.tv_sec && !p2->offset.tv_usec) gettimeofday(&p2->offset, NULL); /* Now, ts is the timestamp of the original packet in the orignal context. Adding rxcore to it gives us when we would want the packet to be delivered normally. Subtracting txcore of the outgoing channel gives us what we'd expect */ ms = (p1->rxcore.tv_sec - p2->offset.tv_sec) * 1000 + (p1->rxcore.tv_usec - p1->offset.tv_usec) / 1000; fakets += ms; if (fakets <= p2->lastsent) fakets = p2->lastsent + 1; p2->lastsent = fakets; return fakets; } #endif static unsigned int calc_rxstamp(struct chan_iax_pvt *p) { /* Returns where in "receive time" we are */ struct timeval tv; unsigned int ms; /* Setup rxcore if necessary */ if (!p->rxcore.tv_sec && !p->rxcore.tv_usec) gettimeofday(&p->rxcore, NULL); gettimeofday(&tv, NULL); ms = (tv.tv_sec - p->rxcore.tv_sec) * 1000 + (tv.tv_usec - p->rxcore.tv_usec) / 1000; return ms; } static int iax_send(struct chan_iax_pvt *pvt, struct ast_frame *f, unsigned int ts, int seqno, int now, int transfer, int final) { /* Queue a packet for delivery on a given private structure. Use "ts" for timestamp, or calculate if ts is 0. Send immediately without retransmission or delayed, with retransmission */ struct ast_iax_full_hdr *fh; struct ast_iax_mini_hdr *mh; struct ast_iax_frame *fr, fr2; int res; unsigned int lastsent; /* Allocate an ast_iax_frame */ if (now) fr = &fr2; else fr = ast_iax_frame_new(DIRECTION_OUTGRESS); if (!fr) { ast_log(LOG_WARNING, "Out of memory\n"); return -1; } if (!pvt) { ast_log(LOG_WARNING, "No private structure for packet (%d)?\n", fr->callno); if (!now) ast_iax_frame_free(fr); return -1; } /* Isolate our frame for transmission */ fr->f = ast_frdup(f); if (!fr->f) { ast_log(LOG_WARNING, "Out of memory\n"); if (!now) ast_iax_frame_free(fr); return -1; } if (fr->f->offset < sizeof(struct ast_iax_full_hdr)) { ast_log(LOG_WARNING, "Packet from '%s' not friendly\n", fr->f->src); free(fr); return -1; } lastsent = pvt->lastsent; fr->ts = calc_timestamp(pvt, ts); if (!fr->ts) { ast_log(LOG_WARNING, "timestamp is 0?\n"); if (!now) ast_iax_frame_free(fr); return -1; } fr->callno = pvt->callno; fr->transfer = transfer; fr->final = final; if (((fr->ts & 0xFFFF0000L) != (lastsent & 0xFFFF0000L)) /* High two bits of timestamp differ */ || (fr->f->frametype != AST_FRAME_VOICE) /* or not a voice frame */ || (fr->f->subclass != pvt->svoiceformat) /* or new voice format */ ) { /* We need a full frame */ if (seqno > -1) fr->seqno = seqno; else fr->seqno = pvt->oseqno++; fh = (struct ast_iax_full_hdr *)(fr->f->data - sizeof(struct ast_iax_full_hdr)); fh->callno = htons(fr->callno | AST_FLAG_FULL); fh->ts = htonl(fr->ts); fh->seqno = htons(fr->seqno); fh->type = fr->f->frametype & 0xFF; fh->csub = compress_subclass(fr->f->subclass); if (transfer) { fh->dcallno = htons(pvt->transfercallno); } else fh->dcallno = htons(pvt->peercallno); fr->datalen = fr->f->datalen + sizeof(struct ast_iax_full_hdr); fr->data = fh; fr->retries = 0; /* Retry after 2x the ping time has passed */ fr->retrytime = pvt->pingtime * 2; if (fr->retrytime < MIN_RETRY_TIME) fr->retrytime = MIN_RETRY_TIME; if (fr->retrytime > MAX_RETRY_TIME) fr->retrytime = MAX_RETRY_TIME; /* Acks' don't get retried */ if ((f->frametype == AST_FRAME_IAX) && (f->subclass == AST_IAX_COMMAND_ACK)) fr->retries = -1; if (f->frametype == AST_FRAME_VOICE) { pvt->svoiceformat = f->subclass; } if (now) { res = send_packet(fr); ast_frfree(fr->f); } else res = iax_transmit(fr); } else { /* Mini-frames have no sequence number */ fr->seqno = -1; /* Mini frame will do */ mh = (struct ast_iax_mini_hdr *)(fr->f->data - sizeof(struct ast_iax_mini_hdr)); mh->callno = htons(fr->callno); mh->ts = htons(fr->ts & 0xFFFF); fr->datalen = fr->f->datalen + sizeof(struct ast_iax_mini_hdr); fr->data = mh; fr->retries = -1; if (now) { res = send_packet(fr); ast_frfree(fr->f); } else res = iax_transmit(fr); } return res; } static int iax_show_users(int fd, int argc, char *argv[]) { #define FORMAT "%-15.15s %-15.15s %-15.15s %-15.15s %-5.5s\n" struct iax_user *user; if (argc != 3) return RESULT_SHOWUSAGE; ast_pthread_mutex_lock(&userl.lock); ast_cli(fd, FORMAT, "Username", "Secret", "Authen", "Def.Context", "A/C"); for(user=userl.users;user;user=user->next) { ast_cli(fd, FORMAT, user->name, user->secret, user->methods, user->contexts ? user->contexts->context : context, user->ha ? "Yes" : "No"); } ast_pthread_mutex_unlock(&userl.lock); return RESULT_SUCCESS; #undef FORMAT } static int iax_show_peers(int fd, int argc, char *argv[]) { #define FORMAT2 "%-15.15s %-15.15s %-15.15s %s %-15.15s %s\n" #define FORMAT "%-15.15s %-15.15s %-15.15s %s %-15.15s %d\n" struct iax_peer *peer; if (argc != 3) return RESULT_SHOWUSAGE; ast_pthread_mutex_lock(&peerl.lock); ast_cli(fd, FORMAT2, "Name", "Username", "Host", " ", "Mask", "Port"); for (peer = peerl.peers;peer;peer = peer->next) { char nm[20]; strncpy(nm, inet_ntoa(peer->mask), sizeof(nm)-1); ast_cli(fd, FORMAT, peer->name, peer->username ? peer->username : "(Any)", peer->addr.sin_addr.s_addr ? inet_ntoa(peer->addr.sin_addr) : "(Unspecified)", peer->dynamic ? "(D)" : " ", nm, ntohs(peer->addr.sin_port)); } ast_pthread_mutex_unlock(&peerl.lock); return RESULT_SUCCESS; #undef FORMAT #undef FORMAT2 } static char *regstate2str(int regstate) { switch(regstate) { case REG_STATE_UNREGISTERED: return "Unregistered"; case REG_STATE_REGSENT: return "Request Sent"; case REG_STATE_AUTHSENT: return "Auth. Sent"; case REG_STATE_REGISTERED: return "Registered"; case REG_STATE_REJECTED: return "Rejected"; case REG_STATE_TIMEOUT: return "Timeout"; case REG_STATE_NOAUTH: return "No Authentication"; default: return "Unknown"; } } static int iax_show_registry(int fd, int argc, char *argv[]) { #define FORMAT2 "%-20.20s %-10.10s %-20.20s %8.8s %s\n" #define FORMAT "%-20.20s %-10.10s %-20.20s %8d %s\n" struct iax_registry *reg; char host[80]; char perceived[80]; if (argc != 3) return RESULT_SHOWUSAGE; ast_pthread_mutex_lock(&peerl.lock); ast_cli(fd, FORMAT2, "Host", "Username", "Perceived", "Refresh", "State"); for (reg = registrations;reg;reg = reg->next) { snprintf(host, sizeof(host), "%s:%d", inet_ntoa(reg->addr.sin_addr), ntohs(reg->addr.sin_port)); if (reg->us.sin_addr.s_addr) snprintf(perceived, sizeof(perceived), "%s:%d", inet_ntoa(reg->us.sin_addr), ntohs(reg->us.sin_port)); else strcpy(perceived, ""); ast_cli(fd, FORMAT, host, reg->username, perceived, reg->refresh, regstate2str(reg->regstate)); } ast_pthread_mutex_unlock(&peerl.lock); return RESULT_SUCCESS; #undef FORMAT #undef FORMAT2 } static int iax_show_channels(int fd, int argc, char *argv[]) { #define FORMAT2 "%-15.15s %-10.10s %-11.11s %-11.11s %-7.7s %-6.6s %s\n" #define FORMAT "%-15.15s %-10.10s %5.5d/%5.5d %5.5d/%5.5d %-5.5dms %-4.4dms %d\n" int x; if (argc != 3) return RESULT_SHOWUSAGE; ast_pthread_mutex_lock(&iaxs_lock); ast_cli(fd, FORMAT2, "Peer", "Username", "ID (Lo/Rem)", "Seq (Tx/Rx)", "Lag", "Jitter", "Format"); for (x=0;xaddr.sin_addr), strlen(iaxs[x]->username) ? iaxs[x]->username : "(None)", iaxs[x]->callno, iaxs[x]->peercallno, iaxs[x]->oseqno, iaxs[x]->iseqno, iaxs[x]->lag, iaxs[x]->jitter, iaxs[x]->voiceformat); ast_pthread_mutex_unlock(&iaxs_lock); return RESULT_SUCCESS; #undef FORMAT #undef FORMAT2 } static int iax_do_debug(int fd, int argc, char *argv[]) { if (argc != 2) return RESULT_SHOWUSAGE; iaxdebug = 1; ast_cli(fd, "IAX Debugging Enabled\n"); return RESULT_SUCCESS; } static int iax_no_debug(int fd, int argc, char *argv[]) { if (argc != 3) return RESULT_SHOWUSAGE; iaxdebug = 0; ast_cli(fd, "IAX Debugging Disabled\n"); return RESULT_SUCCESS; } static char show_users_usage[] = "Usage: iax show users\n" " Lists all users known to the IAX (Inter-Asterisk eXchange) subsystem.\n"; static char show_channels_usage[] = "Usage: iax show channels\n" " Lists all currently active IAX channels.\n"; static char show_peers_usage[] = "Usage: iax show peers\n" " Lists all known IAX peers.\n"; static char show_reg_usage[] = "Usage: iax show registry\n" " Lists all registration requests and status.\n"; #ifdef DEBUG_SUPPORT static char debug_usage[] = "Usage: iax debug\n" " Enables dumping of IAX packets for debugging purposes\n"; static char no_debug_usage[] = "Usage: iax no debug\n" " Disables dumping of IAX packets for debugging purposes\n"; #endif static struct ast_cli_entry cli_show_users = { { "iax", "show", "users", NULL }, iax_show_users, "Show defined IAX users", show_users_usage }; static struct ast_cli_entry cli_show_channels = { { "iax", "show", "channels", NULL }, iax_show_channels, "Show active IAX channels", show_channels_usage }; static struct ast_cli_entry cli_show_peers = { { "iax", "show", "peers", NULL }, iax_show_peers, "Show defined IAX peers", show_peers_usage }; static struct ast_cli_entry cli_show_registry = { { "iax", "show", "registry", NULL }, iax_show_registry, "Show IAX registration status", show_reg_usage }; static struct ast_cli_entry cli_debug = { { "iax", "debug", NULL }, iax_do_debug, "Enable IAX debugging", debug_usage }; static struct ast_cli_entry cli_no_debug = { { "iax", "no", "debug", NULL }, iax_no_debug, "Disable IAX debugging", no_debug_usage }; static int iax_write(struct ast_channel *c, struct ast_frame *f) { struct chan_iax_pvt *i = c->pvt->pvt; if (!i) return -1; /* If there's an outstanding error, return failure now */ if (i->error) { ast_log(LOG_DEBUG, "Write error: %s\n", strerror(errno)); return -1; } /* If it's already gone, just return */ if (i->alreadygone) return 0; /* Don't waste bandwidth sending null frames */ if (f->frametype == AST_FRAME_NULL) return 0; /* If we're quelching voice, don't bother sending it */ if ((f->frametype == AST_FRAME_VOICE) && i->quelch) return 0; /* Simple, just queue for transmission */ return iax_send(i, f, 0, -1, 0, 0, 0); } static int __send_command(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno, int now, int transfer, int final) { struct ast_frame f; f.frametype = type; f.subclass = command; f.datalen = datalen; f.timelen = 0; f.mallocd = 0; f.offset = 0; f.src = __FUNCTION__; f.data = data; return iax_send(i, &f, ts, seqno, now, transfer, final); } static int send_command(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno) { return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 0); } #ifdef BRIDGE_OPTIMIZATION static int forward_command(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno) { return __send_command(iaxs[i->bridgecallno], type, command, ts, data, datalen, seqno, 0, 0, 0); } #endif static int send_command_final(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno) { iax_predestroy(i); return __send_command(i, type, command, ts, data, datalen, seqno, 0, 0, 1); } static int send_command_immediate(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen, int seqno) { return __send_command(i, type, command, ts, data, datalen, seqno, 1, 0, 0); } static int send_command_transfer(struct chan_iax_pvt *i, char type, int command, unsigned int ts, char *data, int datalen) { return __send_command(i, type, command, ts, data, datalen, 0, 0, 1, 0); } static int apply_context(struct iax_context *con, char *context) { while(con) { if (!strcmp(con->context, context)) return -1; con = con->next; } return 0; } static int iax_getformats(int callno, char *orequest) { char *var, *value; char request[256]; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "formats")) { iaxs[callno]->peerformat = atoi(value); } else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } return 0; } static int check_access(int callno, struct sockaddr_in *sin, char *orequest, int requestl) { /* Start pessimistic */ int res = -1; int version = 1; char *var, *value; struct iax_user *user; char request[256]; int gotcapability=0; strncpy(request, orequest, sizeof(request)-1); if (!iaxs[callno]) return res; var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "exten")) strncpy(iaxs[callno]->exten, value, sizeof(iaxs[callno]->exten)-1); else if (!strcmp(var, "callerid")) strncpy(iaxs[callno]->callerid, value, sizeof(iaxs[callno]->callerid)-1); else if (!strcmp(var, "ani")) strncpy(iaxs[callno]->ani, value, sizeof(iaxs[callno]->ani) - 1); else if (!strcmp(var, "dnid")) strncpy(iaxs[callno]->dnid, value, sizeof(iaxs[callno]->dnid)-1); else if (!strcmp(var, "context")) strncpy(iaxs[callno]->context, value, sizeof(iaxs[callno]->context)-1); else if (!strcmp(var, "language")) strncpy(iaxs[callno]->language, value, sizeof(iaxs[callno]->language)-1); else if (!strcmp(var, "username")) strncpy(iaxs[callno]->username, value, sizeof(iaxs[callno]->username)-1); else if (!strcmp(var, "formats")) iaxs[callno]->peerformat = atoi(value); else if (!strcmp(var, "adsicpe")) iaxs[callno]->peeradsicpe = atoi(value); else if (!strcmp(var, "capability")) { gotcapability = 1; iaxs[callno]->peercapability = atoi(value); } else if (!strcmp(var, "version")) version = atoi(value); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } if (!gotcapability) iaxs[callno]->peercapability = iaxs[callno]->peerformat; if (version > AST_IAX_PROTO_VERSION) { ast_log(LOG_WARNING, "Peer '%s' has too new a protocol version (%d) for me\n", inet_ntoa(sin->sin_addr), version); return res; } ast_pthread_mutex_lock(&userl.lock); /* Search the userlist for a compatible entry, and fill in the rest */ user = userl.users; while(user) { if ((!strlen(iaxs[callno]->username) || /* No username specified */ !strcmp(iaxs[callno]->username, user->name)) /* Or this username specified */ && ast_apply_ha(user->ha, sin) /* Access is permitted from this IP */ && (!strlen(iaxs[callno]->context) || /* No context specified */ apply_context(user->contexts, iaxs[callno]->context))) { /* Context is permitted */ /* We found our match (use the first) */ /* Store the requested username if not specified */ if (!strlen(iaxs[callno]->username)) strncpy(iaxs[callno]->username, user->name, sizeof(iaxs[callno]->username)-1); /* And use the default context */ if (!strlen(iaxs[callno]->context)) { if (user->contexts) strncpy(iaxs[callno]->context, user->contexts->context, sizeof(iaxs[callno]->context)-1); else strncpy(iaxs[callno]->context, context, sizeof(iaxs[callno]->context)-1); } /* Copy the secret */ strncpy(iaxs[callno]->secret, user->secret, sizeof(iaxs[callno]->secret)-1); /* And any input keys */ strncpy(iaxs[callno]->inkeys, user->inkeys, sizeof(iaxs[callno]->inkeys)); /* And the permitted authentication methods */ strncpy(iaxs[callno]->methods, user->methods, sizeof(iaxs[callno]->methods)-1); /* If they have callerid, override the given caller id. Always store the ANI */ if (strlen(iaxs[callno]->callerid)) { if (user->hascallerid) strncpy(iaxs[callno]->callerid, user->callerid, sizeof(iaxs[callno]->callerid)-1); strncpy(iaxs[callno]->ani, user->callerid, sizeof(iaxs[callno]->ani)-1); } if (strlen(user->accountcode)) strncpy(iaxs[callno]->accountcode, user->accountcode, sizeof(iaxs[callno]->accountcode)-1); if (user->amaflags) iaxs[callno]->amaflags = user->amaflags; res = 0; break; } user = user->next; } ast_pthread_mutex_unlock(&userl.lock); return res; } static int raw_hangup(struct sockaddr_in *sin, short src, short dst) { struct ast_iax_full_hdr fh; fh.callno = htons(src | AST_FLAG_FULL); fh.dcallno = htons(dst); fh.ts = 0; fh.seqno = 0; fh.type = AST_FRAME_IAX; fh.csub = compress_subclass(AST_IAX_COMMAND_INVAL); #if 0 if (option_debug) #endif ast_log(LOG_DEBUG, "Raw Hangup %s:%d, src=%d, dst=%d\n", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port), src, dst); return sendto(netsocket, &fh, sizeof(fh), 0, (struct sockaddr *)sin, sizeof(*sin)); } static int authenticate_request(struct chan_iax_pvt *p) { char requeststr[256] = ""; MYSNPRINTF "methods=%s;", p->methods); if (strstr(p->methods, "md5") || strstr(p->methods, "rsa")) { /* Build the challenge */ snprintf(p->challenge, sizeof(p->challenge), "%d", rand()); MYSNPRINTF "challenge=%s;", p->challenge); } MYSNPRINTF "username=%s;", p->username); if (strlen(requeststr)) requeststr[strlen(requeststr) - 1] = '\0'; return send_command(p, AST_FRAME_IAX, AST_IAX_COMMAND_AUTHREQ, 0, requeststr, strlen(requeststr) + 1, -1); } static int authenticate_verify(struct chan_iax_pvt *p, char *orequest) { char requeststr[256] = ""; char *var, *value, request[256]; char md5secret[256] = ""; char secret[256] = ""; char rsasecret[256] = ""; int res = -1; int x; if (!(p->state & IAX_STATE_AUTHENTICATED)) return res; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "secret")) strncpy(secret, value, sizeof(secret)-1); else if (!strcmp(var, "md5secret")) strncpy(md5secret, value, sizeof(md5secret)-1); else if (!strcmp(var, "rsasecret")) strncpy(rsasecret, value, sizeof(rsasecret)-1); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } if (strstr(p->methods, "rsa") && strlen(rsasecret) && strlen(p->inkeys)) { struct ast_key *key; char *keyn; char tmpkey[256]; strncpy(tmpkey, p->inkeys, sizeof(tmpkey)); keyn = strtok(tmpkey, ":"); while(keyn) { key = ast_key_get(keyn, AST_KEY_PUBLIC); if (key && !ast_check_signature(key, p->challenge, rsasecret)) { res = 0; break; } else if (!key) ast_log(LOG_WARNING, "requested inkey '%s' for RSA authentication does not exist\n", keyn); keyn = strtok(NULL, ":"); } } else if (strstr(p->methods, "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++) MYSNPRINTF "%2.2x", digest[x]); if (!strcasecmp(requeststr, md5secret)) res = 0; } else if (strstr(p->methods, "plaintext")) { if (!strcmp(secret, p->secret)) res = 0; } return res; } static int register_verify(int callno, struct sockaddr_in *sin, char *orequest) { char request[256]; char requeststr[256] = ""; char peer[256] = ""; char md5secret[256] = ""; char rsasecret[256] = ""; char secret[256] = ""; struct iax_peer *p; struct ast_key *key; char *var; char *value; char *keyn; int x; int expire = 0; iaxs[callno]->state &= ~IAX_STATE_AUTHENTICATED; strcpy(iaxs[callno]->peer, ""); if (!orequest) return -1; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "peer")) strncpy(peer, value, sizeof(peer)-1); else if (!strcmp(var, "md5secret")) strncpy(md5secret, value, sizeof(md5secret)-1); else if (!strcmp(var, "rsasecret")) strncpy(rsasecret, value, sizeof(rsasecret)-1); else if (!strcmp(var, "secret")) strncpy(secret, value, sizeof(secret)-1); else if (!strcmp(var, "refresh")) expire = atoi(value); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } if (!strlen(peer)) { ast_log(LOG_NOTICE, "Empty registration from %s\n", inet_ntoa(sin->sin_addr)); return -1; } for (p = peerl.peers; p ; p = p->next) if (!strcasecmp(p->name, peer)) break; if (!p) { ast_log(LOG_NOTICE, "No registration for peer '%s' (from %s)\n", peer, inet_ntoa(sin->sin_addr)); return -1; } if (!p->dynamic) { ast_log(LOG_NOTICE, "Peer '%s' is not dynamic (from %s)\n", peer, inet_ntoa(sin->sin_addr)); return -1; } if (!ast_apply_ha(p->ha, sin)) { ast_log(LOG_NOTICE, "Host %s denied access to register peer '%s'\n", inet_ntoa(sin->sin_addr), p->name); return -1; } strncpy(iaxs[callno]->secret, p->secret, sizeof(iaxs[callno]->secret)-1); strncpy(iaxs[callno]->inkeys, p->inkeys, sizeof(iaxs[callno]->inkeys)-1); /* Check secret against what we have on file */ if (strlen(rsasecret) && strstr(p->methods, "rsa") && strlen(p->challenge)) { if (strlen(p->inkeys)) { char tmpkeys[256]; strncpy(tmpkeys, p->inkeys, sizeof(tmpkeys)); keyn = strtok(tmpkeys, ":"); while(keyn) { key = ast_key_get(keyn, AST_KEY_PUBLIC); if (key && !ast_check_signature(key, p->challenge, rsasecret)) { iaxs[callno]->state |= IAX_STATE_AUTHENTICATED; break; } else if (!key) ast_log(LOG_WARNING, "requested inkey '%s' does not exist\n", keyn); keyn = strtok(NULL, ":"); } if (!keyn) { ast_log(LOG_NOTICE, "Host %s failed RSA authentication with inkeys '%s'\n", peer, p->inkeys); return -1; } } else { ast_log(LOG_NOTICE, "Host '%s' trying to do RSA authentication, but we have no inkeys\n", peer); return -1; } } else if (strlen(secret) && strstr(p->methods, "plaintext")) { /* They've provided a plain text password and we support that */ if (strcmp(secret, p->secret)) { ast_log(LOG_NOTICE, "Host %s did not provide proper plaintext password for '%s'\n", inet_ntoa(sin->sin_addr), p->name); return -1; } else iaxs[callno]->state |= IAX_STATE_AUTHENTICATED; } else if (strlen(md5secret) && strstr(p->methods, "md5") && strlen(p->challenge)) { 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); for (x=0;x<16;x++) MYSNPRINTF "%2.2x", digest[x]); if (strcasecmp(requeststr, md5secret)) { ast_log(LOG_NOTICE, "Host %s failed MD5 authentication for '%s' (%s != %s)\n", inet_ntoa(sin->sin_addr), p->name, requeststr, md5secret); return -1; } else iaxs[callno]->state |= IAX_STATE_AUTHENTICATED; } else if (strlen(md5secret) || strlen(secret)) { ast_log(LOG_NOTICE, "Inappropriate authentication received\n"); return -1; } strncpy(iaxs[callno]->peer, peer, sizeof(iaxs[callno]->peer)-1); /* Choose lowest expirey number */ if (expire && (expire < iaxs[callno]->expirey)) iaxs[callno]->expirey = expire; return 0; } static int authenticate(char *challenge, char *secret, char *keyn, char *methods, char *requeststr, int reqsize, struct sockaddr_in *sin) { int res = -1; int x; if (keyn && strlen(keyn)) { if (!strstr(methods, "rsa")) { if (!secret || !strlen(secret)) ast_log(LOG_NOTICE, "Asked to authenticate to %s with an RSA key, but they don't allow RSA authentication\n", inet_ntoa(sin->sin_addr)); } else if (!strlen(challenge)) { ast_log(LOG_NOTICE, "No challenge provided for RSA authentication to %s\n", inet_ntoa(sin->sin_addr)); } else { char sig[256]; struct ast_key *key; key = ast_key_get(keyn, AST_KEY_PRIVATE); if (!key) { ast_log(LOG_NOTICE, "Unable to find private key '%s'\n", keyn); } else { if (ast_sign(key, challenge, sig)) { ast_log(LOG_NOTICE, "Unable to sign challenge withy key\n"); res = -1; } else { MYSNPRINTF2 "rsasecret=%s;", sig); res = 0; } } } } /* Fall back */ if (res && secret && strlen(secret)) { if (strstr(methods, "md5") && strlen(challenge)) { struct MD5Context md5; unsigned char digest[16]; MD5Init(&md5); MD5Update(&md5, challenge, strlen(challenge)); MD5Update(&md5, secret, strlen(secret)); MD5Final(digest, &md5); /* If they support md5, authenticate with it. */ MYSNPRINTF2 "md5secret="); for (x=0;x<16;x++) MYSNPRINTF2 "%2.2x", digest[x]); MYSNPRINTF2 ";"); res = 0; } else if (strstr(methods, "plaintext")) { MYSNPRINTF2 "secret=%s;", secret); res = 0; } else ast_log(LOG_NOTICE, "No way to send secret to peer '%s' (their methods: %s)\n", inet_ntoa(sin->sin_addr), methods); } return res; } static int authenticate_reply(struct chan_iax_pvt *p, struct sockaddr_in *sin, char *orequest, char *override, char *okey) { struct iax_peer *peer; /* Start pessimistic */ int res = -1; char request[256]; char methods[80] = ""; char requeststr[256] = ""; char *var, *value; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "username")) strncpy(p->username, value, sizeof(p->username)-1); else if (!strcmp(var, "challenge")) strncpy(p->challenge, value, sizeof(p->challenge)-1); else if (!strcmp(var, "methods")) strncpy(methods, value, sizeof(methods)-1); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } /* Check for override RSA authentication first */ if ((override && strlen(override)) || (okey && strlen(okey))) { /* Normal password authentication */ res = authenticate(p->challenge, override, okey, methods, requeststr, sizeof(requeststr), sin); } else { ast_pthread_mutex_lock(&peerl.lock); peer = peerl.peers; while(peer) { if ((!strlen(p->peer) || !strcmp(p->peer, peer->name)) /* No peer specified at our end, or this is the peer */ && (!strlen(peer->username) || (!strcmp(peer->username, p->username))) /* No username specified in peer rule, or this is the right username */ && (!peer->addr.sin_addr.s_addr || ((sin->sin_addr.s_addr & peer->mask.s_addr) == (peer->addr.sin_addr.s_addr & peer->mask.s_addr))) /* No specified host, or this is our host */ ) { res = authenticate(p->challenge, peer->secret, peer->outkey, methods, requeststr, sizeof(requeststr), sin); if (!res) break; } peer = peer->next; } ast_pthread_mutex_unlock(&peerl.lock); } if (strlen(requeststr)) requeststr[strlen(requeststr)-1] = '\0'; if (!res) res = send_command(p, AST_FRAME_IAX, AST_IAX_COMMAND_AUTHREP, 0, requeststr, strlen(requeststr) + 1, -1); return res; } static int iax_do_register(struct iax_registry *reg); static int iax_do_register_s(void *data) { struct iax_registry *reg = data; reg->expire = -1; iax_do_register(reg); return 0; } static int try_transfer(struct chan_iax_pvt *pvt, char *orequest) { int newport = 0; int newcall = 0; char newip[256] = ""; char request[256] = ""; char *var, *value; struct sockaddr_in new; if (!orequest) return -1; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "remip")) strncpy(newip, value, sizeof(newip)-1); else if (!strcmp(var, "remport")) newport = atoi(value); else if (!strcmp(var, "remcall")) newcall = atoi(value); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } if (!newcall || !inet_aton(newip, &new.sin_addr) || !newport) { ast_log(LOG_WARNING, "Invalid transfer request\n"); return -1; } pvt->transfercallno = newcall; inet_aton(newip, &pvt->transfer.sin_addr); pvt->transfer.sin_port = htons(newport); pvt->transfer.sin_family = AF_INET; pvt->transferring = TRANSFER_BEGIN; send_command_transfer(pvt, AST_FRAME_IAX, AST_IAX_COMMAND_TXCNT, 0, NULL, 0); return 0; } static int complete_dpreply(struct chan_iax_pvt *pvt, char *orequest) { char *var, *value; char request[256] = ""; char exten[256] = ""; int status = CACHE_FLAG_UNKNOWN; int expirey = iaxdefaultdpcache; int x; struct iax_dpcache *dp, *prev; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "number")) strncpy(exten, value, sizeof(exten)-1); else if (!strcmp(var, "status")) { if (!strcasecmp(value, "exists")) status = CACHE_FLAG_EXISTS; else if (!strcasecmp(value, "nonexistant")) status = CACHE_FLAG_NONEXISTANT; else if (!strcasecmp(value, "canexist")) status = CACHE_FLAG_CANEXIST; else ast_log(LOG_WARNING, "Unknown status '%s'\n", value); } else if (!strcmp(var, "expirey")) expirey = atoi(value); else if (!strcmp(var, "ignorepat")) { /* Don' really do much with it */ } else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } ast_pthread_mutex_lock(&dpcache_lock); prev = NULL; dp = pvt->dpentries; while(dp) { if (!strcmp(dp->exten, exten)) { /* Let them go */ if (prev) prev->peer = dp->peer; else pvt->dpentries = dp->peer; dp->peer = NULL; dp->callno = -1; dp->expirey.tv_sec = dp->orig.tv_sec + expirey; if (dp->flags & CACHE_FLAG_PENDING) { dp->flags &= ~CACHE_FLAG_PENDING; dp->flags |= status; } /* Wake up waiters */ for (x=0;xwaiters) / sizeof(dp->waiters[0]); x++) if (dp->waiters[x] > -1) write(dp->waiters[x], "asdf", 4); } prev = dp; dp = dp->peer; } ast_pthread_mutex_unlock(&dpcache_lock); return 0; } static int complete_transfer(int callno, char *orequest) { int peercallno = -1; char request[256] = ""; char *var, *value; struct chan_iax_pvt *pvt = iaxs[callno]; struct ast_iax_frame *cur; if (!orequest) return -1; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "peercallno")) peercallno = atoi(value); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } if (peercallno < 0) { ast_log(LOG_WARNING, "Invalid transfer request\n"); return -1; } memcpy(&pvt->addr, &pvt->transfer, sizeof(pvt->addr)); memset(&pvt->transfer, 0, sizeof(pvt->transfer)); /* Reset sequence numbers */ pvt->oseqno = 0; pvt->iseqno = 0; pvt->peercallno = peercallno; pvt->transferring = TRANSFER_NONE; pvt->svoiceformat = -1; pvt->voiceformat = 0; pvt->transfercallno = -1; memset(&pvt->rxcore, 0, sizeof(pvt->rxcore)); memset(&pvt->offset, 0, sizeof(pvt->offset)); memset(&pvt->history, 0, sizeof(pvt->history)); pvt->jitterbuffer = 0; pvt->jitter = 0; pvt->historicjitter = 0; pvt->lag = 0; pvt->last = 0; pvt->lastsent = 0; pvt->pingtime = DEFAULT_RETRY_TIME; ast_pthread_mutex_lock(&iaxq.lock); for (cur = iaxq.head; cur ; cur = cur->next) { /* We must cancel any packets that would have been transmitted because now we're talking to someone new. It's okay, they were transmitted to someone that didn't care anyway. */ if (callno == cur->callno) cur->retries = -1; } ast_pthread_mutex_unlock(&iaxq.lock); return 0; } static int iax_ack_registry(char *orequest, struct sockaddr_in *sin) { struct iax_registry *reg; /* Start pessimistic */ char request[256] = ""; char peer[256] = ""; int ourport = 0; int refresh = 0; char ourip[256] = ""; struct sockaddr_in oldus; char *var, *value; if (!orequest) return -1; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "yourip")) strncpy(ourip, value, sizeof(ourip)-1); else if (!strcmp(var, "peer")) strncpy(peer, value, sizeof(peer)-1); else if (!strcmp(var, "yourport")) ourport = atoi(value); else if (!strcmp(var, "refresh")) refresh = atoi(value); else if (!strcmp(var, "callerid")) { /* We don't really care about suggested Caller*ID, that's more for phones */ } else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } reg = registrations; while(reg) { if (!strcasecmp(reg->username, peer)) { memcpy(&oldus, ®->us, sizeof(oldus)); if (memcmp(®->addr, sin, sizeof(®->addr))) { ast_log(LOG_WARNING, "Received unsolicited registry ack from '%s'\n", inet_ntoa(sin->sin_addr)); return -1; } if (!inet_aton(ourip, ®->us.sin_addr)) { ast_log(LOG_WARNING, "Registry ack from '%s' contains invalid IP '%s'\n", inet_ntoa(sin->sin_addr), ourip); return -1; } reg->us.sin_port = htons(ourport); if (refresh && (reg->refresh < refresh)) { /* Refresh faster if necessary */ reg->refresh = refresh; if (reg->expire > -1) ast_sched_del(sched, reg->expire); reg->expire = ast_sched_add(sched, (5 * reg->refresh / 6) * 1000, iax_do_register_s, reg); } if (memcmp(&oldus, ®->us, sizeof(oldus)) && (option_verbose > 2)) { snprintf(ourip, sizeof(ourip), "%s:%d", inet_ntoa(reg->us.sin_addr), ntohs(reg->us.sin_port)); ast_verbose(VERBOSE_PREFIX_3 "Registered to '%s', who sees us as %s\n", inet_ntoa(sin->sin_addr), ourip); } reg->regstate = REG_STATE_REGISTERED; return 0; } reg = reg->next; } ast_log(LOG_WARNING, "Registry acknowledge on unknown registery '%s'\n", peer); return -1; } static int iax_register(char *value, int lineno) { struct iax_registry *reg; char copy[256]; char *username, *hostname, *secret; char *porta; struct hostent *hp; if (!value) return -1; strncpy(copy, value, sizeof(copy)-1); username = strtok(copy, "@"); hostname = strtok(NULL, "@"); if (!hostname) { ast_log(LOG_WARNING, "Format for registration is user[:secret]@host[:port] at line %d", lineno); return -1; } username = strtok(username, ":"); secret = strtok(NULL, ":"); hostname = strtok(hostname, ":"); porta = strtok(NULL, ";"); if (porta && !atoi(porta)) { ast_log(LOG_WARNING, "%s is not a valid port number at line %d\n", porta, lineno); return -1; } hp = gethostbyname(hostname); if (!hp) { ast_log(LOG_WARNING, "Host '%s' not found at line %d\n", hostname, lineno); return -1; } reg = malloc(sizeof(struct iax_registry)); if (reg) { memset(reg, 0, sizeof(struct iax_registry)); strncpy(reg->username, username, sizeof(reg->username)-1); if (secret) strncpy(reg->secret, secret, sizeof(reg->secret)-1); reg->expire = -1; reg->refresh = AST_DEFAULT_REG_EXPIRE; reg->addr.sin_family = AF_INET; memcpy(®->addr.sin_addr, hp->h_addr, sizeof(®->addr.sin_addr)); reg->addr.sin_port = porta ? htons(atoi(porta)) : htons(AST_DEFAULT_IAX_PORTNO); reg->next = registrations; reg->callno = -1; registrations = reg; } else { ast_log(LOG_ERROR, "Out of memory\n"); return -1; } return 0; } static int expire_registry(void *data) { struct iax_peer *p = data; /* Reset the address */ memset(&p->addr, 0, sizeof(p->addr)); /* Reset expire notice */ p->expire = -1; /* Reset expirey value */ p->expirey = expirey; if (regfunk) regfunk(p->name, 0); return 0; } static int update_registry(char *name, struct sockaddr_in *sin, int callno) { char requeststr[256] = ""; struct iax_peer *p; for (p = peerl.peers;p;p = p->next) { if (!strcasecmp(name, p->name)) { if (memcmp(&p->addr, sin, sizeof(p->addr))) { if (regfunk) regfunk(p->name, 1); if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Registered '%s' (%s) at %s:%d\n", p->name, iaxs[callno]->state & IAX_STATE_AUTHENTICATED ? "AUTHENTICATED" : "UNAUTHENTICATED", inet_ntoa(sin->sin_addr), htons(sin->sin_port)); } /* Update the host */ memcpy(&p->addr, sin, sizeof(p->addr)); /* Setup the expirey */ if (p->expire > -1) ast_sched_del(sched, p->expire); p->expire = ast_sched_add(sched, p->expirey * 1000, expire_registry, (void *)p); MYSNPRINTF "peer=%s;yourip=%s;yourport=%d;refresh=%d;", p->name, inet_ntoa(p->addr.sin_addr), ntohs(p->addr.sin_port), p->expirey); if (p->hascallerid) MYSNPRINTF "callerid=%s;", p->callerid); requeststr[strlen(requeststr)-1] = '\0'; return send_command_final(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_REGACK, 0, requeststr, strlen(requeststr) + 1, -1);; } } ast_log(LOG_WARNING, "No such peer '%s'\n", name); return -1; } static int registry_authrequest(char *name, int callno) { char requeststr[256] = ""; struct iax_peer *p; for (p = peerl.peers;p;p = p->next) { if (!strcasecmp(name, p->name)) { MYSNPRINTF "methods=%s;", p->methods); if (strstr(p->methods, "md5") || strstr(p->methods, "rsa")) { /* Build the challenge */ snprintf(p->challenge, sizeof(p->challenge), "%d", rand()); MYSNPRINTF "challenge=%s;", p->challenge); } MYSNPRINTF "peer=%s;", name); requeststr[strlen(requeststr)-1] = '\0'; return send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_REGAUTH, 0, requeststr, strlen(requeststr) + 1, -1);; } } ast_log(LOG_WARNING, "No such peer '%s'\n", name); return 0; } static int registry_rerequest(char *orequest, int callno, struct sockaddr_in *sin) { struct iax_registry *reg; /* Start pessimistic */ char request[256] = ""; char requeststr[256] = ""; char peer[256] = ""; char methods[256] = ""; char challenge[256] = ""; char *var, *value; int res; if (!orequest) return -1; strncpy(request, orequest, sizeof(request)-1); var = strtok(request, ";"); while(var) { value = strchr(var, '='); if (value) { *value='\0'; value++; if (!strcmp(var, "methods")) strncpy(methods, value, sizeof(methods)-1); else if (!strcmp(var, "peer")) strncpy(peer, value, sizeof(peer)-1); else if (!strcmp(var, "challenge")) strncpy(challenge, value, sizeof(challenge)-1); else ast_log(LOG_WARNING, "Unknown variable '%s' with value '%s'\n", var, value); } var = strtok(NULL, ";"); } reg = registrations; while(reg) { if (!strcasecmp(reg->username, peer)) { if (memcmp(®->addr, sin, sizeof(®->addr))) { ast_log(LOG_WARNING, "Received unsolicited registry authenticate request from '%s'\n", inet_ntoa(sin->sin_addr)); return -1; } if (!strlen(reg->secret)) { ast_log(LOG_NOTICE, "No secret associated with peer '%s'\n", reg->username); reg->regstate = REG_STATE_NOAUTH; return -1; } MYSNPRINTF "peer=%s;refresh=%d;", reg->username, reg->refresh); if (reg->secret[0] == '[') { char tmpkey[256]; strncpy(tmpkey, reg->secret + 1, sizeof(tmpkey) - 1); tmpkey[strlen(tmpkey) - 1] = '\0'; res = authenticate(challenge, NULL, tmpkey, methods, requeststr, sizeof(requeststr), sin); } else res = authenticate(challenge, reg->secret, NULL, methods, requeststr, sizeof(requeststr), sin); if (!res) { reg->regstate = REG_STATE_AUTHSENT; return send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_REGREQ, 0, requeststr, strlen(requeststr) + 1, -1); } else return -1; } reg = reg->next; } ast_log(LOG_WARNING, "Registry acknowledge on unknown registery '%s'\n", peer); return -1; } static int stop_stuff(int callno) { if (iaxs[callno]->lagid > -1) ast_sched_del(sched, iaxs[callno]->lagid); iaxs[callno]->lagid = -1; if (iaxs[callno]->pingid > -1) ast_sched_del(sched, iaxs[callno]->pingid); iaxs[callno]->pingid = -1; if (iaxs[callno]->autoid > -1) ast_sched_del(sched, iaxs[callno]->autoid); iaxs[callno]->autoid = -1; return 0; } static int auto_hangup(void *nothing) { int callno = (int)(long)(nothing); if (iaxs[callno]) { iaxs[callno]->autoid = -1; send_command_final(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_HANGUP, 0, "Timeout", strlen("Timeout") + 1, -1); } return 0; } static void iax_dprequest(struct iax_dpcache *dp, int callno) { /* Auto-hangup with 30 seconds of inactivity */ if (iaxs[callno]->autoid > -1) ast_sched_del(sched, iaxs[callno]->autoid); iaxs[callno]->autoid = ast_sched_add(sched, 30000, auto_hangup, (void *)callno); send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_DPREQ, 0, dp->exten, strlen(dp->exten) + 1, -1); dp->flags |= CACHE_FLAG_TRANSMITTED; } static int iax_vnak(int callno) { return send_command_immediate(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_VNAK, 0, NULL, 0, iaxs[callno]->iseqno); } static void vnak_retransmit(int callno, int last) { struct ast_iax_frame *f; ast_pthread_mutex_lock(&iaxq.lock); f = iaxq.head; while(f) { /* Send a copy immediately */ if ((f->callno == callno) && iaxs[f->callno] && (f->seqno >= last)) { send_packet(f); } f = f->next; } ast_pthread_mutex_unlock(&iaxq.lock); } static int socket_read(int *id, int fd, short events, void *cbdata) { struct sockaddr_in sin; int res; int new = NEW_PREVENT; char buf[4096]; char src[80]; int len = sizeof(sin); int dcallno = -1; struct ast_iax_full_hdr *fh = (struct ast_iax_full_hdr *)buf; struct ast_iax_mini_hdr *mh = (struct ast_iax_mini_hdr *)buf; struct ast_iax_frame fr, *cur; struct ast_frame f; struct ast_channel *c; struct iax_dpcache *dp; int format; int exists; char rel0[256]; char rel1[255]; char empty[32]=""; /* Safety measure */ res = recvfrom(netsocket, buf, sizeof(buf), 0,(struct sockaddr *) &sin, &len); if (res < 0) { if (errno != ECONNREFUSED) ast_log(LOG_WARNING, "Error: %s\n", strerror(errno)); handle_error(); return 1; } if (res < sizeof(struct ast_iax_mini_hdr)) { ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, sizeof(struct ast_iax_mini_hdr)); return 1; } #ifdef DEBUG_SUPPORT if (iaxdebug) showframe(NULL, fh, 1, &sin); #endif if (ntohs(mh->callno) & AST_FLAG_FULL) { /* Get the destination call number */ dcallno = ntohs(fh->dcallno); /* Retrieve the type and subclass */ f.frametype = fh->type; f.subclass = uncompress_subclass(fh->csub); #if 0 f.subclass = fh->subclasshigh << 16; f.subclass += ntohs(fh->subclasslow); #endif if ((f.frametype == AST_FRAME_IAX) && ((f.subclass == AST_IAX_COMMAND_NEW) || (f.subclass == AST_IAX_COMMAND_REGREQ))) new = NEW_ALLOW; } else { /* Don't knwo anything about it yet */ f.frametype = AST_FRAME_NULL; f.subclass = 0; } ast_pthread_mutex_lock(&iaxs_lock); fr.callno = find_callno(ntohs(mh->callno) & ~AST_FLAG_FULL, dcallno, &sin, new); if ((fr.callno < 0) || !iaxs[fr.callno]) { /* A call arrived for a non-existant destination. Unless it's an "inval" frame, reply with an inval */ if (ntohs(mh->callno) & AST_FLAG_FULL) { /* We can only raw hangup control frames */ if (((f.subclass != AST_IAX_COMMAND_INVAL) && (f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC))|| (f.frametype != AST_FRAME_IAX)) raw_hangup(&sin, ntohs(fh->dcallno), ntohs(mh->callno) & ~AST_FLAG_FULL ); } ast_pthread_mutex_unlock(&iaxs_lock); return 1; } if (((f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC)) || (f.frametype != AST_FRAME_IAX)) iaxs[fr.callno]->peercallno = (short)(ntohs(mh->callno) & ~AST_FLAG_FULL); if (ntohs(mh->callno) & AST_FLAG_FULL) { if (option_debug) ast_log(LOG_DEBUG, "Received packet %d, (%d, %d)\n", ntohs(fh->seqno), f.frametype, f.subclass); /* Check if it's out of order (and not an ACK or INVAL) */ fr.seqno = ntohs(fh->seqno); if ((iaxs[fr.callno]->iseqno != fr.seqno) && (iaxs[fr.callno]->iseqno || ((f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC)) || (f.subclass != AST_FRAME_IAX))) { if ( ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL) && (f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC) && (f.subclass != AST_IAX_COMMAND_VNAK)) || (f.frametype != AST_FRAME_IAX)) { /* If it's not an ACK packet, it's out of order. */ if (option_debug) ast_log(LOG_DEBUG, "Packet arrived out of order (expecting %d, got %d) (frametype = %d, subclass = %d)\n", iaxs[fr.callno]->iseqno, fr.seqno, f.frametype, f.subclass); if (iaxs[fr.callno]->iseqno > fr.seqno) { /* If we've already seen it, ack it XXX There's a border condition here XXX */ if ((f.frametype != AST_FRAME_IAX) || ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL))) { if (option_debug) ast_log(LOG_DEBUG, "Acking anyway\n"); send_command_immediate(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACK, fr.ts, NULL, 0,fr.seqno); } } else { /* Send a VNAK requesting retransmission */ iax_vnak(fr.callno); } ast_pthread_mutex_unlock(&iaxs_lock); return 1; } } else { /* Increment unless it's an ACK or VNAK */ if (((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_INVAL) && (f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC) && (f.subclass != AST_IAX_COMMAND_VNAK)) || (f.frametype != AST_FRAME_IAX)) iaxs[fr.callno]->iseqno++; } /* A full frame */ if (res < sizeof(struct ast_iax_full_hdr)) { ast_log(LOG_WARNING, "midget packet received (%d of %d min)\n", res, sizeof(struct ast_iax_full_hdr)); ast_pthread_mutex_unlock(&iaxs_lock); return 1; } f.datalen = res - sizeof(struct ast_iax_full_hdr); if (f.datalen) f.data = buf + sizeof(struct ast_iax_full_hdr); else f.data = empty; fr.ts = ntohl(fh->ts); /* Unless this is an ACK or INVAL frame, ack it */ if ((f.frametype != AST_FRAME_IAX) || ((f.subclass != AST_IAX_COMMAND_ACK) && (f.subclass != AST_IAX_COMMAND_TXCNT) && (f.subclass != AST_IAX_COMMAND_TXACC) && (f.subclass != AST_IAX_COMMAND_INVAL) && (f.subclass != AST_IAX_COMMAND_VNAK))) send_command_immediate(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACK, fr.ts, NULL, 0,fr.seqno); if (f.frametype == AST_FRAME_VOICE) { if (f.subclass != iaxs[fr.callno]->voiceformat) { iaxs[fr.callno]->voiceformat = f.subclass; ast_log(LOG_DEBUG, "Ooh, voice format changed to %d\n", f.subclass); if (iaxs[fr.callno]->owner) { int orignative; ast_pthread_mutex_lock(&iaxs[fr.callno]->owner->lock); orignative = iaxs[fr.callno]->owner->nativeformats; iaxs[fr.callno]->owner->nativeformats = f.subclass; if (iaxs[fr.callno]->owner->readformat) ast_set_read_format(iaxs[fr.callno]->owner, iaxs[fr.callno]->owner->readformat); iaxs[fr.callno]->owner->nativeformats = orignative; ast_pthread_mutex_unlock(&iaxs[fr.callno]->owner->lock); } } } if (f.frametype == AST_FRAME_IAX) { /* Handle the IAX pseudo frame itself */ if (option_debug) ast_log(LOG_DEBUG, "IAX subclass %d received\n", f.subclass); /* Go through the motions of delivering the packet without actually doing so, unless this is a lag request since it will be done for real */ if (f.subclass != AST_IAX_COMMAND_LAGRQ) schedule_delivery(&fr, 0); switch(f.subclass) { case AST_IAX_COMMAND_ACK: /* Ack the packet with the given timestamp */ ast_pthread_mutex_lock(&iaxq.lock); for (cur = iaxq.head; cur ; cur = cur->next) { /* If it's our call, and our timestamp, mark -1 retries */ if ((fr.callno == cur->callno) && (fr.seqno == cur->seqno)) { cur->retries = -1; /* Destroy call if this is the end */ if (cur->final) iax_destroy(fr.callno); } } ast_pthread_mutex_unlock(&iaxq.lock); break; case AST_IAX_COMMAND_QUELCH: if (iaxs[fr.callno]->state & IAX_STATE_STARTED) iaxs[fr.callno]->quelch = 1; break; case AST_IAX_COMMAND_UNQUELCH: if (iaxs[fr.callno]->state & IAX_STATE_STARTED) iaxs[fr.callno]->quelch = 0; break; case AST_IAX_COMMAND_TXACC: if (iaxs[fr.callno]->transferring == TRANSFER_BEGIN) { /* Ack the packet with the given timestamp */ ast_pthread_mutex_lock(&iaxq.lock); for (cur = iaxq.head; cur ; cur = cur->next) { /* Cancel any outstanding txcnt's */ if ((fr.callno == cur->callno) && (cur->transfer)) cur->retries = -1; } ast_pthread_mutex_unlock(&iaxq.lock); snprintf(rel1, sizeof(rel1), "callno=%d;", iaxs[fr.callno]->callno); send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_TXREADY, 0, rel1, strlen(rel1) + 1, -1); iaxs[fr.callno]->transferring = TRANSFER_READY; } break; case AST_IAX_COMMAND_NEW: /* Ignore if it's already up */ if (iaxs[fr.callno]->state & (IAX_STATE_STARTED | IAX_STATE_TBD)) break; ((char *)f.data)[f.datalen] = '\0'; if (check_access(fr.callno, &sin, f.data, f.datalen)) { /* They're not allowed on */ send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No authority found", strlen("No authority found"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s'\n", inet_ntoa(sin.sin_addr), (char *)f.data); break; } ast_pthread_mutex_unlock(&iaxs_lock); /* This might re-enter the IAX code and need the lock */ exists = ast_exists_extension(NULL, iaxs[fr.callno]->context, iaxs[fr.callno]->exten, 1, iaxs[fr.callno]->callerid); ast_pthread_mutex_lock(&iaxs_lock); if (!strlen(iaxs[fr.callno]->secret) && !strlen(iaxs[fr.callno]->inkeys)) { if (strcmp(iaxs[fr.callno]->exten, "TBD") && !exists) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No such context/extension", strlen("No such context/extension"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->exten, iaxs[fr.callno]->context); } else { /* Select an appropriate format */ format = iaxs[fr.callno]->peerformat & iax_capability; if (!format) { format = iaxs[fr.callno]->peercapability & iax_capability; if (!format) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "Unable to negotiate codec", strlen("Unable to negotiate codec"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat, iaxs[fr.callno]->peercapability, iax_capability); } else { /* Pick one... */ format = ast_best_codec(iaxs[fr.callno]->peercapability & iax_capability); if (!format) { ast_log(LOG_ERROR, "No best format in 0x%x???\n", iaxs[fr.callno]->peercapability & iax_capability); send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "Unable to negotiate codec", strlen("Unable to negotiate codec"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat, iaxs[fr.callno]->peercapability, iax_capability); iaxs[fr.callno]->alreadygone = 1; break; } } } /* No authentication required, let them in */ snprintf(rel1, sizeof(rel1), "formats=%d;", format); send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACCEPT, 0, rel1, strlen(rel1) + 1, -1); if (strcmp(iaxs[fr.callno]->exten, "TBD")) { iaxs[fr.callno]->state |= IAX_STATE_STARTED; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Accepting unauthenticated call from %s, requested format = %d, actual format = %d\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat,format); if(!(c = ast_iax_new(iaxs[fr.callno], AST_STATE_RING, iax_capability))) { iax_destroy(fr.callno); } else c->nativeformats = format; } else { iaxs[fr.callno]->state |= IAX_STATE_TBD; /* If this is a TBD call, we're ready but now what... */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Accepted unauthenticated TBD call from %s\n", inet_ntoa(sin.sin_addr)); } } break; } authenticate_request(iaxs[fr.callno]); iaxs[fr.callno]->state |= IAX_STATE_AUTHENTICATED; break; case AST_IAX_COMMAND_DPREQ: /* Request status in the dialplan */ ((char *)f.data)[f.datalen] = '\0'; if ((iaxs[fr.callno]->state & IAX_STATE_TBD) && !(iaxs[fr.callno]->state & IAX_STATE_STARTED) && f.datalen) { /* Must be started */ if (ast_exists_extension(NULL, iaxs[fr.callno]->context, (char *)f.data, 1, iaxs[fr.callno]->callerid)) { strcpy(rel0, "exists"); } else if (ast_canmatch_extension(NULL, iaxs[fr.callno]->context, (char *)f.data, 1, iaxs[fr.callno]->callerid)) { strcpy(rel0, "canexist"); } else { strcpy(rel0, "nonexistant"); } snprintf(rel1, sizeof(rel1), "number=%s;status=%s;ignorepat=%s;expirey=%d;", (char *)f.data, rel0, ast_ignore_pattern(iaxs[fr.callno]->context, (char *)f.data) ? "yes" : "no", iaxdefaultdpcache); send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_DPREP, 0, rel1, strlen(rel1) + 1, -1); } break; case AST_IAX_COMMAND_HANGUP: iaxs[fr.callno]->alreadygone = 1; iax_destroy(fr.callno); break; case AST_IAX_COMMAND_REJECT: if (f.data) ((char *)f.data)[f.datalen] = '\0'; if (iaxs[fr.callno]->owner) ast_log(LOG_WARNING, "Call rejected by %s: %s\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr), (char *)f.data); iaxs[fr.callno]->error = EPERM; iax_destroy(fr.callno); break; case AST_IAX_COMMAND_ACCEPT: /* Ignore if call is already up or needs authentication or is a TBD */ if (iaxs[fr.callno]->state & (IAX_STATE_STARTED | IAX_STATE_TBD | IAX_STATE_AUTHENTICATED)) break; if (f.data && f.datalen) { ((char *)f.data)[f.datalen]='\0'; iax_getformats(fr.callno, (char *)f.data); } else { if (iaxs[fr.callno]->owner) iaxs[fr.callno]->peerformat = iaxs[fr.callno]->owner->nativeformats; else iaxs[fr.callno]->peerformat = iax_capability; } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Call accepted by %s (format %d)\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr), iaxs[fr.callno]->peerformat); if (!(iaxs[fr.callno]->peerformat & iaxs[fr.callno]->capability)) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "Unable to negotiate codec", strlen("Unable to negotiate codec"), -1); ast_log(LOG_NOTICE, "Rejected call to %s, format 0x%x incompatible with our capability 0x%x.\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat, iaxs[fr.callno]->capability); } else { iaxs[fr.callno]->state |= IAX_STATE_STARTED; if (iaxs[fr.callno]->owner) { /* Switch us to use a compatible format */ iaxs[fr.callno]->owner->nativeformats = iaxs[fr.callno]->peerformat; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Format for call is %d\n", iaxs[fr.callno]->owner->nativeformats); /* Setup read/write formats properly. */ if (iaxs[fr.callno]->owner->writeformat) ast_set_write_format(iaxs[fr.callno]->owner, iaxs[fr.callno]->owner->writeformat); if (iaxs[fr.callno]->owner->readformat) ast_set_read_format(iaxs[fr.callno]->owner, iaxs[fr.callno]->owner->readformat); } } ast_pthread_mutex_lock(&dpcache_lock); dp = iaxs[fr.callno]->dpentries; while(dp) { if (!(dp->flags & CACHE_FLAG_TRANSMITTED)) { iax_dprequest(dp, fr.callno); } dp = dp->peer; } ast_pthread_mutex_unlock(&dpcache_lock); break; case AST_IAX_COMMAND_PING: #ifdef BRIDGE_OPTIMIZATION if (iaxs[fr.callno]->bridgecallno > -1) { /* If we're in a bridged call, just forward this */ forward_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_PING, fr.ts, NULL, 0, -1); } else { /* Send back a pong packet with the original timestamp */ send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_PONG, fr.ts, NULL, 0, -1); } #else /* Send back a pong packet with the original timestamp */ send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_PONG, fr.ts, NULL, 0, -1); #endif break; case AST_IAX_COMMAND_PONG: #ifdef BRIDGE_OPTIMIZATION if (iaxs[fr.callno]->bridgecallno > -1) { /* Forward to the other side of the bridge */ forward_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_PONG, fr.ts, NULL, 0, -1); } else { /* Calculate ping time */ iaxs[fr.callno]->pingtime = calc_timestamp(iaxs[fr.callno], 0) - fr.ts; } #else /* Calculate ping time */ iaxs[fr.callno]->pingtime = calc_timestamp(iaxs[fr.callno], 0) - fr.ts; #endif break; case AST_IAX_COMMAND_LAGRQ: case AST_IAX_COMMAND_LAGRP: #ifdef BRIDGE_OPTIMIZATION if (iaxs[fr.callno]->bridgecallno > -1) { forward_command(iaxs[fr.callno], AST_FRAME_IAX, f.subclass, fr.ts, NULL, 0, -1); } else { #endif /* A little strange -- We have to actually go through the motions of delivering the packet. In the very last step, it will be properly handled by do_deliver */ snprintf(src, sizeof(src), "LAGRQ-IAX/%s/%d", inet_ntoa(sin.sin_addr),fr.callno); f.src = src; f.mallocd = 0; f.offset = 0; fr.f = &f; f.timelen = 0; schedule_delivery(iaxfrdup2(&fr, 0), 1); #ifdef BRIDGE_OPTIMIZATION } #endif break; case AST_IAX_COMMAND_AUTHREQ: if (iaxs[fr.callno]->state & (IAX_STATE_STARTED | IAX_STATE_TBD)) { ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr.callno]->owner ? iaxs[fr.callno]->owner->name : ""); break; } ((char *)f.data)[f.datalen] = '\0'; if (authenticate_reply(iaxs[fr.callno], &iaxs[fr.callno]->addr, (char *)f.data, iaxs[fr.callno]->secret, iaxs[fr.callno]->outkey)) { ast_log(LOG_WARNING, "I don't know how to authenticate %s to %s\n", (char *)f.data, inet_ntoa(iaxs[fr.callno]->addr.sin_addr)); } break; case AST_IAX_COMMAND_AUTHREP: /* Ignore once we've started */ if (iaxs[fr.callno]->state & (IAX_STATE_STARTED | IAX_STATE_TBD)) { ast_log(LOG_WARNING, "Call on %s is already up, can't start on it\n", iaxs[fr.callno]->owner ? iaxs[fr.callno]->owner->name : ""); break; } ((char *)f.data)[f.datalen] = '\0'; if (authenticate_verify(iaxs[fr.callno], (char *)f.data)) { ast_log(LOG_NOTICE, "Host %s failed to authenticate as %s\n", inet_ntoa(iaxs[fr.callno]->addr.sin_addr), iaxs[fr.callno]->username); send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No authority found", strlen("No authority found"), -1); break; } ast_pthread_mutex_unlock(&iaxs_lock); /* This might re-enter the IAX code and need the lock */ exists = ast_exists_extension(NULL, iaxs[fr.callno]->context, iaxs[fr.callno]->exten, 1, iaxs[fr.callno]->callerid); ast_pthread_mutex_lock(&iaxs_lock); if (strcmp(iaxs[fr.callno]->exten, "TBD") && !exists) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No such context/extension", strlen("No such context/extension"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, request '%s@%s' does not exist\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->exten, iaxs[fr.callno]->context); } else { /* Select an appropriate format */ format = iaxs[fr.callno]->peerformat & iax_capability; if (!format) { ast_log(LOG_DEBUG, "We don't do requested format %d, falling back to peer capability %d\n", iaxs[fr.callno]->peerformat, iaxs[fr.callno]->peercapability); format = iaxs[fr.callno]->peercapability & iax_capability; if (!format) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "Unable to negotiate codec", strlen("Unable to negotiate codec"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat, iaxs[fr.callno]->peercapability, iax_capability); } else { /* Pick one... */ format = ast_best_codec(iaxs[fr.callno]->peercapability & iax_capability); if (!format) { ast_log(LOG_ERROR, "No best format in 0x%x???\n", iaxs[fr.callno]->peercapability & iax_capability); send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "Unable to negotiate codec", strlen("Unable to negotiate codec"), -1); ast_log(LOG_NOTICE, "Rejected connect attempt from %s, requested/capability 0x%x/0x%x incompatible with our capability 0x%x.\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat, iaxs[fr.callno]->peercapability, iax_capability); } } } /* Authentication received */ snprintf(rel1, sizeof(rel1), "formats=%d;", format); send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_ACCEPT, 0, rel1, strlen(rel1) + 1, -1); if (strcmp(iaxs[fr.callno]->exten, "TBD")) { iaxs[fr.callno]->state |= IAX_STATE_STARTED; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Accepting AUTHENTICATED call from %s, requested format = %d, actual format = %d\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat,format); iaxs[fr.callno]->state |= IAX_STATE_STARTED; if(!(c = ast_iax_new(iaxs[fr.callno], AST_STATE_RING, iax_capability))) { iax_destroy(fr.callno); } else c->nativeformats = iaxs[fr.callno]->peerformat; } else { iaxs[fr.callno]->state |= IAX_STATE_TBD; /* If this is a TBD call, we're ready but now what... */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Accepted AUTHENTICATED TBD call from %s\n", inet_ntoa(sin.sin_addr)); } } break; case AST_IAX_COMMAND_DIAL: ((char *)f.data)[f.datalen] = '\0'; if (iaxs[fr.callno]->state & IAX_STATE_TBD) { iaxs[fr.callno]->state &= ~IAX_STATE_TBD; strncpy(iaxs[fr.callno]->exten, (char *)f.data, sizeof(iaxs[fr.callno]->exten)-1); if (!ast_exists_extension(NULL, iaxs[fr.callno]->context, iaxs[fr.callno]->exten, 1, iaxs[fr.callno]->callerid)) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No such context/extension", strlen("No such context/extension"), -1); ast_log(LOG_NOTICE, "Rejected dial attempt from %s, request '%s@%s' does not exist\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->exten, iaxs[fr.callno]->context); } else { iaxs[fr.callno]->state |= IAX_STATE_STARTED; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Accepting DIAL from %s, formats = 0x%x\n", inet_ntoa(sin.sin_addr), iaxs[fr.callno]->peerformat); iaxs[fr.callno]->state |= IAX_STATE_STARTED; if(!(c = ast_iax_new(iaxs[fr.callno], AST_STATE_RING, iax_capability))) { iax_destroy(fr.callno); } else c->nativeformats = iaxs[fr.callno]->peerformat; } } break; case AST_IAX_COMMAND_INVAL: iaxs[fr.callno]->error = ENOTCONN; iax_destroy(fr.callno); if (option_debug) ast_log(LOG_DEBUG, "Destroying call %d\n", fr.callno); break; case AST_IAX_COMMAND_VNAK: ast_log(LOG_DEBUG, "Sending VNAK\n"); /* Force retransmission */ vnak_retransmit(fr.callno, fr.seqno); break; case AST_IAX_COMMAND_REGREQ: if (f.data) ((char *)f.data)[f.datalen]='\0'; if (register_verify(fr.callno, &sin, (char *)f.data)) { send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REGREJ, 0, "Registration Refused", strlen("Registration Refused"), -1); break; } if ((!strlen(iaxs[fr.callno]->secret) && !strlen(iaxs[fr.callno]->inkeys)) || (iaxs[fr.callno]->state & IAX_STATE_AUTHENTICATED)) { if (update_registry(iaxs[fr.callno]->peer, &sin, fr.callno)) ast_log(LOG_WARNING, "Registry error\n"); break; } registry_authrequest(iaxs[fr.callno]->peer, fr.callno); break; case AST_IAX_COMMAND_REGACK: if (f.data) ((char *)f.data)[f.datalen] = '\0'; if (iax_ack_registry(f.data, &sin)) ast_log(LOG_WARNING, "Registration failure\n"); iax_destroy(fr.callno); break; case AST_IAX_COMMAND_REGREJ: if (f.data) ((char *)f.data)[f.datalen] = '\0'; if (iaxs[fr.callno]->reg) { ast_log(LOG_NOTICE, "Registration of '%s' rejected: %s\n", iaxs[fr.callno]->reg->username, (char *)f.data); iaxs[fr.callno]->reg->regstate = REG_STATE_REJECTED; } iax_destroy(fr.callno); break; case AST_IAX_COMMAND_REGAUTH: /* Authentication request */ if (f.data) ((char *)f.data)[f.datalen] = '\0'; if (registry_rerequest(f.data, fr.callno, &sin)) send_command_final(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_REJECT, 0, "No authority found", strlen("No authority found"), -1); break; case AST_IAX_COMMAND_TXREJ: iaxs[fr.callno]->transferring = 0; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel '%s' unable to transfer\n", iaxs[fr.callno]->owner ? iaxs[fr.callno]->owner->name : ""); memset(&iaxs[fr.callno]->transfer, 0, sizeof(iaxs[fr.callno]->transfer)); if (iaxs[fr.callno]->bridgecallno > -1) { if (iaxs[iaxs[fr.callno]->bridgecallno]->transferring) { iaxs[iaxs[fr.callno]->bridgecallno]->transferring = 0; send_command(iaxs[iaxs[fr.callno]->bridgecallno], AST_FRAME_IAX, AST_IAX_COMMAND_TXREJ, 0, NULL, 0, -1); } } break; case AST_IAX_COMMAND_TXREADY: if (iaxs[fr.callno]->transferring == TRANSFER_BEGIN) { iaxs[fr.callno]->transferring = TRANSFER_READY; if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel '%s' ready to transfer\n", iaxs[fr.callno]->owner ? iaxs[fr.callno]->owner->name : ""); if (iaxs[fr.callno]->bridgecallno > -1) { if (iaxs[iaxs[fr.callno]->bridgecallno]->transferring == TRANSFER_READY) { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Releasing %s and %s\n", iaxs[fr.callno]->owner ? iaxs[fr.callno]->owner->name : "", iaxs[iaxs[fr.callno]->bridgecallno]->owner ? iaxs[iaxs[fr.callno]->bridgecallno]->owner->name : ""); /* They're both ready, now release them. */ iaxs[iaxs[fr.callno]->bridgecallno]->transferring = TRANSFER_RELEASED; iaxs[fr.callno]->transferring = TRANSFER_RELEASED; iaxs[iaxs[fr.callno]->bridgecallno]->alreadygone = 1; iaxs[fr.callno]->alreadygone = 1; /* Stop doing lag & ping requests */ stop_stuff(fr.callno); stop_stuff(iaxs[fr.callno]->bridgecallno); /* Send the release message */ snprintf(rel0, sizeof(rel0), "peercallno=%d;", iaxs[iaxs[fr.callno]->bridgecallno]->peercallno); snprintf(rel1, sizeof(rel1), "peercallno=%d;", iaxs[fr.callno]->peercallno); send_command(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_TXREL, 0, rel0, strlen(rel0)+1, -1); send_command(iaxs[iaxs[fr.callno]->bridgecallno], AST_FRAME_IAX, AST_IAX_COMMAND_TXREL, 0, rel1, strlen(rel1)+1, -1); } } } break; case AST_IAX_COMMAND_TXREQ: if (f.data) ((char *)f.data)[f.datalen] = '\0'; try_transfer(iaxs[fr.callno], (char *)f.data); break; case AST_IAX_COMMAND_TXCNT: if (iaxs[fr.callno]->transferring) send_command_transfer(iaxs[fr.callno], AST_FRAME_IAX, AST_IAX_COMMAND_TXACC, 0, NULL, 0); break; case AST_IAX_COMMAND_TXREL: if (f.data) ((char *)f.data)[f.datalen] = '\0'; complete_transfer(fr.callno, (char *)f.data); break; case AST_IAX_COMMAND_DPREP: if (f.data) ((char *)f.data)[f.datalen] = '\0'; complete_dpreply(iaxs[fr.callno], (char *)f.data); break; default: ast_log(LOG_DEBUG, "Unknown IAX command %d on %d/%d\n", f.subclass, fr.callno, iaxs[fr.callno]->peercallno); } /* Don't actually pass these frames along */ ast_pthread_mutex_unlock(&iaxs_lock); return 1; } } else { /* A mini frame */ f.frametype = AST_FRAME_VOICE; if (iaxs[fr.callno]->voiceformat > 0) f.subclass = iaxs[fr.callno]->voiceformat; else { ast_log(LOG_WARNING, "Received mini frame before first full voice frame\n "); iax_vnak(fr.callno); ast_pthread_mutex_unlock(&iaxs_lock); return 1; } f.datalen = res - sizeof(struct ast_iax_mini_hdr); if (f.datalen < 0) { ast_log(LOG_WARNING, "Datalen < 0?\n"); ast_pthread_mutex_unlock(&iaxs_lock); return 1; } if (f.datalen) f.data = buf + sizeof(struct ast_iax_mini_hdr); else f.data = NULL; fr.ts = (iaxs[fr.callno]->last & 0xFFFF0000L) | ntohs(mh->ts); } /* Don't pass any packets until we're started */ if (!(iaxs[fr.callno]->state & IAX_STATE_STARTED)) { ast_pthread_mutex_unlock(&iaxs_lock); return 1; } /* Common things */ snprintf(src, sizeof(src), "IAX/%s/%d", inet_ntoa(sin.sin_addr),fr.callno); f.src = src; f.mallocd = 0; f.offset = 0; fr.f = &f; if (f.datalen && (f.frametype == AST_FRAME_VOICE)) f.timelen = get_timelen(&f); else f.timelen = 0; /* If this is our most recent packet, use it as our basis for timestamping */ if (iaxs[fr.callno]->last < fr.ts) { iaxs[fr.callno]->last = fr.ts; fr.outoforder = 0; } else { if (option_debug) ast_log(LOG_DEBUG, "Received out of order packet... (type=%d, subclass %d, ts = %d, last = %d)\n", f.frametype, f.subclass, fr.ts, iaxs[fr.callno]->last); fr.outoforder = -1; } #ifdef BRIDGE_OPTIMIZATION if (iaxs[fr.callno]->bridgecallno > -1) { forward_delivery(&fr); } else { schedule_delivery(iaxfrdup2(&fr, 0), 1); } #else schedule_delivery(iaxfrdup2(&fr, 0), 1); #endif /* Always run again */ ast_pthread_mutex_unlock(&iaxs_lock); return 1; } static int iax_do_register(struct iax_registry *reg) { char requeststr[256] = ""; if (option_debug) ast_log(LOG_DEBUG, "Sending registration request for '%s'\n", reg->username); if (reg->callno < 0) { if (option_debug) ast_log(LOG_DEBUG, "Allocate call number\n"); reg->callno = find_callno(-1, -1, ®->addr, NEW_FORCE); if (reg->callno < 0) { ast_log(LOG_WARNING, "Unable to create call for registration\n"); return -1; } else if (option_debug) ast_log(LOG_DEBUG, "Registration created on call %d\n", reg->callno); iaxs[reg->callno]->reg = reg; } /* Schedule the next registration attempt */ if (reg->expire > -1) ast_sched_del(sched, reg->expire); /* Setup the registration a little early */ reg->expire = ast_sched_add(sched, (5 * reg->refresh / 6) * 1000, iax_do_register_s, reg); /* Send the request */ MYSNPRINTF "peer=%s;refresh=%d;", reg->username, reg->refresh); send_command(iaxs[reg->callno],AST_FRAME_IAX, AST_IAX_COMMAND_REGREQ, 0, requeststr, strlen(requeststr) + 1, -1); reg->regstate = REG_STATE_REGSENT; return 0; } static void free_context(struct iax_context *con) { struct iax_context *conl; while(con) { conl = con; con = con->next; free(conl); } } static struct ast_channel *iax_request(char *type, int format, void *data) { int callno; int res; int sendani; int fmt, native; struct sockaddr_in sin; char s[256]; char *st; struct ast_channel *c; int capability = iax_capability; strncpy(s, (char *)data, sizeof(s)-1); strtok(s, "/"); strtok(s, "@"); st = strtok(NULL, "@"); if (!st) st = s; /* Populate our address from the given */ if (create_addr(&sin, &capability, &sendani, st, NULL)) { return NULL; } ast_pthread_mutex_lock(&iaxs_lock); callno = find_callno(-1, -1, &sin, NEW_FORCE); if (callno < 0) { ast_log(LOG_WARNING, "Unable to create call\n"); return NULL; } /* Keep track of sendani flag */ iaxs[callno]->sendani = sendani; c = ast_iax_new(iaxs[callno], AST_STATE_DOWN, capability); if (c) { /* Choose a format we can live with */ if (c->nativeformats & format) c->nativeformats &= format; else { native = c->nativeformats; fmt = format; res = ast_translator_best_choice(&fmt, &native); if (res < 0) { ast_log(LOG_WARNING, "Unable to create translator path for %d to %d on %s\n", c->nativeformats, fmt, c->name); ast_hangup(c); ast_pthread_mutex_unlock(&iaxs_lock); return NULL; } c->nativeformats = native; } } ast_pthread_mutex_unlock(&iaxs_lock); return c; } 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; struct ast_iax_frame *f, *freeme; /* Establish I/O callback for socket read */ ast_io_add(io, netsocket, socket_read, AST_IO_IN, NULL); ast_pthread_mutex_lock(&iaxs_lock); for(;;) { /* Go through the queue, sending messages which have not yet been sent, and scheduling retransmissions if appropriate */ ast_pthread_mutex_lock(&iaxq.lock); f = iaxq.head; while(f) { freeme = NULL; if (!f->sentyet) { f->sentyet++; /* Send a copy immediately */ if (iaxs[f->callno]) { send_packet(f); } if (f->retries < 0) { /* This is not supposed to be retransmitted */ if (f->prev) f->prev->next = f->next; else iaxq.head = f->next; if (f->next) f->next->prev = f->prev; else iaxq.tail = f->prev; iaxq.count--; /* Free the frame */ ast_frfree(f->f); f->f = NULL; /* Free the iax frame */ freeme = f; } else { /* We need reliable delivery. Schedule a retransmission */ f->retries++; f->retrans = ast_sched_add(sched, f->retrytime, attempt_transmit, f); } } f = f->next; if (freeme) ast_iax_frame_free(freeme); } ast_pthread_mutex_unlock(&iaxq.lock); ast_pthread_mutex_unlock(&iaxs_lock); res = ast_sched_wait(sched); res = ast_io_wait(io, res); ast_pthread_mutex_lock(&iaxs_lock); if (res >= 0) { ast_sched_runq(sched); } } return NULL; } static int start_network_thread() { return pthread_create(&netthreadid, NULL, network_thread, NULL); } static struct iax_context *build_context(char *context) { struct iax_context *con = malloc(sizeof(struct iax_context)); if (con) { strncpy(con->context, context, sizeof(con->context)-1); con->next = NULL; } return con; } static struct iax_peer *build_peer(char *name, struct ast_variable *v) { struct iax_peer *peer; struct iax_peer *prev; int maskfound=0; int format; int found=0; prev = NULL; ast_pthread_mutex_lock(&peerl.lock); peer = peerl.peers; while(peer) { if (!strcasecmp(peer->name, name)) { break; } prev = peer; peer = peer->next; } if (peer) { found++; /* Already in the list, remove it and it will be added back (or FREE'd) */ if (prev) { prev->next = peer->next; } else { peerl.peers = peer->next; } ast_pthread_mutex_unlock(&peerl.lock); } else { ast_pthread_mutex_unlock(&peerl.lock); peer = malloc(sizeof(struct iax_peer)); memset(peer, 0, sizeof(struct iax_peer)); peer->expire = -1; } if (peer) { if (!found) { strncpy(peer->name, name, sizeof(peer->name)-1); peer->addr.sin_port = htons(AST_DEFAULT_IAX_PORTNO); peer->expirey = expirey; } peer->capability = iax_capability; while(v) { if (!strcasecmp(v->name, "secret")) strncpy(peer->secret, v->value, sizeof(peer->secret)-1); else if (!strcasecmp(v->name, "auth")) strncpy(peer->methods, v->value, sizeof(peer->methods)-1); else if (!strcasecmp(v->name, "host")) { if (!strcasecmp(v->value, "dynamic")) { /* They'll register with us */ peer->dynamic = 1; if (!found) { /* Initialize stuff iff we're not found, otherwise we keep going with what we had */ memset(&peer->addr.sin_addr, 0, 4); if (peer->addr.sin_port) { /* If we've already got a port, make it the default rather than absolute */ peer->defaddr.sin_port = peer->addr.sin_port; peer->addr.sin_port = 0; } } } else { /* Non-dynamic. Make sure we become that way if we're not */ if (peer->expire > -1) ast_sched_del(sched, peer->expire); peer->expire = -1; peer->dynamic = 0; if (ast_get_ip(&peer->addr, v->value)) { free(peer); return NULL; } } if (!maskfound) inet_aton("255.255.255.255", &peer->mask); } else if (!strcasecmp(v->name, "defaultip")) { if (ast_get_ip(&peer->defaddr, v->value)) { free(peer); return NULL; } } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { peer->ha = ast_append_ha(v->name, v->value, peer->ha); } else if (!strcasecmp(v->name, "mask")) { maskfound++; inet_aton(v->value, &peer->mask); } else if (!strcasecmp(v->name, "context")) { if (!strlen(peer->context)) strncpy(peer->context, v->value, sizeof(peer->context) - 1); } else if (!strcasecmp(v->name, "port")) { if (peer->dynamic) peer->defaddr.sin_port = htons(atoi(v->value)); else peer->addr.sin_port = htons(atoi(v->value)); } else if (!strcasecmp(v->name, "username")) { strncpy(peer->username, v->value, sizeof(peer->username)-1); } else if (!strcasecmp(v->name, "allow")) { format = ast_getformatbyname(v->value); if (format < 1) ast_log(LOG_WARNING, "Cannot allow unknown format '%s'\n", v->value); else peer->capability |= format; } else if (!strcasecmp(v->name, "disallow")) { format = ast_getformatbyname(v->value); if (format < 1) ast_log(LOG_WARNING, "Cannot disallow unknown format '%s'\n", v->value); else peer->capability &= ~format; } else if (!strcasecmp(v->name, "callerid")) { strncpy(peer->callerid, v->value, sizeof(peer->callerid)-1); peer->hascallerid=1; } else if (!strcasecmp(v->name, "sendani")) { peer->sendani = ast_true(v->value); } else if (!strcasecmp(v->name, "inkeys")) { strncpy(peer->inkeys, v->value, sizeof(peer->inkeys)); } else if (!strcasecmp(v->name, "outkey")) { strncpy(peer->outkey, v->value, sizeof(peer->outkey)); } v=v->next; } if (!strlen(peer->methods)) strcpy(peer->methods, "md5,plaintext"); peer->delme = 0; } return peer; } static struct iax_user *build_user(char *name, struct ast_variable *v) { struct iax_user *user; struct iax_context *con, *conl = NULL; int format; user = (struct iax_user *)malloc(sizeof(struct iax_user)); if (user) { memset(user, 0, sizeof(struct iax_user)); strncpy(user->name, name, sizeof(user->name)-1); while(v) { if (!strcasecmp(v->name, "context")) { con = build_context(v->value); if (con) { if (conl) conl->next = con; else user->contexts = con; conl = con; } } else if (!strcasecmp(v->name, "permit") || !strcasecmp(v->name, "deny")) { user->ha = ast_append_ha(v->name, v->value, user->ha); } else if (!strcasecmp(v->name, "auth")) { strncpy(user->methods, v->value, sizeof(user->methods)-1); } else if (!strcasecmp(v->name, "secret")) { strncpy(user->secret, v->value, sizeof(user->secret)-1); } else if (!strcasecmp(v->name, "callerid")) { strncpy(user->callerid, v->value, sizeof(user->callerid)-1); user->hascallerid=1; } else if (!strcasecmp(v->name, "accountcode")) { strncpy(user->accountcode, v->value, sizeof(user->accountcode)-1); } else if (!strcasecmp(v->name, "amaflags")) { format = ast_cdr_amaflags2int(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno); } else { user->amaflags = format; } } else if (!strcasecmp(v->name, "inkeys")) { strncpy(user->inkeys, v->value, sizeof(user->inkeys)); } v = v->next; } } if (!strlen(user->methods)) { if (strlen(user->secret)) { strncpy(user->methods, "md5,plaintext", sizeof(user->methods) - 1); if (strlen(user->inkeys)) strncat(user->methods, ",rsa", sizeof(user->methods) - 1); } else if (strlen(user->inkeys)) { strncpy(user->methods, "rsa", sizeof(user->methods) - 1); } else strncpy(user->methods, "md5,plaintext", sizeof(user->methods) -1); } return user; } void delete_users(void){ struct iax_user *user, *userlast; struct iax_peer *peer; struct iax_registry *reg, *regl; /* Delete all users */ ast_pthread_mutex_lock(&userl.lock); for (user=userl.users;user;) { ast_free_ha(user->ha); free_context(user->contexts); userlast = user; user=user->next; free(userlast); } userl.users=NULL; ast_pthread_mutex_unlock(&userl.lock); for (reg = registrations;reg;) { regl = reg; reg = reg->next; if (regl->expire > -1) ast_sched_del(sched, regl->expire); free(regl); } registrations = NULL; ast_pthread_mutex_lock(&peerl.lock); for (peer=peerl.peers;peer;) { /* Assume all will be deleted, and we'll find out for sure later */ peer->delme = 1; peer = peer->next; } ast_pthread_mutex_unlock(&peerl.lock); } void prune_peers(void){ /* Prune peers who still are supposed to be deleted */ struct iax_peer *peer, *peerlast, *peernext; ast_pthread_mutex_lock(&peerl.lock); peerlast = NULL; for (peer=peerl.peers;peer;) { peernext = peer->next; if (peer->delme) { /* Delete it, it needs to disappear */ if (peer->expire > -1) ast_sched_del(sched, peer->expire); free(peer); if (peerlast) peerlast->next = peernext; else peerl.peers = peernext; } else peerlast = peer; peer=peernext; } ast_pthread_mutex_unlock(&peerl.lock); } int set_config(char *config_file, struct sockaddr_in* sin){ struct ast_config *cfg; int capability=iax_capability; struct ast_variable *v; char *cat; char *utype; int format; struct iax_user *user; struct iax_peer *peer; static unsigned short int last_port=0; cfg = ast_load(config_file); if (!cfg) { ast_log(LOG_ERROR, "Unable to load config %s\n", config_file); return -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; #if 0 ast_verbose("setting last port\n"); #endif } 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, "pingtime")) ping_time = atoi(v->value); else if (!strcasecmp(v->name, "maxjitterbuffer")) maxjitterbuffer = atoi(v->value); else if (!strcasecmp(v->name, "maxexcessbuffer")) max_jitter_buffer = atoi(v->value); else if (!strcasecmp(v->name, "lagrqtime")) lagrq_time = atoi(v->value); else if (!strcasecmp(v->name, "dropcount")) iax_dropcount = atoi(v->value); else if (!strcasecmp(v->name, "bindaddr")) inet_aton(v->value, &sin->sin_addr); else if (!strcasecmp(v->name, "jitterbuffer")) use_jitterbuffer = ast_true(v->value); else if (!strcasecmp(v->name, "bandwidth")) { if (!strcasecmp(v->value, "low")) { capability = IAX_CAPABILITY_LOWBANDWIDTH; } else if (!strcasecmp(v->value, "medium")) { capability = IAX_CAPABILITY_MEDBANDWIDTH; } else if (!strcasecmp(v->value, "high")) { capability = IAX_CAPABILITY_FULLBANDWIDTH; } else ast_log(LOG_WARNING, "bandwidth must be either low, medium, or high\n"); } else if (!strcasecmp(v->name, "allow")) { format = ast_getformatbyname(v->value); if (format < 1) ast_log(LOG_WARNING, "Cannot allow unknown format '%s'\n", v->value); else capability |= format; } else if (!strcasecmp(v->name, "disallow")) { format = ast_getformatbyname(v->value); if (format < 1) ast_log(LOG_WARNING, "Cannot disallow unknown format '%s'\n", v->value); else capability &= ~format; } else if (!strcasecmp(v->name, "register")) { iax_register(v->value, v->lineno); } else if (!strcasecmp(v->name, "tos")) { if (!strcasecmp(v->value, "lowdelay")) tos = IPTOS_LOWDELAY; else if (!strcasecmp(v->value, "throughput")) tos = IPTOS_THROUGHPUT; else if (!strcasecmp(v->value, "reliability")) tos = IPTOS_RELIABILITY; else if (!strcasecmp(v->value, "mincost")) tos = IPTOS_MINCOST; else if (!strcasecmp(v->value, "none")) tos = 0; else ast_log(LOG_WARNING, "Invalid tos value at line %d, should be 'lowdelay', 'throughput', 'reliability', 'mincost', or 'none'\n", v->lineno); } else if (!strcasecmp(v->name, "accountcode")) { strncpy(accountcode, v->value, sizeof(accountcode)-1); } else if (!strcasecmp(v->name, "amaflags")) { format = ast_cdr_amaflags2int(v->value); if (format < 0) { ast_log(LOG_WARNING, "Invalid AMA Flags: %s at line %d\n", v->value, v->lineno); } else { amaflags = format; } } v = v->next; } iax_capability = capability; cat = ast_category_browse(cfg, NULL); while(cat) { if (strcasecmp(cat, "general")) { utype = ast_variable_retrieve(cfg, cat, "type"); if (utype) { if (!strcasecmp(utype, "user") || !strcasecmp(utype, "friend")) { user = build_user(cat, ast_variable_browse(cfg, cat)); if (user) { ast_pthread_mutex_lock(&userl.lock); user->next = userl.users; userl.users = user; ast_pthread_mutex_unlock(&userl.lock); } } if (!strcasecmp(utype, "peer") || !strcasecmp(utype, "friend")) { peer = build_peer(cat, ast_variable_browse(cfg, cat)); if (peer) { ast_pthread_mutex_lock(&peerl.lock); peer->next = peerl.peers; peerl.peers = peer; ast_pthread_mutex_unlock(&peerl.lock); } } else if (strcasecmp(utype, "user")) { ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, config_file); } } else ast_log(LOG_WARNING, "Section '%s' lacks type\n", cat); } cat = ast_category_browse(cfg, cat); } ast_destroy(cfg); return capability; } static int reload_config(void) { char *config = "iax.conf"; struct iax_registry *reg; struct sockaddr_in dead_sin; strncpy(accountcode, "", sizeof(accountcode)-1); amaflags = 0; srand(time(NULL)); delete_users(); set_config(config,&dead_sin); prune_peers(); for (reg = registrations; reg; reg = reg->next) iax_do_register(reg); return 0; } int reload(void) { return reload_config(); } static int cache_get_callno(char *data) { struct sockaddr_in sin; int x; char st[256], *s; char *host; char *username=NULL; char *password=NULL; char *context=NULL; char requeststr[256] = ""; int callno; for (x=0;xdproot)) return x; } /* No match found, we need to create a new one */ strncpy(st, data, sizeof(st)-1); /* Grab the host */ s = strchr(st, '/'); if (s) { *s = '\0'; s++; context = s; } s = strchr(st, '@'); if (s) { /* Get username/password if there is one */ *s='\0'; username=st; password = strchr(username, ':'); if (password) { *password = '\0'; password++; } s++; host = s; } else { /* Just a hostname */ host = st; } /* Populate our address from the given */ if (create_addr(&sin, NULL, NULL, host, NULL)) { return -1; } ast_log(LOG_DEBUG, "host: %s, user: %s, password: %s, context: %s\n", host, username, password, context); ast_pthread_mutex_lock(&iaxs_lock); callno = find_callno(-1, -1, &sin, NEW_FORCE); ast_pthread_mutex_unlock(&iaxs_lock); strncpy(iaxs[callno]->dproot, data, sizeof(iaxs[callno]->dproot)-1); if (callno < 0) { ast_log(LOG_WARNING, "Unable to create call\n"); return -1; } iaxs[callno]->capability = IAX_CAPABILITY_FULLBANDWIDTH; MYSNPRINTF "exten=TBD;"); if (context) MYSNPRINTF "context=%s;", context); if (username) MYSNPRINTF "username=%s;", username); /* Remember, codec is irrelevent */ MYSNPRINTF "formats=%d;", IAX_CAPABILITY_FULLBANDWIDTH); MYSNPRINTF "capability=%d;", IAX_CAPABILITY_FULLBANDWIDTH); MYSNPRINTF "version=%d;", AST_IAX_PROTO_VERSION); if (strlen(requeststr)) requeststr[strlen(requeststr) -1 ] = '\0'; /* Keep password handy */ if (password) strncpy(iaxs[callno]->secret, password, sizeof(iaxs[callno]->secret)-1); if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Calling TBD using options '%s'\n", requeststr); /* Start the call going */ send_command(iaxs[callno], AST_FRAME_IAX, AST_IAX_COMMAND_NEW, 0, requeststr, strlen(requeststr) + 1, -1); return callno; } static struct iax_dpcache *find_cache(struct ast_channel *chan, char *data, char *context, char *exten, int priority) { struct iax_dpcache *dp, *prev = NULL, *next; struct timeval tv; int x; int com[2]; int timeout; int old=0; int outfd; int abort; int callno; struct ast_channel *c; struct ast_frame *f; gettimeofday(&tv, NULL); dp = dpcache; while(dp) { next = dp->next; /* Expire old caches */ if ((tv.tv_sec > dp->expirey.tv_sec) || ((tv.tv_sec == dp->expirey.tv_sec) && (tv.tv_usec > dp->expirey.tv_usec))) { /* It's expired, let it disappear */ if (prev) prev->next = dp->next; else dpcache = dp->next; if (!dp->peer && !(dp->flags & CACHE_FLAG_PENDING) && (dp->callno == -1)) { /* Free memory and go again */ free(dp); } else { ast_log(LOG_WARNING, "DP still has peer field or pending or callno (flags = %d, peer = %p callno = %d)\n", dp->flags, dp->peer, dp->callno); } dp = next; continue; } /* We found an entry that matches us! */ if (!strcmp(dp->peercontext, data) && !strcmp(dp->exten, exten)) break; dp = next; } if (!dp) { /* No matching entry. Create a new one. */ /* First, can we make a callno? */ callno = cache_get_callno(data); if (callno < 0) { ast_log(LOG_WARNING, "Unable to generate call for '%s'\n", data); return NULL; } dp = malloc(sizeof(struct iax_dpcache)); if (!dp) return NULL; memset(dp, 0, sizeof(struct iax_dpcache)); dp->callno = -1; strncpy(dp->peercontext, data, sizeof(dp->peercontext)-1); strncpy(dp->exten, exten, sizeof(dp->exten)-1); gettimeofday(&dp->expirey, NULL); dp->orig = dp->expirey; /* Expires in 30 mins by default */ dp->expirey.tv_sec += iaxdefaultdpcache; dp->next = dpcache; dp->flags = CACHE_FLAG_PENDING; for (x=0;xwaiters) / sizeof(dp->waiters[0]); x++) dp->waiters[x] = -1; dpcache = dp; dp->peer = iaxs[callno]->dpentries; iaxs[callno]->dpentries = dp; /* Send the request if we're already up */ if (iaxs[callno]->state & IAX_STATE_STARTED) iax_dprequest(dp, callno); } /* By here we must have a dp */ if (dp->flags & CACHE_FLAG_PENDING) { /* Okay, here it starts to get nasty. We need a pipe now to wait for a reply to come back so long as it's pending */ for (x=0;xwaiters) / sizeof(dp->waiters[0]); x++) { /* Find an empty slot */ if (dp->waiters[x] < 0) break; } if (x >= sizeof(dp->waiters) / sizeof(dp->waiters[0])) { ast_log(LOG_WARNING, "No more waiter positions available\n"); return NULL; } if (pipe(com)) { ast_log(LOG_WARNING, "Unable to create pipe for comm\n"); return NULL; } dp->waiters[x] = com[1]; /* Okay, now we wait */ timeout = iaxdefaulttimeout * 1000; /* Temporarily unlock */ ast_pthread_mutex_unlock(&dpcache_lock); /* Defer any dtmf */ if (chan) old = ast_channel_defer_dtmf(chan); abort = 0; while(timeout) { c = ast_waitfor_nandfds(&chan, chan ? 1 : 0, &com[0], 1, NULL, &outfd, &timeout); if (outfd > -1) { break; } if (c) { f = ast_read(c); if (f) ast_frfree(f); else { /* Got hung up on, abort! */ break; abort = 1; } } } if (!timeout) { ast_log(LOG_WARNING, "Timeout waiting for %s exten %s\n", data, exten); } ast_pthread_mutex_lock(&dpcache_lock); dp->waiters[x] = -1; close(com[1]); close(com[0]); if (abort) { /* Don't interpret anything, just abort. Not sure what th epoint of undeferring dtmf on a hung up channel is but hey whatever */ if (!old && chan) ast_channel_undefer_dtmf(chan); return NULL; } if (!(dp->flags & CACHE_FLAG_TIMEOUT)) { /* Now to do non-independent analysis the results of our wait */ if (dp->flags & CACHE_FLAG_PENDING) { /* Still pending... It's a timeout. Wake everybody up. Consider it no longer pending. Don't let it take as long to timeout. */ dp->flags &= ~CACHE_FLAG_PENDING; dp->flags |= CACHE_FLAG_TIMEOUT; /* Expire after only 60 seconds now. This is designed to help reduce backlog in heavily loaded systems without leaving it unavailable once the server comes back online */ dp->expirey.tv_sec = dp->orig.tv_sec + 60; for (x=0;xwaiters) / sizeof(dp->waiters[0]); x++) if (dp->waiters[x] > -1) write(dp->waiters[x], "asdf", 4); } } /* Our caller will obtain the rest */ if (!old && chan) ast_channel_undefer_dtmf(chan); } return dp; } static int iax_exists(struct ast_channel *chan, char *context, char *exten, int priority, char *callerid, char *data) { struct iax_dpcache *dp; int res = 0; #if 0 ast_log(LOG_NOTICE, "iax_exists: con: %s, exten: %s, pri: %d, cid: %s, data: %s\n", context, exten, priority, callerid ? callerid : "", data); #endif if (priority != 1) return 0; ast_pthread_mutex_lock(&dpcache_lock); dp = find_cache(chan, data, context, exten, priority); if (dp) { if (dp->flags & CACHE_FLAG_EXISTS) res= 1; } ast_pthread_mutex_unlock(&dpcache_lock); if (!dp) { ast_log(LOG_WARNING, "Unable to make DP cache\n"); } return res; } static int iax_canmatch(struct ast_channel *chan, char *context, char *exten, int priority, char *callerid, char *data) { int res = 0; struct iax_dpcache *dp; #if 0 ast_log(LOG_NOTICE, "iax_canmatch: con: %s, exten: %s, pri: %d, cid: %s, data: %s\n", context, exten, priority, callerid ? callerid : "", data); #endif if (priority != 1) return 0; ast_pthread_mutex_lock(&dpcache_lock); dp = find_cache(chan, data, context, exten, priority); if (dp) { if (dp->flags & CACHE_FLAG_CANEXIST) res= 1; } ast_pthread_mutex_unlock(&dpcache_lock); if (!dp) { ast_log(LOG_WARNING, "Unable to make DP cache\n"); } return res; } static int iax_exec(struct ast_channel *chan, char *context, char *exten, int priority, char *callerid, int newstack, char *data) { char odata[256]; char req[256]; char *ncontext; struct iax_dpcache *dp; struct ast_app *dial; #if 0 ast_log(LOG_NOTICE, "iax_exec: con: %s, exten: %s, pri: %d, cid: %s, data: %s, newstack: %d\n", context, exten, priority, callerid ? callerid : "", data, newstack); #endif if (priority != 1) return -1; ast_pthread_mutex_lock(&dpcache_lock); dp = find_cache(chan, data, context, exten, priority); if (dp) { if (dp->flags & CACHE_FLAG_EXISTS) { strncpy(odata, data, sizeof(odata)-1); ncontext = strchr(odata, '/'); if (ncontext) { *ncontext = '\0'; ncontext++; snprintf(req, sizeof(req), "IAX/%s/%s@%s", odata, exten, ncontext); } else { snprintf(req, sizeof(req), "IAX/%s/%s", odata, exten); } if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Executing Dial('%s')\n", req); } else { ast_pthread_mutex_unlock(&dpcache_lock); ast_log(LOG_WARNING, "Can't execute non-existant extension '%s[@%s]' in data '%s'\n", exten, context, data); return -1; } } ast_pthread_mutex_unlock(&dpcache_lock); dial = pbx_findapp("Dial"); if (dial) { pbx_exec(chan, dial, req, newstack); } else { ast_log(LOG_WARNING, "No dial application registered\n"); } return -1; } static struct ast_switch iax_switch = { name: "IAX", description: "IAX Remote Dialplan Switch", exists: iax_exists, canmatch: iax_canmatch, exec: iax_exec, }; int load_module(void) { char *config = "iax.conf"; int res = 0; struct iax_registry *reg; struct sockaddr_in sin; /* Seed random number generator */ srand(time(NULL)); sin.sin_family = AF_INET; sin.sin_port = ntohs(AST_DEFAULT_IAX_PORTNO); sin.sin_addr.s_addr = INADDR_ANY; io = io_context_create(); sched = sched_context_create(); if (!io || !sched) { ast_log(LOG_ERROR, "Out of memory\n"); return -1; } ast_pthread_mutex_init(&iaxq.lock); ast_pthread_mutex_init(&userl.lock); ast_cli_register(&cli_show_users); ast_cli_register(&cli_show_channels); ast_cli_register(&cli_show_peers); ast_cli_register(&cli_show_registry); ast_cli_register(&cli_debug); ast_cli_register(&cli_no_debug); ast_cli_register(&cli_set_jitter); ast_cli_register(&cli_show_stats); ast_cli_register(&cli_show_cache); set_config(config,&sin); if (ast_channel_register(type, tdesc, iax_capability, iax_request)) { ast_log(LOG_ERROR, "Unable to register channel class %s\n", type); unload_module(); return -1; } if (ast_register_switch(&iax_switch)) ast_log(LOG_ERROR, "Unable to register IAX switch\n"); /* Make a UDP socket */ 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", inet_ntoa(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, SOL_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 "IAX Ready and Listening on %s port %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); } else { ast_log(LOG_ERROR, "Unable to start network thread\n"); close(netsocket); } for (reg = registrations; reg; reg = reg->next) iax_do_register(reg); return res; } char *description() { return desc; } int unload_module() { int x; /* Cancel the network thread, close the net socket */ pthread_cancel(netthreadid); pthread_join(netthreadid, NULL); close(netsocket); for (x=0;x