Newer
Older
return send_request(p, &req, init ? XMIT_CRITICAL : XMIT_RELIABLE, p->ocseq);
/*! \brief Used in the SUBSCRIBE notification subsystem */
Olle Johansson
committed
static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout)
struct ast_str *tmp = ast_str_alloca(4000);
char from[256], to[256];
char *c, *mfrom, *mto;
struct sip_request req;
Kevin P. Fleming
committed
char hint[AST_MAX_EXTENSION];
char *statestring = "terminated";
const struct cfsubscription_types *subscriptiontype;
enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN;
char *pidfstate = "--";
char *pidfnote= "Ready";
Kevin P. Fleming
committed
memset(from, 0, sizeof(from));
memset(to, 0, sizeof(to));
Kevin P. Fleming
committed
switch (state) {
case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE):
statestring = (global_notifyringing) ? "early" : "confirmed";
Kevin P. Fleming
committed
8024
8025
8026
8027
8028
8029
8030
8031
8032
8033
8034
8035
8036
8037
8038
8039
8040
8041
8042
8043
8044
8045
8046
local_state = NOTIFY_INUSE;
pidfstate = "busy";
pidfnote = "Ringing";
break;
case AST_EXTENSION_RINGING:
statestring = "early";
local_state = NOTIFY_INUSE;
pidfstate = "busy";
pidfnote = "Ringing";
break;
case AST_EXTENSION_INUSE:
statestring = "confirmed";
local_state = NOTIFY_INUSE;
pidfstate = "busy";
pidfnote = "On the phone";
break;
case AST_EXTENSION_BUSY:
statestring = "confirmed";
local_state = NOTIFY_CLOSED;
pidfstate = "busy";
pidfnote = "On the phone";
break;
case AST_EXTENSION_UNAVAILABLE:
Kevin P. Fleming
committed
local_state = NOTIFY_CLOSED;
pidfstate = "away";
pidfnote = "Unavailable";
break;
case AST_EXTENSION_ONHOLD:
statestring = "confirmed";
local_state = NOTIFY_INUSE;
pidfstate = "busy";
pidfnote = "On hold";
break;
Kevin P. Fleming
committed
case AST_EXTENSION_NOT_INUSE:
default:
/* Default setting */
break;
}
subscriptiontype = find_subscription_type(p->subscribed);
/* Check which device/devices we are watching and if they are registered */
if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten)) {
char *hint2 = hint, *individual_hint = NULL;
int hint_count = 0, unavailable_count = 0;
while ((individual_hint = strsep(&hint2, "&"))) {
hint_count++;
if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE)
unavailable_count++;
}
/* If none of the hinted devices are registered, we will
* override notification and show no availability.
*/
if (hint_count > 0 && hint_count == unavailable_count) {
local_state = NOTIFY_CLOSED;
pidfstate = "away";
pidfnote = "Not online";
Kevin P. Fleming
committed
}
}
Mark Spencer
committed
Kevin P. Fleming
committed
ast_copy_string(from, get_header(&p->initreq, "From"), sizeof(from));
Kevin P. Fleming
committed
c = get_in_brackets(from);
if (strncasecmp(c, "sip:", 4)) {
ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
return -1;
}
Olle Johansson
committed
mfrom = remove_uri_parameters(c);
Mark Spencer
committed
Kevin P. Fleming
committed
ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to));
c = get_in_brackets(to);
if (strncasecmp(c, "sip:", 4)) {
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c);
return -1;
}
Olle Johansson
committed
mto = remove_uri_parameters(c);
Kevin P. Fleming
committed
reqprep(&req, p, SIP_NOTIFY, 0, 1);
Kevin P. Fleming
committed
add_header(&req, "Event", subscriptiontype->event);
add_header(&req, "Content-Type", subscriptiontype->mediatype);
switch(state) {
case AST_EXTENSION_DEACTIVATED:
Olle Johansson
committed
if (timeout)
Kevin P. Fleming
committed
add_header(&req, "Subscription-State", "terminated;reason=timeout");
else {
add_header(&req, "Subscription-State", "terminated;reason=probation");
add_header(&req, "Retry-After", "60");
Mark Spencer
committed
}
Kevin P. Fleming
committed
break;
case AST_EXTENSION_REMOVED:
add_header(&req, "Subscription-State", "terminated;reason=noresource");
break;
default:
if (p->expiry)
add_header(&req, "Subscription-State", "active");
else /* Expired */
add_header(&req, "Subscription-State", "terminated;reason=timeout");
Kevin P. Fleming
committed
}
switch (p->subscribed) {
case XPIDF_XML:
case CPIM_PIDF_XML:
ast_str_append(&tmp, 0,
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE presence PUBLIC \"-//IETF//DTD RFCxxxx XPIDF 1.0//EN\" \"xpidf.dtd\">\n"
"<presence>\n");
ast_str_append(&tmp, 0, "<presentity uri=\"%s;method=SUBSCRIBE\" />\n", mfrom);
ast_str_append(&tmp, 0, "<atom id=\"%s\">\n", p->exten);
ast_str_append(&tmp, 0, "<address uri=\"%s;user=ip\" priority=\"0.800000\">\n", mto);
ast_str_append(&tmp, 0, "<status status=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed");
ast_str_append(&tmp, 0, "<msnsubstatus substatus=\"%s\" />\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline");
ast_str_append(&tmp, 0, "</address>\n</atom>\n</presence>\n");
Kevin P. Fleming
committed
break;
case PIDF_XML: /* Eyebeam supports this format */
ast_str_append(&tmp, 0,
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"
"<presence xmlns=\"urn:ietf:params:xml:ns:pidf\" \nxmlns:pp=\"urn:ietf:params:xml:ns:pidf:person\"\nxmlns:es=\"urn:ietf:params:xml:ns:pidf:rpid:status:rpid-status\"\nxmlns:ep=\"urn:ietf:params:xml:ns:pidf:rpid:rpid-person\"\nentity=\"%s\">\n", mfrom);
ast_str_append(&tmp, 0, "<pp:person><status>\n");
Kevin P. Fleming
committed
if (pidfstate[0] != '-')
ast_str_append(&tmp, 0, "<ep:activities><ep:%s/></ep:activities>\n", pidfstate);
ast_str_append(&tmp, 0, "</status></pp:person>\n");
ast_str_append(&tmp, 0, "<note>%s</note>\n", pidfnote); /* Note */
ast_str_append(&tmp, 0, "<tuple id=\"%s\">\n", p->exten); /* Tuple start */
ast_str_append(&tmp, 0, "<contact priority=\"1\">%s</contact>\n", mto);
Kevin P. Fleming
committed
if (pidfstate[0] == 'b') /* Busy? Still open ... */
ast_str_append(&tmp, 0, "<status><basic>open</basic></status>\n");
Kevin P. Fleming
committed
else
ast_str_append(&tmp, 0, "<status><basic>%s</basic></status>\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed");
ast_str_append(&tmp, 0, "</tuple>\n</presence>\n");
Kevin P. Fleming
committed
break;
case DIALOG_INFO_XML: /* SNOM subscribes in this format */
ast_str_append(&tmp, 0, "<?xml version=\"1.0\"?>\n");
ast_str_append(&tmp, 0, "<dialog-info xmlns=\"urn:ietf:params:xml:ns:dialog-info\" version=\"%d\" state=\"%s\" entity=\"%s\">\n", p->dialogver++, full ? "full":"partial", mto);
Kevin P. Fleming
committed
if ((state & AST_EXTENSION_RINGING) && global_notifyringing)
ast_str_append(&tmp, 0, "<dialog id=\"%s\" direction=\"recipient\">\n", p->exten);
Kevin P. Fleming
committed
else
ast_str_append(&tmp, 0, "<dialog id=\"%s\">\n", p->exten);
ast_str_append(&tmp, 0, "<state>%s</state>\n", statestring);
if (state == AST_EXTENSION_ONHOLD) {
ast_str_append(&tmp, 0, "<local>\n<target uri=\"%s\">\n"
"<param pname=\"+sip.rendering\" pvalue=\"no\">\n"
"</target>\n</local>\n", mto);
}
ast_str_append(&tmp, 0, "</dialog>\n</dialog-info>\n");
Kevin P. Fleming
committed
break;
case NONE:
default:
break;
Kevin P. Fleming
committed
add_header_contentLength(&req, tmp->used);
add_line(&req, tmp->str);
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
/*! \brief Notify user of messages waiting in voicemail
\note - Notification only works for registered peers with mailbox= definitions
in sip.conf
- We use the SIP Event package message-summary
MIME type defaults to "application/simple-message-summary";
Kevin P. Fleming
committed
static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, char *vmexten)
struct ast_str *out = ast_str_alloca(500);
initreqprep(&req, p, SIP_NOTIFY);
add_header(&req, "Event", "message-summary");
add_header(&req, "Content-Type", default_notifymime);
ast_str_append(&out, 0, "Messages-Waiting: %s\r\n", newmsgs ? "yes" : "no");
ast_str_append(&out, 0, "Message-Account: sip:%s@%s\r\n",
S_OR(vmexten, default_vmexten), S_OR(p->fromdomain, ast_inet_ntoa(p->ourip.sin_addr)));
/* Cisco has a bug in the SIP stack where it can't accept the
(0/0) notification. This can temporarily be disabled in
ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n",
newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)"));
Kevin P. Fleming
committed
if (p->subscribed) {
if (p->expiry)
add_header(&req, "Subscription-State", "active");
else /* Expired */
add_header(&req, "Subscription-State", "terminated;reason=timeout");
}
add_header_contentLength(&req, out->used);
add_line(&req, out->str);
Olle Johansson
committed
if (!p->initreq.headers)
initialize_initreq(p, &req);
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
/*! \brief Transmit SIP request unreliably (only used in sip_notify subsystem) */
static int transmit_sip_request(struct sip_pvt *p, struct sip_request *req)
if (!p->initreq.headers) /* Initialize first request before sending */
Olle Johansson
committed
initialize_initreq(p, req);
return send_request(p, req, XMIT_UNRELIABLE, p->ocseq);
/*! \brief Notify a transferring party of the status of transfer */
static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate)
{
struct sip_request req;
char tmp[BUFSIZ/2];
reqprep(&req, p, SIP_NOTIFY, 0, 1);
snprintf(tmp, sizeof(tmp), "refer;id=%d", cseq);
add_header(&req, "Event", tmp);
add_header(&req, "Subscription-state", terminate ? "terminated;reason=noresource" : "active");
add_header(&req, "Content-Type", "message/sipfrag;version=2.0");
Olle Johansson
committed
add_header(&req, "Allow", ALLOWED_METHODS);
add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
Olle Johansson
committed
snprintf(tmp, sizeof(tmp), "SIP/2.0 %s\r\n", message);
add_header_contentLength(&req, strlen(tmp));
add_line(&req, tmp);
Olle Johansson
committed
if (!p->initreq.headers)
initialize_initreq(p, &req);
p->lastnoninvite = p->ocseq;
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
}
static const struct _map_x_s regstatestrings[] = {
{ REG_STATE_FAILED, "Failed" },
{ REG_STATE_UNREGISTERED, "Unregistered"},
{ REG_STATE_REGSENT, "Request Sent"},
{ REG_STATE_AUTHSENT, "Auth. Sent"},
{ REG_STATE_REGISTERED, "Registered"},
{ REG_STATE_REJECTED, "Rejected"},
{ REG_STATE_TIMEOUT, "Timeout"},
{ REG_STATE_NOAUTH, "No Authentication"},
{ -1, NULL } /* terminator */
};
/*! \brief Convert registration state status to string */
static const char *regstate2str(enum sipregistrystate regstate)
{
return map_x_s(regstatestrings, regstate, "Unknown");
/*! \brief Update registration with SIP Proxy.
* Called from the scheduler when the previous registration expires,
* so we don't have to cancel the pending event.
* We assume the reference so the sip_registry is valid, since it
* is stored in the scheduled event anyways.
*/
static int sip_reregister(const void *data)
{
/* if we are here, we know that we need to reregister. */
Olle Johansson
committed
struct sip_registry *r= registry_addref((struct sip_registry *) data);
/* if we couldn't get a reference to the registry object, punt */
if (!r)
return 0;
Mark Spencer
committed
if (r->call && r->call->do_history)
Olle Johansson
committed
append_history(r->call, "RegistryRenew", "Account: %s@%s", r->username, r->hostname);
Mark Spencer
committed
/* Since registry's are only added/removed by the the monitor thread, this
may be overkill to reference/dereference at all here */
Russell Bryant
committed
if (sipdebug)
Mark Spencer
committed
ast_log(LOG_NOTICE, " -- Re-registration for %s@%s\n", r->username, r->hostname);
__sip_do_register(r);
Olle Johansson
committed
registry_unref(r);
/*! \brief Register with SIP proxy */
static int __sip_do_register(struct sip_registry *r)
res = transmit_register(r, SIP_REGISTER, NULL, NULL);
/*! \brief Registration timeout, register again
* Registered as a timeout handler during transmit_register(),
* to retransmit the packet if a reply does not come back.
* This is called by the scheduler so the event is not pending anymore when
* we are called.
*/
static int sip_reg_timeout(const void *data)
Mark Spencer
committed
/* if we are here, our registration timed out, so we'll just do it over */
Olle Johansson
committed
struct sip_registry *r = registry_addref((struct sip_registry *) data);
struct sip_pvt *p;
Mark Spencer
committed
/* if we couldn't get a reference to the registry object, punt */
if (!r)
return 0;
Kevin P. Fleming
committed
ast_log(LOG_NOTICE, " -- Registration for '%s@%s' timed out, trying again (Attempt #%d)\n", r->username, r->hostname, r->regattempts);
/* If the initial tranmission failed, we may not have an existing dialog,
* so it is possible that r->call == NULL.
* Otherwise destroy it, as we have a timeout so we don't want it.
*/
/* Unlink us, destroy old call. Locking is not relevant here because all this happens
in the single SIP manager thread. */
p = r->call;
p->needdestroy = 1;
/* Pretend to ACK anything just in case */
__sip_pretend_ack(p); /* XXX we need p locked, not sure we have */
/* decouple the two objects */
/* p->registry == r, so r has 2 refs, and the unref won't take the object away */
if (p->registry)
p->registry = registry_unref(p->registry);
r->call = dialog_unref(r->call);
Kevin P. Fleming
committed
/* If we have a limit, stop registration and give up */
if (global_regattempts_max && r->regattempts > global_regattempts_max) {
Kevin P. Fleming
committed
/* Ok, enough is enough. Don't try any more */
/* We could add an external notification here...
steal it from app_voicemail :-) */
ast_log(LOG_NOTICE, " -- Giving up forever trying to register '%s@%s'\n", r->username, r->hostname);
r->regstate = REG_STATE_FAILED;
Kevin P. Fleming
committed
} else {
r->regstate = REG_STATE_UNREGISTERED;
Kevin P. Fleming
committed
r->timeout = -1;
res=transmit_register(r, SIP_REGISTER, NULL, NULL);
}
Olle Johansson
committed
manager_event(EVENT_FLAG_SYSTEM, "Registry", "ChannelType: SIP\r\nUsername: %s\r\nDomain: %s\r\nStatus: %s\r\n", r->username, r->hostname, regstate2str(r->regstate));
Olle Johansson
committed
registry_unref(r);
/*! \brief Transmit register to SIP proxy or UA
* auth = NULL on the initial registration (from sip_reregister())
*/
static int transmit_register(struct sip_registry *r, int sipmethod, const char *auth, const char *authheader)
{
struct sip_request req;
char from[256];
char to[256];
char tmp[80];
char addr[80];
struct sip_pvt *p;
/* exit if we are already in process with this registrar ?*/
if ( r == NULL || ((auth==NULL) && (r->regstate==REG_STATE_REGSENT || r->regstate==REG_STATE_AUTHSENT))) {
Kevin P. Fleming
committed
ast_log(LOG_NOTICE, "Strange, trying to register %s@%s when registration already pending\n", r->username, r->hostname);
Kevin P. Fleming
committed
if (r->call) { /* We have a registration */
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "Already have a REGISTER going on to %s@%s?? \n", r->username, r->hostname);
Kevin P. Fleming
committed
make_our_tag(p->tag, sizeof(p->tag)); /* create a new local tag for every register attempt */
ast_string_field_set(p, theirtag, NULL); /* forget their old tag, so we don't match tags when getting response */
Kevin P. Fleming
committed
/* Build callid for registration if we haven't registered before */
build_callid_registry(r, internip.sin_addr, default_fromdomain);
/* Allocate SIP dialog for registration */
Olle Johansson
committed
if (!(p = sip_alloc( r->callid, NULL, 0, SIP_REGISTER))) {
ast_log(LOG_WARNING, "Unable to allocate registration transaction (memory or socket error)\n");
Olle Johansson
committed
append_history(p, "RegistryInit", "Account: %s@%s", r->username, r->hostname);
p->outboundproxy = obproxy_get(p, NULL);
Mark Spencer
committed
/* Find address to hostname */
Mark Spencer
committed
if (create_addr(p, r->hostname)) {
/* we have what we hope is a temporary network error,
* probably DNS. We need to reschedule a registration try */
sip_destroy(p);
if (r->timeout > -1) {
r->timeout = ast_sched_replace(r->timeout, sched,
global_reg_timeout * 1000, sip_reg_timeout, r);
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "Still have a registration timeout for %s@%s (create_addr() error), %d\n", r->username, r->hostname, r->timeout);
} else {
r->timeout = ast_sched_add(sched, global_reg_timeout*1000, sip_reg_timeout, r);
ast_log(LOG_WARNING, "Probably a DNS error for registration to %s@%s, trying REGISTER again (after %d seconds)\n", r->username, r->hostname, global_reg_timeout);
Kevin P. Fleming
committed
r->regattempts++;
return 0;
}
/* Copy back Call-ID in case create_addr changed it */
ast_string_field_set(r, callid, p->callid);
p->sa.sin_port = htons(r->portno);
p->recv.sin_port = htons(r->portno);
} else /* Set registry port to the port set from the peer definition/srv or default */
r->portno = ntohs(p->sa.sin_port);
ast_set_flag(&p->flags[0], SIP_OUTGOING); /* Registration is outgoing call */
r->call = dialog_ref(p); /* Save pointer to SIP dialog */
Olle Johansson
committed
p->registry = registry_addref(r); /* Add pointer to registry in packet */
Mark Spencer
committed
if (!ast_strlen_zero(r->secret)) /* Secret (password) */
ast_string_field_set(p, peersecret, r->secret);
if (!ast_strlen_zero(r->md5secret))
ast_string_field_set(p, peermd5secret, r->md5secret);
Mark Spencer
committed
/* User name in this realm
- if authuser is set, use that, otherwise use username */
if (!ast_strlen_zero(r->authuser)) {
ast_string_field_set(p, peername, r->authuser);
ast_string_field_set(p, authname, r->authuser);
} else if (!ast_strlen_zero(r->username)) {
ast_string_field_set(p, peername, r->username);
ast_string_field_set(p, authname, r->username);
ast_string_field_set(p, fromuser, r->username);
Mark Spencer
committed
}
if (!ast_strlen_zero(r->username))
ast_string_field_set(p, username, r->username);
Mark Spencer
committed
/* Save extension in packet */
Olle Johansson
committed
if (!ast_strlen_zero(r->callback))
ast_string_field_set(p, exten, r->callback);
/*
check which address we should use in our contact header
based on whether the remote host is on the external or
internal network so we can register through nat
*/
ast_sip_ouraddrfor(&p->sa.sin_addr, &p->ourip);
Kevin P. Fleming
committed
if (auth == NULL) {
if (r->timeout > -1)
ast_log(LOG_WARNING, "Still have a registration timeout, #%d - deleting it\n", r->timeout);
r->timeout = ast_sched_replace(r->timeout, sched, global_reg_timeout * 1000, sip_reg_timeout, r);
ast_debug(1, "Scheduled a registration timeout for %s id #%d \n", r->hostname, r->timeout);
if (strchr(r->username, '@')) {
Kevin P. Fleming
committed
snprintf(from, sizeof(from), "<sip:%s>;tag=%s", r->username, p->tag);
if (!ast_strlen_zero(p->theirtag))
snprintf(to, sizeof(to), "<sip:%s>;tag=%s", r->username, p->theirtag);
else
snprintf(to, sizeof(to), "<sip:%s>", r->username);
Kevin P. Fleming
committed
snprintf(from, sizeof(from), "<sip:%s@%s>;tag=%s", r->username, p->tohost, p->tag);
if (!ast_strlen_zero(p->theirtag))
snprintf(to, sizeof(to), "<sip:%s@%s>;tag=%s", r->username, p->tohost, p->theirtag);
else
snprintf(to, sizeof(to), "<sip:%s@%s>", r->username, p->tohost);
Kevin P. Fleming
committed
/* Fromdomain is what we are registering to, regardless of actual
host name from SRV */
if (!ast_strlen_zero(p->fromdomain)) {
if (r->portno && r->portno != STANDARD_SIP_PORT)
snprintf(addr, sizeof(addr), "sip:%s:%d", p->fromdomain, r->portno);
else
snprintf(addr, sizeof(addr), "sip:%s", p->fromdomain);
} else {
if (r->portno && r->portno != STANDARD_SIP_PORT)
snprintf(addr, sizeof(addr), "sip:%s:%d", r->hostname, r->portno);
else
snprintf(addr, sizeof(addr), "sip:%s", r->hostname);
}
ast_string_field_set(p, uri, addr);
Tilghman Lesher
committed
p->branch ^= ast_random();
init_req(&req, sipmethod, addr);
Mark Spencer
committed
/* Add to CSEQ */
snprintf(tmp, sizeof(tmp), "%u %s", ++r->ocseq, sip_methods[sipmethod].text);
build_via(p);
add_header(&req, "Via", p->via);
add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
if (!ast_strlen_zero(global_useragent))
add_header(&req, "User-Agent", global_useragent);
Mark Spencer
committed
if (auth) /* Add auth header */
add_header(&req, authheader, auth);
else if (!ast_strlen_zero(r->nonce)) {
Mark Spencer
committed
char digest[1024];
/* We have auth data to reuse, build a digest header.
* Note, this is not always useful because some parties do not
* like nonces to be reused (for good reasons!) so they will
* challenge us anyways.
*/
Russell Bryant
committed
if (sipdebug)
ast_debug(1, " >>> Re-using Auth data for %s@%s\n", r->username, r->hostname);
ast_string_field_set(p, realm, r->realm);
ast_string_field_set(p, nonce, r->nonce);
ast_string_field_set(p, domain, r->domain);
ast_string_field_set(p, opaque, r->opaque);
ast_string_field_set(p, qop, r->qop);
p->noncecount = ++r->noncecount;
Mark Spencer
committed
memset(digest,0,sizeof(digest));
Kevin P. Fleming
committed
if(!build_reply_digest(p, sipmethod, digest, sizeof(digest)))
add_header(&req, "Authorization", digest);
else
ast_log(LOG_NOTICE, "No authorization available for authentication of registration to %s@%s\n", r->username, r->hostname);
Mark Spencer
committed
}
Olle Johansson
committed
snprintf(tmp, sizeof(tmp), "%d", r->expiry);
add_header(&req, "Contact", p->our_contact);
add_header_contentLength(&req, 0);
Olle Johansson
committed
initialize_initreq(p, &req);
if (sip_debug_test_pvt(p)) {
Kevin P. Fleming
committed
ast_verbose("REGISTER %d headers, %d lines\n", p->initreq.headers, p->initreq.lines);
r->regstate = auth ? REG_STATE_AUTHSENT : REG_STATE_REGSENT;
Kevin P. Fleming
committed
r->regattempts++; /* Another attempt */
ast_debug(4, "REGISTER attempt %d to %s@%s\n", r->regattempts, r->username, r->hostname);
return send_request(p, &req, XMIT_CRITICAL, p->ocseq);
/*! \brief Transmit text with SIP MESSAGE method */
Mark Spencer
committed
static int transmit_message_with_text(struct sip_pvt *p, const char *text)
reqprep(&req, p, SIP_MESSAGE, 0, 1);
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
/*! \brief Allocate SIP refer structure */
Kevin P. Fleming
committed
static int sip_refer_allocate(struct sip_pvt *p)
{
p->refer = ast_calloc(1, sizeof(struct sip_refer));
return p->refer ? 1 : 0;
/*! \brief Transmit SIP REFER message (initiated by the transfer() dialplan application
\note this is currently broken as we have no way of telling the dialplan
engine whether a transfer succeeds or fails.
\todo Fix the transfer() dialplan function so that a transfer may fail
*/
Mark Spencer
committed
static int transmit_refer(struct sip_pvt *p, const char *dest)
Russell Bryant
committed
struct sip_request req = {
.headers = 0,
};
const char *of;
char *c;
char *ttag, *ftag;
char *theirtag = ast_strdupa(p->theirtag);
if (sipdebug)
ast_debug(1, "SIP transfer of %s to %s\n", p->callid, dest);
/* Are we transfering an inbound or outbound call ? */
if (ast_test_flag(&p->flags[0], SIP_OUTGOING)) {
of = get_header(&p->initreq, "To");
ttag = theirtag;
ftag = p->tag;
} else {
of = get_header(&p->initreq, "From");
ftag = theirtag;
ttag = p->tag;
}
Kevin P. Fleming
committed
ast_copy_string(from, of, sizeof(from));
Kevin P. Fleming
committed
of = get_in_brackets(from);
ast_string_field_set(p, from, of);
if (strncasecmp(of, "sip:", 4))
ast_log(LOG_NOTICE, "From address missing 'sip:', using it anyway\n");
of += 4;
/* Get just the username part */
if ((c = strchr(dest, '@')))
else if ((c = strchr(of, '@')))
*c++ = '\0';
snprintf(referto, sizeof(referto), "<sip:%s@%s>", dest, c);
snprintf(referto, sizeof(referto), "<sip:%s>", dest);
/* save in case we get 407 challenge */
sip_refer_allocate(p);
ast_copy_string(p->refer->refer_to, referto, sizeof(p->refer->refer_to));
ast_copy_string(p->refer->referred_by, p->our_contact, sizeof(p->refer->referred_by));
p->refer->status = REFER_SENT; /* Set refer status */
reqprep(&req, p, SIP_REFER, 0, 1);
add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
add_header(&req, "Refer-To", referto);
add_header(&req, "Allow", ALLOWED_METHODS);
add_header(&req, "Supported", SUPPORTED_EXTENSIONS);
if (!ast_strlen_zero(p->our_contact))
add_header(&req, "Referred-By", p->our_contact);
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
/* We should propably wait for a NOTIFY here until we ack the transfer */
/* Maybe fork a new thread and wait for a STATUS of REFER_200OK on the refer status before returning to app_transfer */
/*! \todo In theory, we should hang around and wait for a reply, before
returning to the dial plan here. Don't know really how that would
affect the transfer() app or the pbx, but, well, to make this
useful we should have a STATUS code on transfer().
*/
/*! \brief Send SIP INFO dtmf message, see Cisco documentation on cisco.com */
static int transmit_info_with_digit(struct sip_pvt *p, const char digit, unsigned int duration)
reqprep(&req, p, SIP_INFO, 0, 1);
add_digit(&req, digit, duration, (ast_test_flag(&p->flags[0], SIP_DTMF) == SIP_DTMF_SHORTINFO));
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
/*! \brief Send SIP INFO with video update request */
static int transmit_info_with_vidupdate(struct sip_pvt *p)
{
struct sip_request req;
reqprep(&req, p, SIP_INFO, 0, 1);
add_vidupdate(&req);
return send_request(p, &req, XMIT_RELIABLE, p->ocseq);
Olle Johansson
committed
/*! \brief Transmit generic SIP request
returns XMIT_ERROR if transmit failed with a critical error (don't retry)
Olle Johansson
committed
*/
static int transmit_request(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch)
Olle Johansson
committed
if (sipmethod == SIP_ACK)
p->invitestate = INV_CONFIRMED;
reqprep(&resp, p, sipmethod, seqno, newbranch);
if (sipmethod == SIP_CANCEL && p->answered_elsewhere)
Olle Johansson
committed
add_header(&resp, "Reason:", "SIP;cause=200;text=\"Call completed elsewhere\"");
add_header_contentLength(&resp, 0);
return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
/*! \brief return the request and response heade for a 401 or 407 code */
static void auth_headers(enum sip_auth_type code, char **header, char **respheader)
{
if (code == WWW_AUTH) { /* 401 */
*header = "WWW-Authenticate";
*respheader = "Authorization";
} else if (code == PROXY_AUTH) { /* 407 */
*header = "Proxy-Authenticate";
*respheader = "Proxy-Authorization";
} else {
ast_verbose("-- wrong response code %d\n", code);
*header = *respheader = "Invalid";
}
}
/*! \brief Transmit SIP request, auth added */
static int transmit_request_with_auth(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch)
{
struct sip_request resp;
reqprep(&resp, p, sipmethod, seqno, newbranch);
if (!ast_strlen_zero(p->realm)) {
Mark Spencer
committed
memset(digest, 0, sizeof(digest));
if(!build_reply_digest(p, sipmethod, digest, sizeof(digest))) {
char *dummy, *response;
enum sip_auth_type code = p->options ? p->options->auth_type : PROXY_AUTH; /* XXX force 407 if unknown */
auth_headers(code, &dummy, &response);
add_header(&resp, response, digest);
Kevin P. Fleming
committed
ast_log(LOG_WARNING, "No authentication available for call %s\n", p->callid);
Kevin P. Fleming
committed
/* If we are hanging up and know a cause for that, send it in clear text to make
debugging easier. */
if (sipmethod == SIP_BYE && p->owner && p->owner->hangupcause) {
char buf[10];
add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(p->owner->hangupcause));
snprintf(buf, sizeof(buf), "%d", p->owner->hangupcause);
add_header(&resp, "X-Asterisk-HangupCauseCode", buf);
Kevin P. Fleming
committed
}
add_header_contentLength(&resp, 0);
return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
/*! \brief Remove registration data from realtime database or AST/DB when registration expires */
Mark Spencer
committed
static void destroy_association(struct sip_peer *peer)
{
int realtimeregs = ast_check_realtime("sipregs");
char *tablename = (realtimeregs) ? "sipregs" : "sippeers";
if (!sip_cfg.ignore_regexpire) {
if (peer->rt_fromcontact)
ast_update_realtime(tablename, "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "defaultuser", "", "regserver", "", NULL);
else
Mark Spencer
committed
ast_db_del("SIP/Registry", peer->name);
}
}
/*! \brief Expire registration of SIP peer */
static int expire_register(const void *data)
struct sip_peer *peer = (struct sip_peer *)data;
if (!peer) /* Hmmm. We have no peer. Weird. */
return 0;
memset(&peer->addr, 0, sizeof(peer->addr));
Mark Spencer
committed
destroy_association(peer); /* remove registration data from storage */
Mark Spencer
committed
Olle Johansson
committed
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", peer->name);
register_peer_exten(peer, FALSE); /* Remove regexten */
peer->expire = -1;
ast_device_state_changed("SIP/%s", peer->name);
/* Do we need to release this peer from memory?
Only for realtime peers and autocreated peers
*/
if (peer->is_realtime)
ast_debug(3,"-REALTIME- peer expired registration. Name: %s. Realtime peer objects now %d\n", peer->name, rpeerobjs);
if (peer->selfdestruct ||
ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
peer = ASTOBJ_CONTAINER_UNLINK(&peerl, peer); /* Remove from peer list */
unref_peer(peer); /* Remove from memory */
/*! \brief Poke peer (send qualify to check if peer is alive and well) */
static int sip_poke_peer_s(const void *data)
struct sip_peer *peer = (struct sip_peer *)data;
peer->pokeexpire = -1;
sip_poke_peer(peer);
return 0;
}
/*! \brief Get registration details from Asterisk DB */
static void reg_source_db(struct sip_peer *peer)
struct in_addr in;
int expiry;
int port;
char *scan, *addr, *port_str, *expiry_str, *username, *contact;
if (peer->rt_fromcontact)
Mark Spencer
committed
return;
if (ast_db_get("SIP/Registry", peer->name, data, sizeof(data)))
return;
scan = data;
addr = strsep(&scan, ":");
port_str = strsep(&scan, ":");
expiry_str = strsep(&scan, ":");
username = strsep(&scan, ":");
contact = scan; /* Contact include sip: and has to be the last part of the database entry as long as we use : as a separator */
if (!inet_aton(addr, &in))
return;
if (port_str)
port = atoi(port_str);
else
return;
if (expiry_str)
expiry = atoi(expiry_str);
else
return;
if (username)
Kevin P. Fleming
committed
ast_copy_string(peer->username, username, sizeof(peer->username));
Kevin P. Fleming
committed
ast_copy_string(peer->fullcontact, contact, sizeof(peer->fullcontact));
ast_debug(2, "SIP Seeding peer from astdb: '%s' at %s@%s:%d for %d\n",
peer->name, peer->username, ast_inet_ntoa(in), port, expiry);
memset(&peer->addr, 0, sizeof(peer->addr));
peer->addr.sin_family = AF_INET;
peer->addr.sin_addr = in;
peer->addr.sin_port = htons(port);
if (sipsock < 0) {
/* SIP isn't up yet, so schedule a poke only, pretty soon */
peer->pokeexpire = ast_sched_replace(peer->pokeexpire, sched,
ast_random() % 5000 + 1, sip_poke_peer_s, peer);
peer->expire = ast_sched_replace(peer->expire, sched,
(expiry + 10) * 1000, expire_register, peer);
Olle Johansson
committed
register_peer_exten(peer, TRUE);
/*! \brief Save contact header for 200 OK on INVITE */
static int parse_ok_contact(struct sip_pvt *pvt, struct sip_request *req)
{
char *c;
Kevin P. Fleming
committed
ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
c = get_in_brackets(contact);
/* Save full contact to call pvt for later bye or re-invite */
ast_string_field_set(pvt, fullcontact, c);
/* Save URI for later ACKs, BYE or RE-invites */
ast_string_field_set(pvt, okcontacturi, c);
/* We should return false for URI:s we can't handle,
like sips:, tel:, mailto:,ldap: etc */
return TRUE;
}
/*! \brief Change the other partys IP address based on given contact */
static int set_address_from_contact(struct sip_pvt *pvt)
{
struct hostent *hp;
struct ast_hostent ahp;
int port;
char *host, *pt;
char *contact;
if (ast_test_flag(&pvt->flags[0], SIP_NAT_ROUTE)) {
/* NAT: Don't trust the contact field. Just use what they came to us
with. */
pvt->sa = pvt->recv;
return 0;
}
/* Work on a copy */
contact = ast_strdupa(pvt->fullcontact);
/* We have only the part in <brackets> here so we just need to parse a SIP URI.*/
if (parse_uri(contact, "sip:", &contact, NULL, &host, &pt, NULL))
ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact);
port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT;
ast_verbose("--- set_address_from_contact host '%s'\n", host);
/* XXX This could block for a long time XXX */
/* We should only do this if it's a name, not an IP */
hp = ast_gethostbyname(host, &ahp);
if (!hp) {
ast_log(LOG_WARNING, "Invalid host name in Contact: (can't resolve in DNS) : '%s'\n", host);
return -1;
pvt->sa.sin_family = AF_INET;
memcpy(&pvt->sa.sin_addr, hp->h_addr, sizeof(pvt->sa.sin_addr));
pvt->sa.sin_port = htons(port);
/*! \brief Parse contact header and save registration (peer registration) */
static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req)
char contact[BUFSIZ];
char data[BUFSIZ];
const char *expires = get_header(req, "Expires");
const char *useragent;
ast_copy_string(contact, get_header(req, "Contact"), sizeof(contact));
if (ast_strlen_zero(expires)) { /* No expires header, try look in Contact: */
char *s = strcasestr(contact, ";expires=");
if (s) {
expires = strsep(&s, ";"); /* trim ; and beyond */
if (sscanf(expires + 9, "%d", &expiry) != 1)
expiry = default_expiry;
} else {
/* Nothing has been specified */
expiry = default_expiry;
}
if (strchr(contact, '<') == NULL) /* No <, check for ; and strip it */
strsep(&curi, ";"); /* This is Header options, not URI options */
curi = get_in_brackets(contact);
/* if they did not specify Contact: or Expires:, they are querying
what we currently have stored as their contact address, so return
it
*/
if (ast_strlen_zero(curi) && ast_strlen_zero(expires)) {
/* If we have an active registration, tell them when the registration is going to expire */
if (peer->expire > -1 && !ast_strlen_zero(peer->fullcontact))
pvt->expiry = ast_sched_when(sched, peer->expire);
} else if (!strcasecmp(curi, "*") || !expiry) { /* Unregister this peer */
/* This means remove all registrations and return OK */
memset(&peer->addr, 0, sizeof(peer->addr));
if (peer->expire > -1)
ast_sched_del(sched, peer->expire);
peer->expire = -1;
Mark Spencer
committed
Mark Spencer
committed
register_peer_exten(peer, FALSE); /* Remove extension from regexten= setting in sip.conf */
peer->fullcontact[0] = '\0';
peer->useragent[0] = '\0';
peer->sipoptions = 0;
peer->lastms = 0;
ast_verb(3, "Unregistered SIP '%s'\n", peer->name);
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: SIP/%s\r\nPeerStatus: Unregistered\r\n", peer->name);
return PARSE_REGISTER_UPDATE;
/* Store whatever we got as a contact from the client */
ast_copy_string(peer->fullcontact, curi, sizeof(peer->fullcontact));
/* For the 200 OK, we should use the received contact */
ast_string_field_build(pvt, our_contact, "<%s>", curi);
if (parse_uri(curi, "sip:", &curi, NULL, &host, &pt, NULL))
ast_log(LOG_NOTICE, "Not a valid SIP contact (missing sip:) trying to use anyway\n");
port = !ast_strlen_zero(pt) ? atoi(pt) : STANDARD_SIP_PORT;
oldsin = peer->addr;
if (!ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE)) {
/* XXX This could block for a long time XXX */
ast_log(LOG_WARNING, "Invalid host '%s'\n", host);
return PARSE_REGISTER_FAILED;
peer->addr.sin_family = AF_INET;
memcpy(&peer->addr.sin_addr, hp->h_addr, sizeof(peer->addr.sin_addr));
peer->addr.sin_port = htons(port);