Newer
Older
/* if it's actually an IP address and not a name,
there's no need for a managed lookup */
if (!inet_aton(proxy->name, &proxy->ip.sin_addr)) {
/* Ok, not an IP address, then let's check if it's a domain or host */
/* XXX Todo - if we have proxy port, don't do SRV */
if (ast_get_ip_or_srv(&proxy->ip, proxy->name, global_srvlookup ? "_sip._udp" : NULL) < 0) {
ast_log(LOG_WARNING, "Unable to locate host '%s'\n", proxy->name);
return FALSE;
}
}
proxy->last_dnsupdate = time(NULL);
return TRUE;
}
/*! \brief Allocate and initialize sip proxy */
static struct sip_proxy *proxy_allocate(char *name, char *port, int force)
{
struct sip_proxy *proxy;
Tilghman Lesher
committed
proxy = ast_calloc(1, sizeof(*proxy));
if (!proxy)
return NULL;
proxy->force = force;
ast_copy_string(proxy->name, name, sizeof(proxy->name));
Joshua Colp
committed
proxy->ip.sin_port = htons((!ast_strlen_zero(port) ? atoi(port) : STANDARD_SIP_PORT));
proxy_update(proxy);
return proxy;
}
/*! \brief Get default outbound proxy or global proxy */
static struct sip_proxy *obproxy_get(struct sip_pvt *dialog, struct sip_peer *peer)
{
if (peer && peer->outboundproxy) {
if (sipdebug)
ast_debug(1, "OBPROXY: Applying peer OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using peer obproxy %s", peer->outboundproxy->name);
return peer->outboundproxy;
}
if (global_outboundproxy.name[0]) {
if (sipdebug)
ast_debug(1, "OBPROXY: Applying global OBproxy to this call\n");
append_history(dialog, "OBproxy", "Using global obproxy %s", global_outboundproxy.name);
return &global_outboundproxy;
}
if (sipdebug)
ast_debug(1, "OBPROXY: Not applying OBproxy to this call\n");
return NULL;
}
/*! \brief returns true if 'name' (with optional trailing whitespace)
* matches the sip method 'id'.
* Strictly speaking, SIP methods are case SENSITIVE, but we do
* a case-insensitive comparison to be more tolerant.
* following Jon Postel's rule: Be gentle in what you accept, strict with what you send
*/
static int method_match(enum sipmethod id, const char *name)
{
int len = strlen(sip_methods[id].text);
int l_name = name ? strlen(name) : 0;
/* true if the string is long enough, and ends with whitespace, and matches */
return (l_name >= len && name[len] < 33 &&
!strncasecmp(sip_methods[id].text, name, len));
}
/*! \brief find_sip_method: Find SIP method from header */
static int find_sip_method(const char *msg)
{
int i, res = 0;
if (ast_strlen_zero(msg))
return 0;
for (i = 1; i < (sizeof(sip_methods) / sizeof(sip_methods[0])) && !res; i++) {
res = sip_methods[i].id;
}
return res;
}
/*! \brief Parse supported header in incoming packet */
static unsigned int parse_sip_options(struct sip_pvt *pvt, const char *supported)
{
char *temp;
unsigned int profile = 0;
return 0;
temp = ast_strdupa(supported);
if (sipdebug)
ast_debug(3, "Begin: parsing SIP \"Supported: %s\"\n", supported);
if ( (sep = strchr(next, ',')) != NULL)
*sep++ = '\0';
next = ast_skip_blanks(next);
if (sipdebug)
ast_debug(3, "Found SIP option: -%s-\n", next);
for (i=0; i < (sizeof(sip_options) / sizeof(sip_options[0])); i++) {
if (!strcasecmp(next, sip_options[i].text)) {
profile |= sip_options[i].id;
if (sipdebug)
ast_debug(3, "Matched SIP option: %s\n", next);
}
}
if (!found && sipdebug) {
if (!strncasecmp(next, "x-", 2))
ast_debug(3, "Found private SIP option, not supported: %s\n", next);
else
ast_debug(3, "Found no match for SIP option: %s (Please file bug report!)\n", next);
}
if (pvt)
pvt->sipoptions = profile;
}
/*! \brief See if we pass debug IP filter */
static inline int sip_debug_test_addr(const struct sockaddr_in *addr)
Russell Bryant
committed
if (!sipdebug)
return 0;
if (debugaddr.sin_addr.s_addr) {
if (((ntohs(debugaddr.sin_port) != 0)
&& (debugaddr.sin_port != addr->sin_port))
|| (debugaddr.sin_addr.s_addr != addr->sin_addr.s_addr))
return 0;
}
return 1;
}
Olle Johansson
committed
/*! \brief The real destination address for a write */
static const struct sockaddr_in *sip_real_dst(const struct sip_pvt *p)
{
if (p->outboundproxy)
return &p->outboundproxy->ip;
return ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE ? &p->recv : &p->sa;
}
Olle Johansson
committed
/*! \brief Display SIP nat mode */
static const char *sip_nat_mode(const struct sip_pvt *p)
{
return ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_ROUTE ? "NAT" : "no NAT";
}
/*! \brief Test PVT for debugging output */
static inline int sip_debug_test_pvt(struct sip_pvt *p)
{
Russell Bryant
committed
if (!sipdebug)
return sip_debug_test_addr(sip_real_dst(p));
/*! \brief Transmit SIP message */
static int __sip_xmit(struct sip_pvt *p, char *data, int len)
{
int res;
const struct sockaddr_in *dst = sip_real_dst(p);
res = sendto(sipsock, data, len, 0, (const struct sockaddr *)dst, sizeof(struct sockaddr_in));
Olle Johansson
committed
if (res == -1) {
switch (errno) {
case EBADF: /* Bad file descriptor - seems like this is generated when the host exist, but doesn't accept the UDP packet */
case EHOSTUNREACH: /* Host can't be reached */
case ENETDOWN: /* Interface down */
Olle Johansson
committed
case ENETUNREACH: /* Network failure */
res = XMIT_ERROR; /* Don't bother with trying to transmit again */
Olle Johansson
committed
}
}
Russell Bryant
committed
ast_log(LOG_WARNING, "sip_xmit of %p (len %d) to %s:%d returned %d: %s\n", data, len, ast_inet_ntoa(dst->sin_addr), ntohs(dst->sin_port), res, strerror(errno));
/*! \brief Build a Via header for a request */
static void build_via(struct sip_pvt *p)
/* Work around buggy UNIDEN UIP200 firmware */
const char *rport = ast_test_flag(&p->flags[0], SIP_NAT) & SIP_NAT_RFC3581 ? ";rport" : "";
/* z9hG4bK is a magic cookie. See RFC 3261 section 8.1.1.7 */
ast_string_field_build(p, via, "SIP/2.0/UDP %s:%d;branch=z9hG4bK%08x%s",
ast_inet_ntoa(p->ourip.sin_addr),
ntohs(p->ourip.sin_port), p->branch, rport);
/*! \brief NAT fix - decide which IP address to use for Asterisk server?
*
* Using the localaddr structure built up with localnet statements in sip.conf
* apply it to their address to see if we need to substitute our
* externip or can get away with our internal bindaddr
* 'us' is always overwritten.
static void ast_sip_ouraddrfor(struct in_addr *them, struct sockaddr_in *us)
{
struct sockaddr_in theirs;
/* Set want_remap to non-zero if we want to remap 'us' to an externally
* reachable IP address and port. This is done if:
* 1. we have a localaddr list (containing 'internal' addresses marked
* as 'deny', so ast_apply_ha() will return AST_SENSE_DENY on them,
* and AST_SENSE_ALLOW on 'external' ones);
* 2. either stunaddr or externip is set, so we know what to use as the
* externally visible address;
* 3. the remote address, 'them', is external;
* 4. the address returned by ast_ouraddrfor() is 'internal' (AST_SENSE_DENY
* when passed to ast_apply_ha() so it does need to be remapped.
* This fourth condition is checked later.
*/
Jason Parker
committed
int want_remap;
Joshua Colp
committed
*us = internip; /* starting guess for the internal address */
/* now ask the system what would it use to talk to 'them' */
ast_ouraddrfor(them, &us->sin_addr);
Mark Spencer
committed
theirs.sin_addr = *them;
Jason Parker
committed
want_remap = localaddr &&
(externip.sin_addr.s_addr || stunaddr.sin_addr.s_addr) &&
ast_apply_ha(localaddr, &theirs) == AST_SENSE_ALLOW ;
if (want_remap &&
(!global_matchexterniplocally || !ast_apply_ha(localaddr, us)) ) {
/* if we used externhost or stun, see if it is time to refresh the info */
if (externexpire && time(NULL) >= externexpire) {
if (stunaddr.sin_addr.s_addr) {
ast_stun_request(sipsock, &stunaddr, NULL, &externip);
} else {
if (ast_parse_arg(externhost, PARSE_INADDR, &externip))
ast_log(LOG_NOTICE, "Warning: Re-lookup of '%s' failed!\n", externhost);
}
externexpire = time(NULL) + externrefresh;
if (externip.sin_addr.s_addr)
*us = externip;
else
ast_log(LOG_WARNING, "stun failed\n");
ast_debug(1, "Target address %s is not local, substituting externip\n",
ast_inet_ntoa(*(struct in_addr *)&them->s_addr));
} else if (bindaddr.sin_addr.s_addr) {
/* no remapping, but we bind to a specific address, so use it. */
*us = bindaddr;
}
/*! \brief Append to SIP dialog history with arg list */
Olle Johansson
committed
static void append_history_va(struct sip_pvt *p, const char *fmt, va_list ap)
Mark Spencer
committed
{
Olle Johansson
committed
char buf[80], *c = buf; /* max history length */
struct sip_history *hist;
int l;
Olle Johansson
committed
vsnprintf(buf, sizeof(buf), fmt, ap);
strsep(&c, "\r\n"); /* Trim up everything after \r or \n */
l = strlen(buf) + 1;
if (!(hist = ast_calloc(1, sizeof(*hist) + l)))
Olle Johansson
committed
return;
if (!p->history && !(p->history = ast_calloc(1, sizeof(*p->history)))) {
Tilghman Lesher
committed
ast_free(hist);
Olle Johansson
committed
return;
Mark Spencer
committed
}
Olle Johansson
committed
memcpy(hist->event, buf, l);
if (p->history_entries == MAX_HISTORY_ENTRIES) {
struct sip_history *oldest;
oldest = AST_LIST_REMOVE_HEAD(p->history, list);
p->history_entries--;
ast_free(oldest);
}
Olle Johansson
committed
AST_LIST_INSERT_TAIL(p->history, hist, list);
Olle Johansson
committed
}
/*! \brief Append to SIP dialog history with arg list */
Olle Johansson
committed
static void append_history_full(struct sip_pvt *p, const char *fmt, ...)
Olle Johansson
committed
{
Olle Johansson
committed
Olle Johansson
committed
return;
if (!p->do_history && !recordhistory && !dumphistory)
return;
va_start(ap, fmt);
append_history_va(p, fmt, ap);
va_end(ap);
Olle Johansson
committed
Olle Johansson
committed
return;
Mark Spencer
committed
}
Olle Johansson
committed
/*! \brief Retransmit SIP message if no answer (Called from scheduler) */
static int retrans_pkt(const void *data)
struct sip_pkt *pkt = (struct sip_pkt *)data, *prev, *cur = NULL;
int reschedule = DEFAULT_RETRANS;
Olle Johansson
committed
int xmitres = 0;
if (pkt->retrans < MAX_RETRANS) {
pkt->retrans++;
if (!pkt->timer_t1) { /* Re-schedule using timer_a and timer_t1 */
if (sipdebug)
ast_debug(4, "SIP TIMER: Not rescheduling id #%d:%s (Method %d) (No timer T1)\n", pkt->retransid, sip_methods[pkt->method].text, pkt->method);
} else {
int siptimer_a;
if (sipdebug)
ast_debug(4, "SIP TIMER: Rescheduling retransmission #%d (%d) %s - %d\n", pkt->retransid, pkt->retrans, sip_methods[pkt->method].text, pkt->method);
if (!pkt->timer_a)
pkt->timer_a = 2 ;
else
pkt->timer_a = 2 * pkt->timer_a;
/* For non-invites, a maximum of 4 secs */
siptimer_a = pkt->timer_t1 * pkt->timer_a; /* Double each time */
if (pkt->method != SIP_INVITE && siptimer_a > 4000)
siptimer_a = 4000;
/* Reschedule re-transmit */
reschedule = siptimer_a;
ast_debug(4, "** SIP timers: Rescheduling retransmission %d to %d ms (t1 %d ms (Retrans id #%d)) \n", pkt->retrans +1, siptimer_a, pkt->timer_t1, pkt->retransid);
if (sip_debug_test_pvt(pkt->owner)) {
const struct sockaddr_in *dst = sip_real_dst(pkt->owner);
ast_verbose("Retransmitting #%d (%s) to %s:%d:\n%s\n---\n",
pkt->retrans, sip_nat_mode(pkt->owner),
Russell Bryant
committed
ast_inet_ntoa(dst->sin_addr),
ntohs(dst->sin_port), pkt->data);
Olle Johansson
committed
append_history(pkt->owner, "ReTx", "%d %s", reschedule, pkt->data);
Olle Johansson
committed
xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen);
ast_log(LOG_WARNING, "Network error on retransmit in dialog %s\n", pkt->owner->callid);
else
Olle Johansson
committed
return reschedule;
}
/* Too many retries */
Olle Johansson
committed
if (pkt->owner && pkt->method != SIP_OPTIONS && xmitres == 0) {
if (pkt->is_fatal || sipdebug) /* Tell us if it's critical or if we're debugging */
ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s)\n",
pkt->owner->callid, pkt->seqno,
pkt->is_fatal ? "Critical" : "Non-critical", pkt->is_resp ? "Response" : "Request");
} else if (pkt->method == SIP_OPTIONS && sipdebug) {
ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) \n", pkt->owner->callid);
Olle Johansson
committed
}
if (xmitres == XMIT_ERROR) {
ast_log(LOG_WARNING, "Transmit error :: Cancelling transmission on Call ID %s\n", pkt->owner->callid);
append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
} else
append_history(pkt->owner, "MaxRetries", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
pkt->retransid = -1;
while(pkt->owner->owner && ast_channel_trylock(pkt->owner->owner)) {
sip_pvt_unlock(pkt->owner); /* SIP_PVT, not channel */
if (pkt->owner->owner && !pkt->owner->owner->hangupcause)
pkt->owner->owner->hangupcause = AST_CAUSE_NO_USER_RESPONSE;
ast_log(LOG_WARNING, "Hanging up call %s - no reply to our critical packet.\n", pkt->owner->callid);
ast_queue_hangup(pkt->owner->owner);
} else {
/* If no channel owner, destroy now */
Olle Johansson
committed
/* Let the peerpoke system expire packets when the timer expires for poke_noanswer */
if (pkt->method != SIP_OPTIONS && pkt->method != SIP_REGISTER) {
pkt->owner->needdestroy = 1;
append_history(pkt->owner, "DialogKill", "Killing this failed dialog immediately");
if (pkt->method == SIP_BYE) {
/* We're not getting answers on SIP BYE's. Tear down the call anyway. */
if (pkt->owner->owner)
ast_channel_unlock(pkt->owner->owner);
append_history(pkt->owner, "ByeFailure", "Remote peer doesn't respond to bye. Destroying call anyway.");
pkt->owner->needdestroy = 1;
/* Remove the packet */
for (prev = NULL, cur = pkt->owner->packets; cur; prev = cur, cur = cur->next) {
if (cur == pkt) {
UNLINK(cur, pkt->owner->packets, prev);
sip_pvt_unlock(pkt->owner);
ast_free(pkt);
return 0;
}
/* error case */
ast_log(LOG_WARNING, "Weird, couldn't find packet owner!\n");
sip_pvt_unlock(pkt->owner);
return 0;
Olle Johansson
committed
/*! \brief Transmit packet with retransmits
\return 0 on success, -1 on failure to allocate packet
*/
Olle Johansson
committed
static enum sip_result __sip_reliable_xmit(struct sip_pvt *p, int seqno, int resp, char *data, int len, int fatal, int sipmethod)
int siptimer_a = DEFAULT_RETRANS;
Olle Johansson
committed
int xmitres = 0;
if (!(pkt = ast_calloc(1, sizeof(*pkt) + len + 1)))
Olle Johansson
committed
return AST_FAILURE;
/* copy data, add a terminator and save length */
/* copy other parameters from the caller */
pkt->method = sipmethod;
pkt->is_resp = resp;
pkt->is_fatal = fatal;
pkt->owner = dialog_ref(p);
pkt->next = p->packets;
p->packets = pkt;
pkt->timer_t1 = p->timer_t1; /* Set SIP timer T1 */
if (pkt->timer_t1)
siptimer_a = pkt->timer_t1 * 2;
/* Schedule retransmission */
pkt->retransid = ast_sched_replace_variable(pkt->retransid, sched,
siptimer_a, retrans_pkt, pkt, 1);
if (sipdebug)
ast_debug(4, "*** SIP TIMER: Initializing retransmit timer on packet: Id #%d\n", pkt->retransid);
if (sipmethod == SIP_INVITE) {
/* Note this is a pending invite */
p->pendinginvite = seqno;
}
Olle Johansson
committed
xmitres = __sip_xmit(pkt->owner, pkt->data, pkt->packetlen); /* Send packet */
if (xmitres == XMIT_ERROR) { /* Serious network trouble, no need to try again */
append_history(pkt->owner, "XmitErr", "%s", pkt->is_fatal ? "(Critical)" : "(Non-critical)");
ast_sched_del(sched, pkt->retransid); /* No more retransmission */
Olle Johansson
committed
pkt->retransid = -1;
return AST_FAILURE;
Olle Johansson
committed
return AST_SUCCESS;
/*! \brief Kill a SIP dialog (called only by the scheduler)
* The scheduler has a reference to this dialog when p->autokillid != -1,
* and we are called using that reference. So if the event is not
* rescheduled, we need to call dialog_unref().
*/
static int __sip_autodestruct(const void *data)
struct sip_pvt *p = (struct sip_pvt *)data;
/* If this is a subscription, tell the phone that we got a timeout */
if (p->subscribed) {
Olle Johansson
committed
transmit_state_notify(p, AST_EXTENSION_DEACTIVATED, 1, TRUE); /* Send last notification */
p->subscribed = NONE;
append_history(p, "Subscribestatus", "timeout");
ast_debug(3, "Re-scheduled destruction of SIP subscription %s\n", p->callid ? p->callid : "<unknown>");
return 10000; /* Reschedule this destruction so that we know that it's gone */
}
/* If there are packets still waiting for delivery, delay the destruction */
if (p->packets) {
ast_debug(3, "Re-scheduled destruction of SIP call %s\n", p->callid ? p->callid : "<unknown>");
append_history(p, "ReliableXmit", "timeout");
return 10000;
Olle Johansson
committed
if (p->subscribed == MWI_NOTIFICATION)
if (p->relatedpeer)
unref_peer(p->relatedpeer); /* Remove link to peer. If it's realtime, make sure it's gone from memory) */
Olle Johansson
committed
/* Reset schedule ID */
p->autokillid = -1;
Olle Johansson
committed
ast_log(LOG_WARNING, "Autodestruct on dialog '%s' with owner in place (Method: %s)\n", p->callid, sip_methods[p->method].text);
Mark Spencer
committed
ast_queue_hangup(p->owner);
ast_debug(3, "Finally hanging up channel after transfer: %s\n", p->callid);
transmit_request_with_auth(p, SIP_BYE, 0, XMIT_RELIABLE, 1);
append_history(p, "ReferBYE", "Sending BYE on transferer call leg %s", p->callid);
sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT);
} else {
append_history(p, "AutoDestroy", "%s", p->callid);
ast_debug(3, "Auto destroying SIP dialog '%s'\n", p->callid);
sip_destroy(p); /* Go ahead and destroy dialog. All attempts to recover is done */
/* sip_destroy also absorbs the reference */
Olle Johansson
committed
static void sip_scheddestroy(struct sip_pvt *p, int ms)
Olle Johansson
committed
if (p->timer_t1 == 0) {
p->timer_t1 = global_t1; /* Set timer T1 if not set (RFC 3261) */
p->timer_b = global_timer_b; /* Set timer B if not set (RFC 3261) */
}
ms = p->timer_t1 * 64;
}
if (sip_debug_test_pvt(p))
Olle Johansson
committed
ast_verbose("Scheduling destruction of SIP dialog '%s' in %d ms (Method: %s)\n", p->callid, ms, sip_methods[p->method].text);
Olle Johansson
committed
append_history(p, "SchedDestroy", "%d ms", ms);
p->autokillid = ast_sched_add(sched, ms, __sip_autodestruct, dialog_ref(p));
/*! \brief Cancel destruction of SIP dialog.
* Be careful as this also absorbs the reference - if you call it
* from within the scheduler, this might be the last reference.
*/
Olle Johansson
committed
static void sip_cancel_destroy(struct sip_pvt *p)
if (p->autokillid > -1) {
append_history(p, "CancelDestroy", "");
p->autokillid = -1;
/*! \brief Acknowledges receipt of a packet and stops retransmission */
Olle Johansson
committed
static void __sip_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
struct sip_pkt *cur, *prev = NULL;
const char *msg = "Not Found"; /* used only for debugging */
Kevin P. Fleming
committed
sip_pvt_lock(p);
/* If we have an outbound proxy for this dialog, then delete it now since
the rest of the requests in this dialog needs to follow the routing.
If obforcing is set, we will keep the outbound proxy during the whole
dialog, regardless of what the SIP rfc says
*/
if (p->outboundproxy && !p->outboundproxy->force)
p->outboundproxy = NULL;
for (cur = p->packets; cur; prev = cur, cur = cur->next) {
if (cur->seqno != seqno || cur->is_resp != resp)
if (cur->is_resp || cur->method == sipmethod) {
if (!resp && (seqno == p->pendinginvite)) {
ast_debug(1, "Acked pending invite %d\n", p->pendinginvite);
p->pendinginvite = 0;
}
if (cur->retransid > -1) {
if (sipdebug)
ast_debug(4, "** SIP TIMER: Cancelling retransmit of packet (reply received) Retransid #%d\n", cur->retransid);
ast_sched_del(sched, cur->retransid);
UNLINK(cur, p->packets, prev);
dialog_unref(cur->owner);
ast_free(cur);
sip_pvt_unlock(p);
ast_debug(1, "Stopping retransmission on '%s' of %s %d: Match %s\n",
p->callid, resp ? "Response" : "Request", seqno, msg);
/*! \brief Pretend to ack all packets
* maybe the lock on p is not strictly necessary but there might be a race */
static void __sip_pretend_ack(struct sip_pvt *p)
{
struct sip_pkt *cur = NULL;
while (p->packets) {
int method;
if (cur == p->packets) {
ast_log(LOG_WARNING, "Have a packet that doesn't want to give up! %s\n", sip_methods[cur->method].text);
return;
}
cur = p->packets;
method = (cur->method) ? cur->method : find_sip_method(cur->data);
__sip_ack(p, cur->seqno, cur->is_resp, method);
}
}
/*! \brief Acks receipt of packet, keep it around (used for provisional responses) */
static int __sip_semi_ack(struct sip_pvt *p, int seqno, int resp, int sipmethod)
for (cur = p->packets; cur; cur = cur->next) {
if (cur->seqno == seqno && cur->is_resp == resp &&
(cur->is_resp || method_match(sipmethod, cur->data))) {
if (cur->retransid > -1) {
if (sipdebug)
ast_debug(4, "*** SIP TIMER: Cancelling retransmission #%d - %s (got response)\n", cur->retransid, sip_methods[sipmethod].text);
ast_sched_del(sched, cur->retransid);
ast_debug(1, "(Provisional) Stopping retransmission (but retaining packet) on '%s' %s %d: %s\n", p->callid, resp ? "Response" : "Request", seqno, res ? "Not Found" : "Found");
/*! \brief Copy SIP request, parse it */
static void parse_copy(struct sip_request *dst, const struct sip_request *src)
{
memset(dst, 0, sizeof(*dst));
memcpy(dst->data, src->data, sizeof(dst->data));
dst->len = src->len;
parse_request(dst);
static void add_blank(struct sip_request *req)
{
if (!req->lines) {
/* Add extra empty return. add_header() reserves 4 bytes so cannot be truncated */
Russell Bryant
committed
ast_copy_string(req->data + req->len, "\r\n", sizeof(req->data) - req->len);
req->len += strlen(req->data + req->len);
}
}
/*! \brief Transmit response on SIP request*/
static int send_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
add_blank(req);
if (sip_debug_test_pvt(p)) {
const struct sockaddr_in *dst = sip_real_dst(p);
ast_verbose("\n<--- %sTransmitting (%s) to %s:%d --->\n%s\n<------------>\n",
reliable ? "Reliably " : "", sip_nat_mode(p),
Russell Bryant
committed
ast_inet_ntoa(dst->sin_addr),
ntohs(dst->sin_port), req->data);
if (p->do_history) {
Olle Johansson
committed
struct sip_request tmp;
parse_copy(&tmp, req);
append_history(p, reliable ? "TxRespRel" : "TxResp", "%s / %s - %s", tmp.data, get_header(&tmp, "CSeq"),
(tmp.method == SIP_RESPONSE || tmp.method == SIP_UNKNOWN) ? tmp.rlPart2 : sip_methods[tmp.method].text);
Mark Spencer
committed
}
Olle Johansson
committed
res = (reliable) ?
Olle Johansson
committed
__sip_reliable_xmit(p, seqno, 1, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
Olle Johansson
committed
__sip_xmit(p, req->data, req->len);
/*! \brief Send SIP Request to the other part of the dialogue */
static int send_request(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable, int seqno)
/* If we have an outbound proxy, reset peer address
Only do this once.
*/
if (p->outboundproxy) {
p->sa = p->outboundproxy->ip;
}
add_blank(req);
if (sip_debug_test_pvt(p)) {
if (ast_test_flag(&p->flags[0], SIP_NAT_ROUTE))
Russell Bryant
committed
ast_verbose("%sTransmitting (NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->recv.sin_addr), ntohs(p->recv.sin_port), req->data);
Russell Bryant
committed
ast_verbose("%sTransmitting (no NAT) to %s:%d:\n%s\n---\n", reliable ? "Reliably " : "", ast_inet_ntoa(p->sa.sin_addr), ntohs(p->sa.sin_port), req->data);
if (p->do_history) {
Olle Johansson
committed
struct sip_request tmp;
parse_copy(&tmp, req);
append_history(p, reliable ? "TxReqRel" : "TxReq", "%s / %s - %s", tmp.data, get_header(&tmp, "CSeq"), sip_methods[tmp.method].text);
Mark Spencer
committed
}
Olle Johansson
committed
res = (reliable) ?
__sip_reliable_xmit(p, seqno, 0, req->data, req->len, (reliable == XMIT_CRITICAL), req->method) :
Olle Johansson
committed
__sip_xmit(p, req->data, req->len);
/*! \brief Locate closing quote in a string, skipping escaped quotes.
* optionally with a limit on the search.
* start must be past the first quote.
*/
static const char *find_closing_quote(const char *start, const char *lim)
{
char last_char = '\0';
const char *s;
for (s = start; *s && s != lim; last_char = *s++) {
if (*s == '"' && last_char != '\\')
break;
}
return s;
/*! \brief Pick out text in brackets from character string
\return pointer to terminated stripped string
\param tmp input string that will be modified
Examples:
"foo" <bar> valid input, returns bar
foo returns the whole string
< "foo ... > returns the string between brackets
< "foo... bogus (missing closing bracket), returns the whole string
XXX maybe should still skip the opening bracket
Kevin P. Fleming
committed
static char *get_in_brackets(char *tmp)
const char *parse = tmp;
Kevin P. Fleming
committed
char *first_bracket;
/*
* Skip any quoted text until we find the part in brackets.
* On any error give up and return the full string.
*/
while ( (first_bracket = strchr(parse, '<')) ) {
char *first_quote = strchr(parse, '"');
if (!first_quote || first_quote > first_bracket)
break; /* no need to look at quoted part */
/* the bracket is within quotes, so ignore it */
parse = find_closing_quote(first_quote + 1, NULL);
if (!*parse) { /* not found, return full string ? */
/* XXX or be robust and return in-bracket part ? */
ast_log(LOG_WARNING, "No closing quote found in '%s'\n", tmp);
break;
parse++;
}
if (first_bracket) {
char *second_bracket = strchr(first_bracket + 1, '>');
if (second_bracket) {
*second_bracket = '\0';
tmp = first_bracket + 1;
} else {
ast_log(LOG_WARNING, "No closing bracket found in '%s'\n", tmp);
Kevin P. Fleming
committed
}
Olle Johansson
committed
/*! \brief * parses a URI in its components.
*
* \note
* - If scheme is specified, drop it from the top.
Olle Johansson
committed
* - If a component is not requested, do not split around it.
* This means that if we don't have domain, we cannot split
* name:pass and domain:port.
* It is safe to call with ret_name, pass, domain, port
* pointing all to the same place.
* Init pointers to empty string so we never get NULL dereferencing.
* Overwrites the string.
* return 0 on success, other values on error.
Olle Johansson
committed
* general form we are expecting is sip[s]:username[:password][;parameter]@host[:port][;...]
*/
static int parse_uri(char *uri, char *scheme,
char **ret_name, char **pass, char **domain, char **port, char **options)
{
char *name = NULL;
int error = 0;
/* init field as required */
if (scheme) {
int l = strlen(scheme);
if (!strncasecmp(uri, scheme, l))
Olle Johansson
committed
uri += l;
Olle Johansson
committed
ast_log(LOG_NOTICE, "Missing scheme '%s' in '%s'\n", scheme, uri);
error = -1;
}
}
if (!domain) {
/* if we don't want to split around domain, keep everything as a name,
* so we need to do nothing here, except remember why.
*/
} else {
/* store the result in a temp. variable to avoid it being
* overwritten if arguments point to the same place.
*/
char *c, *dom = "";
Olle Johansson
committed
if ((c = strchr(uri, '@')) == NULL) {
/* domain-only URI, according to the SIP RFC. */
Olle Johansson
committed
dom = uri;
*c++ = '\0';
dom = c;
Olle Johansson
committed
name = uri;
Olle Johansson
committed
/* Remove options in domain and name */
dom = strsep(&dom, ";");
name = strsep(&name, ";");
if (port && (c = strchr(dom, ':'))) { /* Remove :port */
*c++ = '\0';
*port = c;
}
if (pass && (c = strchr(name, ':'))) { /* user:password */
*c++ = '\0';
*pass = c;
}
*domain = dom;
}
if (ret_name) /* same as for domain, store the result only at the end */
*ret_name = name;
if (options)
*options = uri ? uri : "";
return error;
}
/*! \brief Send message with Access-URL header, if this is an HTML URL only! */
static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen)
{
struct sip_pvt *p = chan->tech_pvt;
Kevin P. Fleming
committed
ast_string_field_build(p, url, "<%s>;mode=active", data);
if (sip_debug_test_pvt(p))
ast_debug(1, "Send URL %s, state = %d!\n", data, chan->_state);
switch (chan->_state) {
case AST_STATE_RING:
transmit_response(p, "100 Trying", &p->initreq);
break;
case AST_STATE_RINGING:
transmit_response(p, "180 Ringing", &p->initreq);
break;
case AST_STATE_UP:
if (!p->pendinginvite) { /* We are up, and have no outstanding invite */
transmit_reinvite_with_sdp(p, FALSE);
} else if (!ast_test_flag(&p->flags[0], SIP_PENDINGBYE)) {
ast_set_flag(&p->flags[0], SIP_NEEDREINVITE);
}
break;
default:
ast_log(LOG_WARNING, "Don't know how to send URI when state is %d!\n", chan->_state);
}
/*! \brief Send SIP MESSAGE text within a call
Called from PBX core sendtext() application */
Mark Spencer
committed
static int sip_sendtext(struct ast_channel *ast, const char *text)
struct sip_pvt *p = ast->tech_pvt;
int debug = sip_debug_test_pvt(p);
ast_verbose("Sending text %s on %s\n", text, ast->name);
if (!p)
return -1;
if (ast_strlen_zero(text))
ast_verbose("Really sending text %s on %s\n", text, ast->name);
transmit_message_with_text(p, text);
return 0;
/*! \brief Update peer object in realtime storage
If the Asterisk system name is set in asterisk.conf, we will use
that name and store that in the "regserver" field in the sippeers
table to facilitate multi-server setups.
*/
static void realtime_update_peer(const char *peername, struct sockaddr_in *sin, const char *defaultuser, const char *fullcontact, int expirey)
Russell Bryant
committed
char ipaddr[INET_ADDRSTRLEN];
char *tablename = NULL;
const char *sysname = ast_config_AST_SYSTEM_NAME;
char *syslabel = NULL;
time_t nowtime = time(NULL) + expirey;
const char *fc = fullcontact ? "fullcontact" : NULL;
int realtimeregs = ast_check_realtime("sipregs");
tablename = realtimeregs ? "sipregs" : "sippeers";
snprintf(regseconds, sizeof(regseconds), "%d", (int)nowtime); /* Expiration time */
Russell Bryant
committed
ast_copy_string(ipaddr, ast_inet_ntoa(sin->sin_addr), sizeof(ipaddr));
snprintf(port, sizeof(port), "%d", ntohs(sin->sin_port));
if (ast_strlen_zero(sysname)) /* No system name, disable this */
sysname = NULL;
else if (sip_cfg.rtsave_sysname)
syslabel = "regserver";
if (fc)
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
"port", port, "regseconds", regseconds,
"defaultuser", defaultuser, fc, fullcontact, syslabel, sysname, NULL); /* note fc and syslabel _can_ be NULL */
ast_update_realtime(tablename, "name", peername, "ipaddr", ipaddr,
"port", port, "regseconds", regseconds,
"defaultuser", defaultuser, syslabel, sysname, NULL); /* note syslabel _can_ be NULL */
/*! \brief Automatically add peer extension to dial plan */
static void register_peer_exten(struct sip_peer *peer, int onoff)
char multi[256];
char *stringp, *ext, *context;
/* XXX note that global_regcontext is both a global 'enable' flag and
* the name of the global regexten context, if not specified
* individually.
*/
if (ast_strlen_zero(global_regcontext))
return;
ast_copy_string(multi, S_OR(peer->regexten, peer->name), sizeof(multi));
stringp = multi;
while ((ext = strsep(&stringp, "&"))) {
if ((context = strchr(ext, '@'))) {
*context++ = '\0'; /* split ext@context */
if (!ast_context_find(context)) {
ast_log(LOG_WARNING, "Context %s must exist in regcontext= in sip.conf!\n", context);
continue;
Mark Spencer
committed
}
if (onoff)
ast_add_extension(context, 1, ext, 1, NULL, NULL, "Noop",
Russell Bryant
committed
ast_strdup(peer->name), ast_free_ptr, "SIP");
else
ast_context_remove_extension(context, ext, 1, NULL);
}
/*! Destroy mailbox subscriptions */
Russell Bryant
committed
static void destroy_mailbox(struct sip_mailbox *mailbox)
{
if (mailbox->mailbox)
ast_free(mailbox->mailbox);
if (mailbox->context)
ast_free(mailbox->context);
if (mailbox->event_sub)
ast_event_unsubscribe(mailbox->event_sub);
ast_free(mailbox);
}
/*! Destroy all peer-related mailbox subscriptions */
Russell Bryant
committed
static void clear_peer_mailboxes(struct sip_peer *peer)
{
struct sip_mailbox *mailbox;
while ((mailbox = AST_LIST_REMOVE_HEAD(&peer->mailboxes, entry)))
destroy_mailbox(mailbox);
}
/*! \brief Destroy peer object from memory */
Mark Spencer
committed
static void sip_destroy_peer(struct sip_peer *peer)
ast_debug(3, "Destroying SIP peer %s\n", peer->name);