Newer
Older
reqprep(&req, p, SIP_MESSAGE, 0, 1);
return send_request(p, &req, 1, 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 (option_debug || sipdebug)
ast_log(LOG_DEBUG, "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 (strncmp(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);
add_header(&req, "Max-Forwards", DEFAULT_MAX_FORWARDS);
/* 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, "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, 1, 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)
reqprep(&req, p, SIP_INFO, 0, 1);
return send_request(p, &req, 1, 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, 1, p->ocseq);
}
/*! \brief Transmit generic SIP request */
static int transmit_request(struct sip_pvt *p, int sipmethod, int seqno, enum xmittype reliable, int newbranch)
reqprep(&resp, p, sipmethod, seqno, newbranch);
add_header_contentLength(&resp, 0);
return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq);
/*! \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))) {
if (p->options && p->options->auth_type == PROXY_AUTH)
add_header(&resp, "Proxy-Authorization", digest);
else if (p->options && p->options->auth_type == WWW_AUTH)
add_header(&resp, "Authorization", digest);
else /* Default, to be backwards compatible (maybe being too careful, but leaving it for now) */
add_header(&resp, "Proxy-Authorization", digest);
} else
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)
{
if (!ast_test_flag(&global_flags[1], SIP_PAGE2_IGNOREREGEXPIRE)) {
if (ast_test_flag(&peer->flags[1], SIP_PAGE2_RT_FROMCONTACT))
ast_update_realtime("sippeers", "name", peer->name, "fullcontact", "", "ipaddr", "", "port", "", "regseconds", "0", "username", "", "regserver", "", NULL);
else
Mark Spencer
committed
ast_db_del("SIP/Registry", peer->name);
}
}
/*! \brief Expire registration of SIP peer */
struct sip_peer *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
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: 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 (ast_test_flag(&peer->flags[1], SIP_PAGE2_SELFDESTRUCT) ||
ast_test_flag(&peer->flags[1], SIP_PAGE2_RTAUTOCLEAR)) {
peer = ASTOBJ_CONTAINER_UNLINK(&peerl, peer); /* Remove from peer list */
ASTOBJ_UNREF(peer, sip_destroy_peer); /* Remove from memory */
/*! \brief Poke peer (send qualify to check if peer is alive and well) */
static int sip_poke_peer_s(void *data)
{
struct sip_peer *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 (ast_test_flag(&peer->flags[1], SIP_PAGE2_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));
if (option_verbose > 2)
ast_verbose(VERBOSE_PREFIX_3 "SIP Seeding peer from astdb: '%s' at %s@%s:%d for %d\n",
Russell Bryant
committed
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 */
if (peer->pokeexpire > -1)
ast_sched_del(sched, peer->pokeexpire);
Tilghman Lesher
committed
peer->pokeexpire = ast_sched_add(sched, ast_random() % 5000 + 1, sip_poke_peer_s, peer);
} else
sip_poke_peer(peer);
if (peer->expire > -1)
ast_sched_del(sched, peer->expire);
peer->expire = ast_sched_add(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 contact[250];
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);
7280
7281
7282
7283
7284
7285
7286
7287
7288
7289
7290
7291
7292
7293
7294
7295
7296
7297
7298
7299
7300
7301
7302
7303
7304
7305
7306
/* 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 *c, *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);
/* XXX this code is repeated all over */
if (strncasecmp(contact, "sip:", 4)) {
ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", contact);
contact += 4;
/* XXX this code is replicated also shortly below */
contact = strsep(&contact, ";"); /* trim ; and beyond */
host = strchr(contact, '@');
if (!host) { /* No username part */
host = contact;
*host++ = '\0';
pt = strchr(host, ':');
*pt++ = '\0';
port = atoi(pt);
} else
port = DEFAULT_SIP_PORT;
/* 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 */
expires = strcasestr(contact, ";expires=");
/* XXX bug here, we overwrite the string */
expires = strsep((char **) &expires, ";"); /* 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, 0); /* Add extension from regexten= setting in sip.conf */
peer->fullcontact[0] = '\0';
peer->useragent[0] = '\0';
peer->sipoptions = 0;
peer->lastms = 0;
ast_verbose(VERBOSE_PREFIX_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 (strncasecmp(curi, "sip:", 4)) {
ast_log(LOG_NOTICE, "'%s' is not a valid SIP contact (missing sip:) trying to use anyway\n", curi);
} else
*n++ = '\0';
*pt++ = '\0';
port = atoi(pt);
} else
port = DEFAULT_SIP_PORT;
oldsin = peer->addr;
if (!ast_test_flag(&peer->flags[0], SIP_NAT_ROUTE)) {
/* XXX This could block for a long time XXX */
hp = ast_gethostbyname(n, &ahp);
if (!hp) {
ast_log(LOG_WARNING, "Invalid host '%s'\n", n);
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);
} else {
/* Don't trust the contact field. Just use what they came to us
with */
/* Save SIP options profile */
peer->sipoptions = pvt->sipoptions;
if (curi) /* Overwrite the default username from config at registration */
ast_copy_string(peer->username, curi, sizeof(peer->username));
if (peer->expire > -1)
ast_sched_del(sched, peer->expire);
Olle Johansson
committed
if (expiry > max_expiry)
Olle Johansson
committed
if (expiry < min_expiry)
expiry = min_expiry;
peer->expire = ast_test_flag(&peer->flags[0], SIP_REALTIME) ? -1 :
ast_sched_add(sched, (expiry + 10) * 1000, expire_register, peer);
Russell Bryant
committed
snprintf(data, sizeof(data), "%s:%d:%d:%s:%s", ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port), expiry, peer->username, peer->fullcontact);
if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_RT_FROMCONTACT))
ast_db_put("SIP/Registry", peer->name, data);
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name);
/* Is this a new IP address for us? */
if (inaddrcmp(&peer->addr, &oldsin)) {
sip_poke_peer(peer);
Russell Bryant
committed
ast_verbose(VERBOSE_PREFIX_3 "Registered SIP '%s' at %s port %d expires %d\n", peer->name, ast_inet_ntoa(peer->addr.sin_addr), ntohs(peer->addr.sin_port), expiry);
/* Save User agent */
if (useragent && strcasecmp(useragent, peer->useragent)) {
ast_copy_string(peer->useragent, useragent, sizeof(peer->useragent));
if (option_verbose > 3)
ast_verbose(VERBOSE_PREFIX_3 "Saved useragent \"%s\" for peer %s\n", peer->useragent, peer->name);
return PARSE_REGISTER_UPDATE;
/*! \brief Remove route from route list */
static void free_old_route(struct sip_route *route)
{
struct sip_route *next;
while (route) {
next = route->next;
free(route);
route = next;
}
}
static void list_route(struct sip_route *route)
{
if (!route)
ast_verbose("list_route: no route\n");
else {
for (;route; route = route->next)
ast_verbose("list_route: hop: <%s>\n", route->hop);
/*! \brief Build route list from Record-Route header */
static void build_route(struct sip_pvt *p, struct sip_request *req, int backwards)
{
struct sip_route *thishop, *head, *tail;
int start = 0;
int len;
const char *rr, *contact, *c;
/* Once a persistant route is set, don't fool with it */
if (p->route && p->route_persistant) {
ast_log(LOG_DEBUG, "build_route: Retaining previous route: <%s>\n", p->route->hop);
return;
}
if (p->route) {
free_old_route(p->route);
p->route = NULL;
}
p->route_persistant = backwards;
/* Build a tailq, then assign it to p->route when done.
* If backwards, we add entries from the head so they end up
* in reverse order. However, we do need to maintain a correct
* tail pointer because the contact is always at the end.
*/
head = NULL;
tail = head;
/* 1st we pass through all the hops in any Record-Route headers */
for (;;) {
/* Each Record-Route header */
rr = __get_header(req, "Record-Route", &start);
if (*rr == '\0')
break;
for (; (rr = strchr(rr, '<')) ; rr += len) { /* Each route entry */
Kevin P. Fleming
committed
len = strcspn(rr, ">") + 1;
if ((thishop = ast_malloc(sizeof(*thishop) + len))) {
/* ast_calloc is not needed because all fields are initialized in this block */
Kevin P. Fleming
committed
ast_copy_string(thishop->hop, rr, len);
if (option_debug > 1)
ast_log(LOG_DEBUG, "build_route: Record-Route hop: <%s>\n", thishop->hop);
/* Link in */
if (backwards) {
/* Link in at head so they end up in reverse order */
thishop->next = head;
head = thishop;
/* If this was the first then it'll be the tail */
if (!tail)
tail = thishop;
/* Link in at the end */
if (tail)
tail->next = thishop;
else
head = thishop;
tail = thishop;
}
}
}
}
/* Only append the contact if we are dealing with a strict router */
if (!head || (!ast_strlen_zero(head->hop) && strstr(head->hop,";lr") == NULL) ) {
/* 2nd append the Contact: if there is one */
/* Can be multiple Contact headers, comma separated values - we just take the first */
contact = get_header(req, "Contact");
if (!ast_strlen_zero(contact)) {
ast_log(LOG_DEBUG, "build_route: Contact hop: %s\n", contact);
/* Look for <: delimited address */
c = strchr(contact, '<');
if (c) {
/* Take to > */
++c;
len = strcspn(c, ">") + 1;
} else {
/* No <> - just take the lot */
c = contact;
len = strlen(contact) + 1;
}
if ((thishop = ast_malloc(sizeof(*thishop) + len))) {
/* ast_calloc is not needed because all fields are initialized in this block */
ast_copy_string(thishop->hop, c, len);
thishop->next = NULL;
/* Goes at the end */
if (tail)
tail->next = thishop;
else
head = thishop;
}
/* Store as new route */
p->route = head;
/* For debugging dump what we ended up with */
if (sip_debug_test_pvt(p))
list_route(p->route);
}
/*! \brief Check user authorization from peer definition
Some actions, like REGISTER and INVITEs from peers require
\return 0 on success, non-zero on error
static enum check_auth_result check_auth(struct sip_pvt *p, struct sip_request *req, const char *username,
const char *secret, const char *md5secret, int sipmethod,
char *uri, enum xmittype reliable, int ignore)
const char *response = "407 Proxy Authentication Required";
const char *reqheader = "Proxy-Authorization";
const char *respheader = "Proxy-Authenticate";
const char *authtoken;
char a1_hash[256];
char resp_hash[256]="";
char tmp[BUFSIZ * 2]; /* Make a large enough buffer */
char *c;
int wrongnonce = FALSE;
int good_response;
const char *usednonce = p->randdata;
/* table of recognised keywords, and their value in the digest */
enum keys { K_RESP, K_URI, K_USER, K_NONCE, K_LAST };
struct x {
const char *key;
const char *s;
} *i, keys[] = {
[K_RESP] = { "response=", "" },
[K_URI] = { "uri=", "" },
[K_USER] = { "username=", "" },
[K_NONCE] = { "nonce=", "" },
[K_LAST] = { NULL, NULL}
};
if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret))
return AUTH_SUCCESSFUL;
Kevin P. Fleming
committed
if (sipmethod == SIP_REGISTER || sipmethod == SIP_SUBSCRIBE) {
Mark Spencer
committed
/* On a REGISTER, we have to use 401 and its family of headers instead of 407 and its family
of headers -- GO SIP! Whoo hoo! Two things that do the same thing but are used in
different circumstances! What a surprise. */
response = "401 Unauthorized";
reqheader = "Authorization";
respheader = "WWW-Authenticate";
}
authtoken = get_header(req, reqheader);
if (ignore && !ast_strlen_zero(p->randdata) && ast_strlen_zero(authtoken)) {
/* This is a retransmitted invite/register/etc, don't reconstruct authentication
information */
if (!reliable) {
/* Resend message if this was NOT a reliable delivery. Otherwise the
retransmission should get it */
transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
/* Schedule auto destroy in 32 seconds (according to RFC 3261) */
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
return AUTH_CHALLENGE_SENT;
} else if (ast_strlen_zero(p->randdata) || ast_strlen_zero(authtoken)) {
/* We have no auth, so issue challenge and request authentication */
Tilghman Lesher
committed
ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */
transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
return AUTH_CHALLENGE_SENT;
/* Whoever came up with the authentication section of SIP can suck my %&#$&* for not putting
an example in the spec of just what it is you're doing a hash on. */
Kevin P. Fleming
committed
/* Make a copy of the response and parse it */
ast_copy_string(tmp, authtoken, sizeof(tmp));
c = tmp;
Kevin P. Fleming
committed
while(c && *(c = ast_skip_blanks(c)) ) { /* lookup for keys */
for (i = keys; i->key != NULL; i++) {
const char *separator = ","; /* default */
if (strncasecmp(c, i->key, strlen(i->key)) != 0)
continue;
/* Found. Skip keyword, take text in quotes or up to the separator. */
c += strlen(i->key);
if (*c == '"') { /* in quotes. Skip first and look for last */
c++;
separator = "\"";
Kevin P. Fleming
committed
}
i->s = c;
strsep(&c, separator);
break;
}
if (i->key == NULL) /* not found, jump after space or comma */
strsep(&c, " ,");
}
/* Verify that digest username matches the username we auth as */
if (strcmp(username, keys[K_USER].s)) {
ast_log(LOG_WARNING, "username mismatch, have <%s>, digest has <%s>\n",
username, keys[K_USER].s);
/* Oops, we're trying something here */
return AUTH_USERNAME_MISMATCH;
}
Olle Johansson
committed
7728
7729
7730
7731
7732
7733
7734
7735
7736
7737
7738
7739
7740
7741
7742
7743
7744
7745
7746
7747
7748
7749
7750
7751
7752
7753
7754
7755
7756
7757
7758
7759
7760
7761
7762
7763
7764
7765
7766
7767
7768
7769
7770
7771
/* Verify nonce from request matches our nonce. If not, send 401 with new nonce */
if (strcasecmp(p->randdata, keys[K_NONCE].s)) { /* XXX it was 'n'casecmp ? */
wrongnonce = TRUE;
usednonce = keys[K_NONCE].s;
}
if (!ast_strlen_zero(md5secret))
ast_copy_string(a1_hash, md5secret, sizeof(a1_hash));
else {
char a1[256];
snprintf(a1, sizeof(a1), "%s:%s:%s", username, global_realm, secret);
ast_md5_hash(a1_hash, a1);
}
/* compute the expected response to compare with what we received */
{
char a2[256];
char a2_hash[256];
char resp[256];
snprintf(a2, sizeof(a2), "%s:%s", sip_methods[sipmethod].text,
S_OR(keys[K_URI].s, uri));
ast_md5_hash(a2_hash, a2);
snprintf(resp, sizeof(resp), "%s:%s:%s", a1_hash, usednonce, a2_hash);
ast_md5_hash(resp_hash, resp);
}
good_response = keys[K_RESP].s &&
!strncasecmp(keys[K_RESP].s, resp_hash, strlen(resp_hash));
if (wrongnonce) {
ast_string_field_build(p, randdata, "%08lx", ast_random());
if (good_response) {
if (sipdebug)
ast_log(LOG_NOTICE, "Correct auth, but based on stale nonce received from '%s'\n", get_header(req, "To"));
/* We got working auth token, based on stale nonce . */
transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 1);
} else {
/* Everything was wrong, so give the device one more try with a new challenge */
if (sipdebug)
ast_log(LOG_NOTICE, "Bad authentication received from '%s'\n", get_header(req, "To"));
transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
}
/* Schedule auto destroy in 32 seconds */
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
return AUTH_CHALLENGE_SENT;
}
if (good_response)
return AUTH_SUCCESSFUL;
/* Ok, we have a bad username/secret pair */
/* Challenge again, and again, and again */
transmit_response_with_auth(p, response, req, p->randdata, reliable, respheader, 0);
sip_scheddestroy(p, SIP_TRANS_TIMEOUT);
return AUTH_CHALLENGE_SENT;
/*! \brief Change onhold state of a peer using a pvt structure */
static void sip_peer_hold(struct sip_pvt *p, int hold)
{
struct sip_peer *peer = find_peer(p->peername, NULL, 1);
if (!peer)
return;
/* If they put someone on hold, increment the value... otherwise decrement it */
if (hold)
peer->onHold++;
else if (hold > 0)
peer->onHold--;
/* Request device state update */
ast_device_state_changed("SIP/%s", peer->name);
return;
}
/*! \brief Callback for the devicestate notification (SUBSCRIBE) support subsystem
\note If you add an "hint" priority to the extension in the dial plan,
you will get notifications on device state changes */
Kevin P. Fleming
committed
static int cb_extensionstate(char *context, char* exten, int state, void *data)
Kevin P. Fleming
committed
Kevin P. Fleming
committed
switch(state) {
case AST_EXTENSION_DEACTIVATED: /* Retry after a while */
case AST_EXTENSION_REMOVED: /* Extension is gone */
Kevin P. Fleming
committed
if (p->autokillid > -1)
sip_cancel_destroy(p); /* Remove subscription expiry for renewals */
sip_scheddestroy(p, SIP_TRANS_TIMEOUT); /* Delete subscription in 32 secs */
Kevin P. Fleming
committed
ast_verbose(VERBOSE_PREFIX_2 "Extension state: Watcher for hint %s %s. Notify User %s\n", exten, state == AST_EXTENSION_DEACTIVATED ? "deactivated" : "removed", p->username);
Kevin P. Fleming
committed
p->subscribed = NONE;
Olle Johansson
committed
append_history(p, "Subscribestatus", "%s", state == AST_EXTENSION_REMOVED ? "HintRemoved" : "Deactivated");
Kevin P. Fleming
committed
break;
default: /* Tell user */
p->laststate = state;
break;
transmit_state_notify(p, state, 1);
Kevin P. Fleming
committed
if (option_debug > 1)
ast_verbose(VERBOSE_PREFIX_1 "Extension Changed %s new state %s for Notify User %s\n", exten, ast_extension_state2str(state), p->username);
return 0;
/*! \brief Send a fake 401 Unauthorized response when the administrator
wants to hide the names of local users/peers from fishers
*/
static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, int reliable)
{
ast_string_field_build(p, randdata, "%08lx", ast_random()); /* Create nonce for challenge */
transmit_response_with_auth(p, "401 Unauthorized", req, p->randdata, reliable, "WWW-Authenticate", 0);
}
/*! \brief Verify registration of user
- Registration is done in several steps, first a REGISTER without auth
to get a challenge (nonce) then a second one with auth
- Registration requests are only matched with peers that are marked as "dynamic"
*/
static enum check_auth_result register_verify(struct sip_pvt *p, struct sockaddr_in *sin,
struct sip_request *req, char *uri)
enum check_auth_result res = AUTH_NOT_FOUND;
char *domain;
Kevin P. Fleming
committed
/* Terminate URI */
t = uri;
while(*t && (*t > 32) && (*t != ';'))
t++;
*t = '\0';
Kevin P. Fleming
committed
ast_copy_string(tmp, get_header(req, "To"), sizeof(tmp));
if (pedanticsipchecking)
ast_uri_decode(tmp);
Kevin P. Fleming
committed
c = get_in_brackets(tmp);
c = strsep(&c, ";"); /* Ditch ;user=phone */
if (!strncmp(c, "sip:", 4)) {
name = c + 4;
} else {
name = c;
Russell Bryant
committed
ast_log(LOG_NOTICE, "Invalid to address: '%s' from %s (missing sip:) trying to use anyway...\n", c, ast_inet_ntoa(sin->sin_addr));
/* Strip off the domain name */
if ((c = strchr(name, '@'))) {
*c++ = '\0';
domain = c;
if ((c = strchr(domain, ':'))) /* Remove :port */
*c = '\0';
if (!AST_LIST_EMPTY(&domain_list)) {
if (!check_sip_domain(domain, NULL, 0)) {
transmit_response(p, "404 Not found (unknown domain)", &p->initreq);
return AUTH_UNKNOWN_DOMAIN;
}
}
}
ast_string_field_set(p, exten, name);
peer = find_peer(name, NULL, 1);
if (!(peer && ast_apply_ha(peer->ha, sin))) {
ASTOBJ_UNREF(peer, sip_destroy_peer);
if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_DYNAMIC)) {
Kevin P. Fleming
committed
ast_log(LOG_ERROR, "Peer '%s' is trying to register, but not configured as host=dynamic\n", peer->name);
ast_copy_flags(&p->flags[0], &peer->flags[0], SIP_NAT);
transmit_response(p, "100 Trying", req);
if (!(res = check_auth(p, req, peer->name, peer->secret, peer->md5secret, SIP_REGISTER, uri, XMIT_UNRELIABLE, ast_test_flag(req, SIP_PKT_IGNORE)))) {
/* We have a succesful registration attemp with proper authentication,
now, update the peer */
switch (parse_register_contact(p, peer, req)) {
case PARSE_REGISTER_FAILED:
ast_log(LOG_WARNING, "Failed to parse contact info\n");
transmit_response_with_date(p, "400 Bad Request", req);
peer->lastmsgssent = -1;
res = 0;
break;
case PARSE_REGISTER_QUERY:
transmit_response_with_date(p, "200 OK", req);
peer->lastmsgssent = -1;
res = 0;
break;
case PARSE_REGISTER_UPDATE:
update_peer(peer, p->expiry);
/* Say OK and ask subsystem to retransmit msg counter */
transmit_response_with_date(p, "200 OK", req);
Kevin P. Fleming
committed
if (!ast_test_flag((&peer->flags[1]), SIP_PAGE2_SUBSCRIBEMWIONLY))
peer->lastmsgssent = -1;
if (!peer && autocreatepeer) {
/* Create peer if we have autocreate mode enabled */
peer = temp_peer(name);
if (peer) {
Mark Spencer
committed
ASTOBJ_CONTAINER_LINK(&peerl, peer);
switch (parse_register_contact(p, peer, req)) {
case PARSE_REGISTER_FAILED:
ast_log(LOG_WARNING, "Failed to parse contact info\n");
transmit_response_with_date(p, "400 Bad Request", req);
peer->lastmsgssent = -1;
res = 0;
break;
case PARSE_REGISTER_QUERY:
transmit_response_with_date(p, "200 OK", req);
peer->lastmsgssent = -1;
res = 0;
break;
case PARSE_REGISTER_UPDATE:
/* Say OK and ask subsystem to retransmit msg counter */
transmit_response_with_date(p, "200 OK", req);
manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "Peer: SIP/%s\r\nPeerStatus: Registered\r\n", peer->name);
peer->lastmsgssent = -1;
res = 0;
ast_device_state_changed("SIP/%s", peer->name);
Kevin P. Fleming
committed
if (res < 0) {
switch (res) {
case AUTH_SECRET_FAILED:
Kevin P. Fleming
committed
/* Wrong password in authentication. Go away, don't try again until you fixed it */
transmit_response(p, "403 Forbidden (Bad auth)", &p->initreq);
Kevin P. Fleming
committed
break;
case AUTH_USERNAME_MISMATCH:
Kevin P. Fleming
committed
/* Username and digest username does not match.
Asterisk uses the From: username for authentication. We need the
users to use the same authentication user name until we support
proper authentication by digest auth name */
transmit_response(p, "403 Authentication user name does not match account name", &p->initreq);
break;
case AUTH_NOT_FOUND:
if (global_alwaysauthreject) {
transmit_fake_auth_response(p, &p->initreq, 1);
} else {
/* URI not found */
transmit_response(p, "404 Not found", &p->initreq);
}
break;
default:
break;
}
if (option_debug > 1) {
const char *reason = "";
switch (res) {
case AUTH_SECRET_FAILED:
reason = "Bad password";
break;
case AUTH_USERNAME_MISMATCH:
reason = "Bad digest user";
break;
case AUTH_NOT_FOUND: