Index: asterisk-1.6.speakup.1.4/channels/chan_sip.c =================================================================== --- asterisk-1.6.speakup.1.4.orig/channels/chan_sip.c 2009-08-31 16:42:30.000000000 +0200 +++ asterisk-1.6.speakup.1.4/channels/chan_sip.c 2009-09-01 12:24:02.000000000 +0200 @@ -1299,6 +1299,7 @@ AST_STRING_FIELD(rpid_from); /*!< Our RPID From header */ AST_STRING_FIELD(url); /*!< URL to be sent with next message to peer */ AST_STRING_FIELD(parkinglot); /*!< Parkinglot */ + AST_STRING_FIELD(via_branch); /*!< branch parameter of first Via header */ ); char via[128]; /*!< Via: header */ struct sip_socket socket; /*!< The socket used for this dialog */ @@ -6346,18 +6347,42 @@ ao2_t_link(dialogs, p, "link pvt into dialogs table"); - ast_debug(1, "Allocating new SIP dialog for %s - %s (%s)\n", callid ? callid : p->callid, sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP"); + ast_debug(1, "Allocating new SIP dialog (%p) for %s - %s (%s)\n", p, callid ? callid : p->callid, sip_methods[intended_method].text, p->rtp ? "With RTP" : "No RTP"); return p; } -/*! \brief argument to the helper function to identify a call */ -struct find_call_cb_arg { - enum sipmethod method; - const char *callid; - const char *fromtag; - const char *totag; - const char *tag; -}; +/*! \brief check Via: header for branch paramater */ +static char *get_branch(const struct sip_request *req, char *branch) +{ + char via[512]; + char *c, *b; + + ast_copy_string(via, get_header(req, "Via"), sizeof(via)); + + /* Work on the leftmost value of the topmost Via header */ + c = strchr(via, ','); + if (c) + *c = '\0'; + ast_debug(5, "First Via: %s\n", via); + + /* Check for branch */ + b = strstr(via, ";branch="); + if (!b) { + ast_log(LOG_DEBUG, "No branch in Via\n"); + *branch = '\0'; + return branch; + } + b = b + 8; + + c = strchr(b, ';'); + if (c) + *c = '\0'; + + ast_copy_string(branch, b, 128); + ast_debug(5, "This is branch: %s\n", branch); + + return branch; +} /*! * code to determine whether this is the pvt that we are looking for. @@ -6366,37 +6391,87 @@ static int find_call_cb(void *__pvt, void *__arg, int flags) { struct sip_pvt *p = __pvt; - struct find_call_cb_arg *arg = __arg; + struct sip_pvt *arg = __arg; + char *our_fromtag; + char *our_totag; + /* In pedantic, we do not want packets with bad syntax to be connected to a PVT */ - int found = FALSE; - - if (!ast_strlen_zero(p->callid)) { /* XXX double check, do we allow match on empty p->callid ? */ - if (arg->method == SIP_REGISTER) - found = (!strcmp(p->callid, arg->callid)); - else { - found = !strcmp(p->callid, arg->callid); - if (pedanticsipchecking && found) { - found = ast_strlen_zero(arg->tag) || ast_strlen_zero(p->theirtag) || !ast_test_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED) || !strcmp(p->theirtag, arg->tag); - } + if (ast_strlen_zero(p->callid)) { /* XXX double check, do we allow match on empty p->callid ? */ + return 0; + } + + ast_debug(5, "Considering channel: %p callid: %s tag: %s theirtag: %s state: %d \n", p, p->callid, p->tag, p->theirtag, p->invitestate); + + /* Call-ID should match */ + if (strcmp(p->callid, arg->callid)) + return 0; + + /* For REGISTER or SUBSCRIBE request, we don't care about tags */ + if ((arg->method == SIP_REGISTER) || (arg->method == SIP_SUBSCRIBE)) + return CMP_MATCH | CMP_STOP; + + /* tags depend on the direction of the request */ + if (arg->method == SIP_RESPONSE) { + our_fromtag = p->tag; + our_totag = (char*) p->theirtag; + } else { + our_fromtag = (char*) p->theirtag; + our_totag = p->tag; + } + + /* from-tags must ALWAYS match + * remember that the fromtag is in fromuser */ + if (strcmp(our_fromtag, arg->fromuser)) { + ast_debug(5, "From-tags do not match. local tag: %s remote tag: %s\n", our_fromtag, arg->fromuser); + return 0; + } + + /* Check initial INVITE */ + if (ast_strlen_zero(arg->theirtag) && ((arg->method == SIP_INVITE) || (arg->method == SIP_CANCEL))) { + /* Check for retransmission + * CSeq and branch must be identical. + * Skip CSeq check as we don't keep a history of CSeq's in the pvt :( */ + if (strcmp(arg->via_branch, p->via_branch) == 0) { + ast_debug(5, "Match: Retransmission\n"); + return CMP_MATCH | CMP_STOP; } - - ast_debug(5, "= %s Their Call ID: %s Their Tag %s Our tag: %s\n", found ? "Found" : "No match", p->callid, p->theirtag, p->tag); - - /* If we get a new request within an existing to-tag - check the to tag as well */ - if (pedanticsipchecking && found && arg->method != SIP_RESPONSE) { /* SIP Request */ - if (p->tag[0] == '\0' && arg->totag[0]) { - /* We have no to tag, but they have. Wrong dialog */ - found = FALSE; - } else if (arg->totag[0]) { /* Both have tags, compare them */ - if (strcmp(arg->totag, p->tag)) { - found = FALSE; /* This is not our packet */ - } - } - if (!found) - ast_debug(5, "= Being pedantic: This is not our match on request: Call ID: %s Ourtag Totag %s Method %s\n", p->callid, arg->totag, sip_methods[arg->method].text); + + /* If the dialog is already up, absorb the INVITE + * as it may be a late fork and we don't want 2 dialogs */ + if (ast_test_flag(&(p->flags[1]), SIP_PAGE2_DIALOG_ESTABLISHED)) { + ast_debug(5, "Match: Late initial INVITE for established dialog\n"); + return CMP_MATCH | CMP_STOP; + } + + /* We must match when authentication is used (CSEQ = previous CSEQ + 1) */ + ast_debug(5, "CSeq: seqno: %d icseq: %d\n", arg->icseq, p->icseq); + if (arg->icseq == (p->icseq + 1)) { + ast_debug(5, "Match: Authentication attempt for previous request\n"); + return CMP_MATCH | CMP_STOP; + } + + /* Every other initial INVITE must be a forked request and will be treated as new dialog */ + return 0; + } + + /* Check to-tags */ + if (strcmp(our_totag, arg->theirtag) == 0) { + /* to-tags match */ + ast_debug(5, "Match: both tags matched\n"); + return CMP_MATCH | CMP_STOP; + } else { + /* to-tags must match for in-dialog requests and ACK's*/ + if (ast_test_flag(&(p->flags[1]), SIP_PAGE2_DIALOG_ESTABLISHED) + || ((p->invitestate >= INV_COMPLETED) && p->invitestate != INV_CANCELLED)) { + /* to-tags differ */ + ast_debug(5, "To-tags do not match for in-dialog request. local tag: %s remote tag: %s\n", our_totag, arg->theirtag); + return 0; } } - return found; + + /* There is not an established dialog for this pvt. Allow any to-tag */ + ast_debug(5, "Match: to-tags ignored in early-dialog\n"); + return CMP_MATCH | CMP_STOP; } /*! \brief find or create a dialog structure for an incoming SIP message. @@ -6406,16 +6481,15 @@ */ static struct sip_pvt *find_call(struct sip_request *req, struct sockaddr_in *sin, const int intended_method) { - struct sip_pvt *p = NULL; - char *tag = ""; /* note, tag is never NULL */ + struct sip_pvt *p; char totag[128]; char fromtag[128]; - struct find_call_cb_arg arg; + struct sip_pvt arg; const char *callid = get_header(req, "Call-ID"); const char *from = get_header(req, "From"); const char *to = get_header(req, "To"); const char *cseq = get_header(req, "Cseq"); - struct sip_pvt *sip_pvt_ptr; + char branch[128]; /* Call-ID, to, from and Cseq are required by RFC 3261. (Max-forwards and via too - ignored now) */ /* get_header always returns non-NULL so we must use ast_strlen_zero() */ @@ -6423,11 +6497,8 @@ ast_strlen_zero(from) || ast_strlen_zero(cseq)) return NULL; /* Invalid packet */ - arg.method = req->method; - arg.callid = callid; - arg.fromtag = fromtag; - arg.totag = totag; - arg.tag = ""; /* make sure tag is never NULL */ + ast_string_field_init(&arg, 512); + ast_string_field_set(&arg, callid, callid); if (pedanticsipchecking) { /* In principle Call-ID's uniquely identify a call, but with a forking SIP proxy @@ -6440,9 +6511,7 @@ req->has_to_tag = 1; /* Used in handle_request/response */ gettag(req, "From", fromtag, sizeof(fromtag)); - tag = (req->method == SIP_RESPONSE) ? totag : fromtag; - - ast_debug(5, "= Looking for Call ID: %s (Checking %s) --From tag %s --To-tag %s \n", callid, req->method==SIP_RESPONSE ? "To" : "From", fromtag, totag); + ast_debug(5, "= %s looking for Call-ID: %s fromtag: %s totag: %s \n", cseq, callid, fromtag, totag); /* All messages must always have From: tag */ if (ast_strlen_zero(fromtag)) { @@ -6454,32 +6523,33 @@ ast_debug(5, "%s must have a to tag. dropping callid: %s from: %s\n", sip_methods[req->method].text , callid, from ); return NULL; } + + /* Extra fields for dialog matching */ + arg.method = req->method; + /* the fromuser field is abused to transfer the fromtag, + because 'tag' is not a stringfield and is length limited + */ + ast_string_field_set(&arg, fromuser, fromtag); + ast_string_field_set(&arg, theirtag, totag); + + /* And the branch and cseq */ + if ((req->method == SIP_INVITE) || (req->method == SIP_CANCEL)) { + sscanf(cseq, "%d ", &arg.icseq); + get_branch(req, branch); + ast_string_field_set(&arg, via_branch, branch); + } } -restartsearch: if (!pedanticsipchecking) { - struct sip_pvt tmp_dialog = { - .callid = callid, - }; - sip_pvt_ptr = ao2_t_find(dialogs, &tmp_dialog, OBJ_POINTER, "ao2_find in dialogs"); - if (sip_pvt_ptr) { /* well, if we don't find it-- what IS in there? */ - /* Found the call */ - sip_pvt_lock(sip_pvt_ptr); - return sip_pvt_ptr; - } - } else { /* in pedantic mode! -- do the fancy linear search */ - ao2_lock(dialogs); - p = ao2_t_callback(dialogs, 0 /* single, data */, find_call_cb, &arg, "pedantic linear search for dialog"); - if (p) { - if (sip_pvt_trylock(p)) { - ao2_unlock(dialogs); - usleep(1); - goto restartsearch; - } - ao2_unlock(dialogs); - return p; - } - ao2_unlock(dialogs); + p = ao2_t_find(dialogs, &arg, OBJ_POINTER, "ao2_find in dialogs"); + } else { + p = ao2_t_callback(dialogs, OBJ_POINTER, find_call_cb, &arg, "pedantic linear search for dialog"); + } + ast_string_field_free_memory(&arg); + if (p) { /* well, if we don't find it-- what IS in there? */ + /* Found the call */ + sip_pvt_lock(p); + return p; } /* See if the method is capable of creating a dialog */ @@ -14685,6 +14755,7 @@ ast_cli(a->fd, " Audio IP: %s %s\n", ast_inet_ntoa(cur->redirip.sin_addr.s_addr ? cur->redirip.sin_addr : cur->ourip.sin_addr), cur->redirip.sin_addr.s_addr ? "(Outside bridge)" : "(local)" ); ast_cli(a->fd, " Our Tag: %s\n", cur->tag); ast_cli(a->fd, " Their Tag: %s\n", cur->theirtag); + ast_cli(a->fd, " Branch: %s\n", cur->via_branch); ast_cli(a->fd, " SIP User agent: %s\n", cur->useragent); if (!ast_strlen_zero(cur->username)) ast_cli(a->fd, " Username: %s\n", cur->username); @@ -16578,10 +16649,9 @@ handle_response_notify(p, resp, rest, req, seqno); } else if (sipmethod == SIP_REGISTER) res = handle_response_register(p, resp, rest, req, seqno); - else if (sipmethod == SIP_BYE) { /* Ok, we're ready to go */ + else if (sipmethod == SIP_BYE) /* Ok, we're ready to go */ p->needdestroy = 1; - ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); - } else if (sipmethod == SIP_SUBSCRIBE) { + else if (sipmethod == SIP_SUBSCRIBE) { ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); } break; @@ -17822,6 +17892,7 @@ struct ast_channel *c = NULL; /* New channel */ int reinvite = 0; int rtn; + char branch[128]; const char *p_uac_se_hdr; /* UAC's Session-Expires header string */ const char *p_uac_min_se; /* UAC's requested Min-SE interval (char string) */ @@ -17833,6 +17904,12 @@ int dlg_min_se = -1; st_ref = SESSION_TIMER_REFRESHER_AUTO; + /* Remember the branch from the Via header */ + if (req->method == SIP_INVITE) { + get_branch(req, branch); + ast_string_field_set(p, via_branch, branch); + } + /* Find out what they support */ if (!p->sipoptions) { const char *supported = get_header(req, "Supported"); @@ -19151,7 +19228,6 @@ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); ast_debug(3, "Received bye, no owner, selfdestruct soon.\n"); } - ast_clear_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response(p, "200 OK", req); return 1;