Index: channels/chan_sip.c =================================================================== RCS file: /usr/cvsroot/asterisk/channels/chan_sip.c,v retrieving revision 1.836 diff -u -r1.836 chan_sip.c --- channels/chan_sip.c 2 Sep 2005 19:24:32 -0000 1.836 +++ channels/chan_sip.c 5 Sep 2005 12:59:33 -0000 @@ -524,6 +524,19 @@ static int global_rtautoclear = 120; +/* Parameters to know status of transfer */ +enum referstatus { + REFER_IDLE, /* No REFER is in progress */ + REFER_SENT, /* Sent REFER to transferee */ + REFER_RECEIVED, /* Received REFER from transferer */ + REFER_ACCEPTED, /* Accepted by transferee with 202 */ + REFER_CONFIRMED, /* Refer confirmed with 100 trying */ + REFER_RINGING, /* Target Ringing */ + REFER_200OK, /* Answered by transfer target */ + REFER_FAILED, /* REFER declined - go on */ + REFER_NOAUTH /* We had no auth for REFER */ +}; + /* sip_pvt: PVT structures are used for each SIP conversation, ie. a call */ static struct sip_pvt { ast_mutex_t lock; /* Channel private lock */ @@ -562,6 +575,7 @@ char refer_to[AST_MAX_EXTENSION]; /* Place to store REFER-TO extension */ char referred_by[AST_MAX_EXTENSION]; /* Place to store REFERRED-BY extension */ char refer_contact[AST_MAX_EXTENSION]; /* Place to store Contact info from a REFER extension */ + enum referstatus refer_status; /* Status of REFER transaction */ struct sip_pvt *refer_call; /* Call we are referring */ struct sip_route *route; /* Head of linked list of routing steps (fm Record-Route) */ int route_persistant; /* Is this the "real" route? */ @@ -2513,10 +2527,11 @@ return res; } -#define DEFAULT_MAX_FORWARDS 70 +#define DEFAULT_MAX_FORWARDS 70 /* According to RFC 3261 */ -/*--- sip_transfer: Transfer SIP call */ +/*--- sip_transfer: Transfer SIP call - part of PBX interface */ +/* This is called from app_transfer via ast_transfer */ static int sip_transfer(struct ast_channel *ast, const char *dest) { struct sip_pvt *p = ast->tech_pvt; @@ -3936,12 +3951,12 @@ return -1; } respprep(&resp, p, msg, req); - add_header_contentLength(&resp, 0); /* If we are cancelling an incoming invite for some reason, add information about the reason why we are doing this in clear text */ - if (p->owner && p->owner->hangupcause) { + if (p->owner && p->owner->hangupcause && req->method == SIP_INVITE) { add_header(&resp, "X-Asterisk-HangupCause", ast_cause2str(p->owner->hangupcause)); } + add_header_contentLength(&resp, 0); add_blank_header(&resp); return send_response(p, &resp, reliable, seqno); } @@ -4003,7 +4018,7 @@ return send_response(p, &resp, reliable, 0); } -/* transmit_response_with_auth: Respond with authorization request */ +/*--- transmit_response_with_auth: Respond with authorization request */ static int transmit_response_with_auth(struct sip_pvt *p, char *msg, struct sip_request *req, char *randdata, int reliable, char *header, int stale) { struct sip_request resp; @@ -4792,7 +4807,7 @@ char *t = tmp; size_t maxbytes = sizeof(tmp); - initreqprep(&req, p, SIP_NOTIFY); + initreqprep(&req, p, SIP_NOTIFY); add_header(&req, "Event", "message-summary"); add_header(&req, "Content-Type", default_notifymime); @@ -5168,7 +5183,7 @@ { struct sip_request req; char from[256]; - char *of, *c; + char *of, *domain; char referto[256]; char tmp[80]; @@ -5184,14 +5199,14 @@ } else of += 4; /* Get just the username part */ - if ((c = strchr(dest, '@'))) { - c = NULL; - } else if ((c = strchr(of, '@'))) { - *c = '\0'; - c++; + if ((domain = strchr(dest, '@'))) { + domain = NULL; + } else if ((domain = strchr(of, '@'))) { + *domain = '\0'; + domain++; } - if (c) { - snprintf(referto, sizeof(referto), "", dest, c); + if (domain) { + snprintf(referto, sizeof(referto), "", dest, domain); } else { snprintf(referto, sizeof(referto), "", dest); } @@ -8079,23 +8094,30 @@ /*--- handle_request_info: Receive SIP INFO Message ---*/ /* Doesn't read the duration of the DTMF signal */ -static void handle_request_info(struct sip_pvt *p, struct sip_request *req) +int handle_request_info(struct sip_pvt *p, struct sip_request *req, int ignore) { char buf[1024] = ""; unsigned int event; char resp = 0; struct ast_frame f; char *c; + char *contentheader; + + if (ignore) { + transmit_response(p, "200 OK", req); /* Should return error */ + return -1; + } + contentheader = get_header(req, "Content-Type"); /* Need to check the media/type */ - if (!strcasecmp(get_header(req, "Content-Type"), "application/dtmf-relay") || - !strcasecmp(get_header(req, "Content-Type"), "application/vnd.nortelnetworks.digits")) { + if (!strcasecmp(contentheader, "application/dtmf-relay") || + !strcasecmp(contentheader, "application/vnd.nortelnetworks.digits")) { /* Try getting the "signal=" part */ if (ast_strlen_zero(c = get_sdp(req, "Signal")) && ast_strlen_zero(c = get_sdp(req, "d"))) { ast_log(LOG_WARNING, "Unable to retrieve DTMF signal from INFO message from %s\n", p->callid); transmit_response(p, "200 OK", req); /* Should return error */ - return; + return -1; } else { ast_copy_string(buf, c, sizeof(buf)); } @@ -8131,18 +8153,18 @@ ast_queue_frame(p->owner, &f); } transmit_response(p, "200 OK", req); - return; + return 0; } else { transmit_response(p, "481 Call leg/transaction does not exist", req); ast_set_flag(p, SIP_NEEDDESTROY); } - return; - } else if (!strcasecmp(get_header(req, "Content-Type"), "application/media_control+xml")) { + return 0; + } else if (!strcasecmp(contentheader, "application/media_control+xml")) { /* Eh, we'll just assume it's a fast picture update for now */ if (p->owner) ast_queue_control(p->owner, AST_CONTROL_VIDUPDATE); transmit_response(p, "200 OK", req); - return; + return 0; } else if ((c = get_header(req, "X-ClientCode"))) { /* Client code (from SNOM phone) */ if (ast_test_flag(p, SIP_USECLIENTCODE)) { @@ -8152,16 +8174,18 @@ ast_cdr_setuserfield(ast_bridged_channel(p->owner), c); transmit_response(p, "200 OK", req); } else { - transmit_response(p, "403 Unauthorized", req); + transmit_response(p, "403 Forbidden", req); + return -1; } - return; + return 0; } /* Other type of INFO message, not really understood by Asterisk */ /* if (get_msg_text(buf, sizeof(buf), req)) { */ - ast_log(LOG_WARNING, "Unable to parse INFO message from %s. Content %s\n", p->callid, buf); + if (sipdebug) + ast_log(LOG_DEBUG, "Unable to parse INFO message from %s. Content %s\n", p->callid, buf); transmit_response(p, "415 Unsupported media type", req); - return; + return -1; } /*--- sip_do_debug: Enable SIP Debugging in CLI ---*/ @@ -9047,6 +9071,62 @@ return 1; } +/*--- handle_response_refer: Handle SIP response in dialogue ---*/ +/* We've sent a REFER, now handle responses to it */ +/* At this point, this function does not do much since we do + not follow up outbound REFERs caused by app transfer() + at all... + */ +static int handle_response_refer(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int ignore, int seqno) +{ + char *auth = "Proxy-Authenticate"; + char *auth2 = "Proxy-Authorization"; + + switch (resp) { + case 202: /* Transfer accepted */ + /* We need to do something here */ + /* The transferee is now sending INVITE to target */ + p->refer_status = REFER_ACCEPTED; + /* Now wait for next message */ + if (option_debug > 2) + ast_log(LOG_DEBUG, "Got 202 accepted on transfer\n"); + /* We should hang along, waiting for NOTIFY's here */ + break; + + case 401: /* Not www-authorized on SIP method */ + case 407: /* Proxy auth */ + if (resp == 401) { + auth = "WWW-Authenticate"; + auth2 = "Authorization"; + } + p->theirtag[0] = '\0'; + if ((p->authtries == MAX_AUTHTRIES) || do_proxy_auth(p, req, auth, auth2, SIP_REFER, 0)) { + ast_log(LOG_NOTICE, "Failed to authenticate on REFER to '%s'\n", get_header(&p->initreq, "From")); + p->refer_status = REFER_NOAUTH; + ast_set_flag(p, SIP_NEEDDESTROY); + } + break; + + + case 500: /* Server error */ + case 501: /* Method not implemented */ + case 503: /* Service Unavailable */ + /*OEJ: Return to the current call onhold */ + /* Status flag needed to be reset */ + p->refer_status = REFER_FAILED; + ast_log(LOG_NOTICE, "SIP transfer to %s failed, call miserably fails. \n", p->refer_to); + break; + case 603: /* Transfer declined */ + p->refer_status = REFER_FAILED; + ast_log(LOG_NOTICE, "SIP transfer to %s declined, call fails. \n", p->refer_to); + ast_set_flag(p, SIP_NEEDDESTROY); + break; + } + if (p->refer_status == REFER_FAILED) + return -1; + return 0; +} + /*--- handle_response: Handle SIP response in dialogue ---*/ static void handle_response(struct sip_pvt *p, int resp, char *rest, struct sip_request *req, int ignore, int seqno) { @@ -9174,8 +9254,15 @@ check_pendings(p); } else if (sipmethod == SIP_REGISTER) { res = handle_response_register(p, resp, rest, req, ignore, seqno); + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } break; + case 202: /* Accepted */ + if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); + } + break; case 401: /* Not www-authorized on SIP method */ if (sipmethod == SIP_INVITE) { /* First we ACK */ @@ -9191,6 +9278,8 @@ } } else if (p->registry && sipmethod == SIP_REGISTER) { res = handle_response_register(p, resp, rest, req, ignore, seqno); + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } else { ast_log(LOG_WARNING, "Got authentication request (401) on unknown %s to '%s'\n", sip_methods[sipmethod].text, get_header(req, "To")); ast_set_flag(p, SIP_NEEDDESTROY); @@ -9206,6 +9295,8 @@ ast_set_flag(p, SIP_NEEDDESTROY); } else if (p->registry && sipmethod == SIP_REGISTER) { res = handle_response_register(p, resp, rest, req, ignore, seqno); + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } else { ast_log(LOG_WARNING, "Forbidden - wrong password on authentication for %s\n", msg); } @@ -9213,6 +9304,8 @@ case 404: /* Not found */ if (p->registry && sipmethod == SIP_REGISTER) { res = handle_response_register(p, resp, rest, req, ignore, seqno); + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } else if (owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); break; @@ -9243,6 +9336,8 @@ } } else if (p->registry && sipmethod == SIP_REGISTER) { res = handle_response_register(p, resp, rest, req, ignore, seqno); + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } else ast_set_flag(p, SIP_NEEDDESTROY); @@ -9251,8 +9346,11 @@ if (sipmethod == SIP_INVITE) { if (p->owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); - } else + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); + } else { ast_log(LOG_WARNING, "Host '%s' does not implement '%s'\n", ast_inet_ntoa(iabuf, sizeof(iabuf), p->sa.sin_addr), msg); + } break; default: if ((resp >= 300) && (resp < 700)) { @@ -9300,7 +9398,6 @@ case 410: /* Gone */ case 400: /* Bad Request */ case 500: /* Server error */ - case 503: /* Service Unavailable */ if (owner) ast_queue_control(p->owner, AST_CONTROL_CONGESTION); break; @@ -9335,17 +9432,28 @@ if (sip_debug_test_pvt(p)) ast_verbose("Response message %s arrived\n", msg); switch(resp) { - case 200: + case 200: /* OK */ + /* Most of these are just acks handled in sip_ack */ /* Change branch since this is a 200 response */ if (sipmethod == SIP_INVITE) { transmit_request(p, SIP_ACK, seqno, 0, 1); p->authtries = 0; + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } else if (sipmethod == SIP_MESSAGE) /* We successfully transmitted a message */ ast_set_flag(p, SIP_NEEDDESTROY); break; + case 202: /* Accepted */ + if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); + } + case 401: + if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); + } case 407: - if (sipmethod == SIP_BYE || sipmethod == SIP_REFER) { + if (sipmethod == SIP_BYE) { if (ast_strlen_zero(p->authname)) ast_log(LOG_WARNING, "Asked to authenticate %s, to %s:%d but we have no matching peer!\n", msg, ast_inet_ntoa(iabuf, sizeof(iabuf), p->recv.sin_addr), ntohs(p->recv.sin_port)); @@ -9353,6 +9461,8 @@ ast_log(LOG_NOTICE, "Failed to authenticate on %s to '%s'\n", msg, get_header(&p->initreq, "From")); ast_set_flag(p, SIP_NEEDDESTROY); } + } else if (sipmethod == SIP_REFER) { + res = handle_response_refer(p, resp, rest, req, ignore, seqno); } break; } @@ -10123,6 +10233,147 @@ return res; } +/*--- handle_request_notify: Handle incoming notifications */ +static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, int debug, int ignore, struct sockaddr_in *sin, int seqno, char *e) +{ + int res = 0; + char *event = get_header(req, "Event"); + char *eventid = NULL; + char *sep; + + if( (sep = strchr(event, ';')) ) { + *sep = '\0'; + eventid = ++sep; + } + + if (option_debug > 1 && sipdebug) + ast_log(LOG_DEBUG, "Got NOTIFY Event: %s\n", event); + + if (strcmp(event, "refer")) { + /* We don't understand this event. */ + transmit_response(p, "489 Bad event", req); + return -1; + } else { + /* Handle REFER notifications */ + + char buf[1024]; + char *cmd, *code; + int respcode; + + /* EventID for each transfer... EventID is basically + the REFER cseq */ + + /* We are getting notifications on a call that we transfered */ + /* We should set transferstatus only + when we are getting a 200 OK in a sipfrag */ + + if (!p->lastinvite) { /* Notify out of context */ + transmit_response(p, "481 Call leg/transaction does not exist", req); + ast_set_flag(p, SIP_ALREADYGONE); + return -1; + } + if (!p->owner) { /* No call is going on any more */ + transmit_response(p, "481 Call leg/transaction does not exist", req); + ast_set_flag(p, SIP_ALREADYGONE); + return -1; + } + + /* Check the content type */ + if (strncasecmp(get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) { + /* We need a sipfrag */ + transmit_response(p, "400 Bad request", req); + return -1; + } + + /* Get the text of the attachment */ + if (get_msg_text(buf, sizeof(buf), req)) { + ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid); + return -1; + } + + if (option_debug > 2) + ast_log(LOG_DEBUG, "* SIP Transfer NOTIFY Attachment: \n---%s\n---\n", buf); + cmd = ast_skip_blanks(buf); + code = cmd; + + /* We are at SIP/2.0 */ + while(*code && (*code > 32)) { /* Search white space */ + code++; + } + *code = '\0'; + code++; + code = ast_skip_blanks(code); + sep = code; + sep++; + while(*sep && (*sep > 32)) { /* Search white space */ + sep++; + } + *sep = '\0'; + sep++; /* Response string */ + respcode = atoi(code); + if (option_debug > 2) + ast_log(LOG_DEBUG, "*** SIP transfer: REFER response %d!\n", respcode); + switch (respcode) { + case 100: /* Trying: */ + p->refer_status = REFER_CONFIRMED; + /* Don't do anything yet */ + break; + case 183: /* Ringing: */ + p->refer_status = REFER_RINGING; + /* Don't do anything yet */ + break; + case 200: /* OK: The new call is up, hangup this call */ + p->refer_status = REFER_200OK; + ast_log(LOG_DEBUG, "*** SIP transfer: Transfer succeeded!\n"); + /* Hangup the call that we are replacing */ + break; + case 301: /* Moved permanently */ + case 302: /* Moved temporarily */ + /* Do we get the header in the packet in this case? */ + p->refer_status = REFER_FAILED; + break; + case 487: /* Request terminated */ + p->refer_status = REFER_FAILED; + break; + case 503: /* Service Unavailable: The new call failed */ + /* Cancel transfer, continue the call */ + p->refer_status = REFER_FAILED; + ast_log(LOG_DEBUG, "*** SIP transfer: Cancelled!\n"); + break; + case 603: /* Declined: Not accepted */ + /* Cancel transfer, continue the current call */ + p->refer_status = REFER_FAILED; + break; + default: /* Unknown code */ + p->refer_status = REFER_FAILED; + break; + } + if (p->refer_status == REFER_FAILED) { + ast_log(LOG_NOTICE, "Transfer(): failed. No recovery provedure implemented, sorry.\n"); + ast_set_flag(p, SIP_ALREADYGONE); + } + + /* Confirm that we received this packet */ + transmit_response(p, "200 OK", req); + + /* Hangup after saying 200 OK on Notify */ + if (respcode == 200) /* Transfer succeeded */ + ast_hangup(p->owner); /* We don't need this call any more */ + /* At this point we should return from sip_transfer, but since + we have no "watcher" thread, there's not much we can do. + */ + return res; + }; + + /* --- Unknown NOTIFY arrives here */ + + /* This could be voicemail notification */ + transmit_response(p, "200 OK", req); + if (!p->lastinvite) + ast_set_flag(p, SIP_NEEDDESTROY); + return res; +} + /*--- handle_request: Handle SIP requests (methods) ---*/ /* this is where all incoming requests go first */ static int handle_request(struct sip_pvt *p, struct sip_request *req, struct sockaddr_in *sin, int *recount, int *nounlock) @@ -10259,20 +10510,10 @@ res = handle_request_register(p, req, debug, ignore, sin, e); break; case SIP_INFO: - if (!ignore) { - if (debug) - ast_verbose("Receiving INFO!\n"); - handle_request_info(p, req); - } else { /* if ignoring, transmit response */ - transmit_response(p, "200 OK", req); - } + res = handle_request_info(p, req, ignore); break; case SIP_NOTIFY: - /* XXX we get NOTIFY's from some servers. WHY?? Maybe we should - look into this someday XXX */ - transmit_response(p, "200 OK", req); - if (!p->lastinvite) - ast_set_flag(p, SIP_NEEDDESTROY); + res = handle_request_notify(p, req, debug, ignore, sin, seqno, e); break; case SIP_ACK: /* Make sure we don't ignore this */