Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 123742) +++ channels/chan_sip.c (working copy) @@ -4641,8 +4641,6 @@ if (!found && option_debug > 4) ast_log(LOG_DEBUG, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag Totag %s Method %s\n", p->callid, totag, sip_methods[req->method].text); } - - if (found) { /* Found the call */ ast_mutex_lock(&p->lock); @@ -13766,7 +13764,272 @@ return 0; } +/*! \brief helper routine for sip_uri_cmp + * + * This takes the parameters from two SIP URIs and determines + * if the URIs match. The rules for parameters *suck*. Here's a breakdown + * 1. If a parameter appears in both URIs, then they must have the same value + * in order for the URIs to match + * 2. If one URI has a user, maddr, ttl, or method parameter, then the other + * URI must also have that parameter and must have the same value + * in order for the URIs to match + * 3. All other headers appearing in only one URI are not considered when + * determining if URIs match + * + * This means that the only way to accurately compare parameters is to make + * lists of all the parameters in each URI, and step through making the appropriate + * comparisons. Wish me luck :/ + * + * \param input1 Parameters from URI 1 + * \param input2 Parameters from URI 2 + * \return Return 0 if the URIs' parameters match, 1 if they do not + */ +static int sip_uri_params_cmp(const char *input1, const char *input2) +{ + char *params1 = ast_strdupa(input1); + char *params2 = ast_strdupa(input2); + char *pos1; + char *pos2; + int maddrmatch = 0; + int ttlmatch = 0; + int usermatch = 0; + int methodmatch = 0; + /*Quick optimization. If both params are zero-length, then + * they match + */ + if (ast_strlen_zero(params1) && ast_strlen_zero(params2)) { + return 0; + } + + pos1 = params1; + while (!ast_strlen_zero(pos1)) { + char *name1 = pos1; + char *value1 = strchr(pos1, '='); + char *semicolon1 = strchr(pos1, ';'); + int matched = 0; + if (semicolon1) { + *semicolon1++ = '\0'; + } + if (!value1) { + goto fail; + } + *value1++ = '\0'; + /* Checkpoint reached. We have the name and value parsed for param1 + * We have to duplicate params2 each time through the second loop + * or else we can't search and replace the semicolons with \0 each + * time + */ + pos2 = ast_strdupa(params2); + while (!ast_strlen_zero(pos2)) { + char *name2 = pos2; + char *value2 = strchr(pos2, '='); + char *semicolon2 = strchr(pos2, ';'); + if (semicolon2) { + *semicolon2++ = '\0'; + } + if (!value2) { + goto fail; + } + /* Checkpoint 2 reached. We have the name and value parsed for param2 */ + if (!strcasecmp(name1, name2)) { + /* The two have a parameter in common */ + if (strcasecmp(value1, value2)) { + /*uh oh, they have the same parameter but different values */ + goto fail; + } else { + matched = 1; + break; + } + } + pos2 = semicolon2; + } + /* Need to see if the parameter we're looking at is one of the 'must-match' parameters */ + if (!strcasecmp(name1, "maddr")) { + if (matched) { + maddrmatch = 1; + } else { + goto fail; + } + } else if (!strcasecmp(name1, "ttl")) { + if (matched) { + ttlmatch = 1; + } else { + goto fail; + } + } else if (!strcasecmp(name1, "user")) { + if (matched) { + usermatch = 1; + } else { + goto fail; + } + } else if (!strcasecmp(name1, "method")) { + if (matched) { + methodmatch = 1; + } else { + goto fail; + } + } + pos1 = semicolon1; + } + + /* We've made it out of that horrible O(m*n) construct and there are no + * failures yet. We're not done yet, though, because params2 could have + * an maddr, ttl, user, or method header and params1 did not. + * Time for another loop through params2! + */ + pos2 = params2; + while (!ast_strlen_zero(pos2)) { + char *name2 = pos2; + char *value2 = strchr(pos2, '='); + char *semicolon2 = strchr(pos2, ';'); + if (semicolon2) { + *semicolon2++ = '\0'; + } + if (!value2) { + goto fail; + } + if ((!strcasecmp(name2, "maddr") && !maddrmatch) || + (!strcasecmp(name2, "ttl") && !ttlmatch) || + (!strcasecmp(name2, "user") && !usermatch) || + (!strcasecmp(name2, "method") && !methodmatch)) { + goto fail; + } + } + return 0; + +fail: + return 1; +} + +/*! \brief helper routine for sip_uri_cmp + * + * This takes the "headers" from two SIP URIs and determines + * if the URIs match. The rules for headers is simple. If a header + * appears in one URI, then it must also appear in the other URI. The + * order in which the headers appear does not matter. + * + * \param input1 Headers from URI 1 + * \param input2 Headers from URI 2 + * \return Return 0 if the URIs' headers match, 1 if they do not + */ +static int sip_uri_headers_cmp(const char *input1, const char *input2) +{ + char *headers1 = ast_strdupa(input1); + char *headers2 = ast_strdupa(input2); + int zerolength1 = ast_strlen_zero(headers1); + int zerolength2 = ast_strlen_zero(headers2); + int different = 0; + char *header1; + + if ((zerolength1 && !zerolength2) || + (zerolength2 && !zerolength1)) + return 1; + + if (zerolength1 && zerolength2) + return 0; + + /* At this point, we can definitively state that both inputs are + * not zero-length. First, one more optimization. If the length + * of the headers is not equal, then we definitely have no match + */ + if (strlen(headers1) != strlen(headers2)) { + return 1; + } + + for (header1 = strsep(&headers1, "&"); header1; header1 = strsep(&headers1, "&")) { + if (!strcasestr(headers2, header1)) { + different = 1; + break; + } + } + + return different; +} + +static int sip_uri_cmp(const char *input1, const char *input2) +{ + char *uri1 = ast_strdupa(input1); + char *uri2 = ast_strdupa(input2); + char *host1; + char *host2; + char *params1; + char *params2; + char *headers1; + char *headers2; + + /* Strip off "sip:" from the URI. We know this is present + * because it was checked back in parse_request() + */ + strsep(&uri1, ":"); + strsep(&uri2, ":"); + + if ((host1 = strchr(uri1, '@'))) { + *host1++ = '\0'; + } + if ((host2 = strchr(uri2, '@'))) { + *host2++ = '\0'; + } + + /* Check for mismatched username and passwords. This is the + * only case-sensitive comparison of a SIP URI + */ + if ((host1 && !host2) || + (host2 && !host1) || + (host1 && host2 && strcmp(uri1, uri2))) { + return 1; + } + + if (!host1) + host1 = uri1; + if (!host2) + host2 = uri2; + + /* Strip off the parameters and headers so we can compare + * host and port + */ + + if ((params1 = strchr(host1, ';'))) { + *params1++ = '\0'; + } + if ((params2 = strchr(host2, ';'))) { + *params2++ = '\0'; + } + + /* Headers come after parameters, but there may be headers without + * parameters, thus the S_OR + */ + if ((headers1 = strchr(S_OR(params1, host1), '?'))) { + *headers1++ = '\0'; + } + if ((headers2 = strchr(S_OR(params2, host2), '?'))) { + *headers2++ = '\0'; + } + + /* Now the host/port are properly isolated. We can get by with a string comparison + * because the SIP URI checking rules have some interesting exceptions that make + * this possible. I will note 2 in particular + * 1. hostnames which resolve to the same IP address as well as a hostname and its + * IP address are not considered a match with SIP URI's. + * 2. If one URI specifies a port and the other does not, then the URIs do not match. + * This includes if one URI explicitly contains port 5060 and the other implies it + * by not having a port specified. + */ + + if (strcasecmp(host1, host2)) { + return 1; + } + + /* Headers have easier rules to follow, so do those first */ + if (sip_uri_headers_cmp(headers1, headers2)) { + return 1; + } + + /* And now the parameters. Ugh */ + return sip_uri_params_cmp(params1, params2); +} + + /*! \brief Handle incoming INVITE request \note If the INVITE has a Replaces header, it is part of an * attended transfer. If so, we do not go through the dial @@ -13813,10 +14076,42 @@ being able to call yourself */ /* If pedantic is on, we need to check the tags. If they're different, this is in fact a forked call through a SIP proxy somewhere. */ - transmit_response(p, "482 Loop Detected", req); - p->invitestate = INV_COMPLETED; - sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); - return 0; + int different; + if (pedanticsipchecking) + different = sip_uri_cmp(p->initreq.rlPart2, req->rlPart2); + else + different = strcmp(p->initreq.rlPart2, req->rlPart2); + if (!different) { + transmit_response(p, "482 Loop Detected", req); + p->invitestate = INV_COMPLETED; + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); + return 0; + } else { + /* This is a spiral. What we need to do is to just change the outgoing INVITE + * so that it now routes to the new Request URI. Since we created the INVITE ourselves + * that should be all we need to do. + */ + char *uri = ast_strdupa(req->rlPart2); + char *at = strchr(uri, '@'); + char *peerorhost; + struct sip_pkt *pkt = NULL; + ast_log(LOG_NOTICE, "Spiral detected\n"); + if (at) { + *at = '\0'; + } + /* Parse out "sip:" */ + if ((peerorhost = strchr(uri, ':'))) { + *peerorhost++ = '\0'; + } + create_addr(p, peerorhost); + ast_string_field_free(p, theirtag); + for (pkt = p->packets; pkt; pkt = pkt->next) { + if (pkt->seqno == p->icseq && pkt->method == SIP_INVITE) { + AST_SCHED_DEL(sched, pkt->retransid); + } + } + return transmit_invite(p, SIP_INVITE, 1, 2); + } } if (!ast_test_flag(req, SIP_PKT_IGNORE) && p->pendinginvite) {