Index: channels/chan_sip.c =================================================================== RCS file: /usr/cvsroot/asterisk/channels/chan_sip.c,v retrieving revision 1.822 diff -u -r1.822 chan_sip.c --- channels/chan_sip.c 27 Aug 2005 23:55:14 -0000 1.822 +++ channels/chan_sip.c 28 Aug 2005 22:38:38 -0000 @@ -136,6 +136,28 @@ bad things will happen. */ +enum subscriptiontype { + NONE = 0, + XPIDF_XML, + DIALOG_INFO_XML, + CPIM_PIDF_XML, + PIDF_XML +}; + +static const struct cfsubscription_types { + enum subscriptiontype type; + const char *event; + const char *mediatype; + const char *text; +} subscription_types[] = { + { NONE, "-", "unknown", "unknown" }, + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + { DIALOG_INFO_XML, "dialog", "application/dialog-info+xml", "dialog-info+xml" }, + { CPIM_PIDF_XML, "presence", "application/cpim-pidf+xml", "cpim-pidf+xml" }, /* RFC 3863 */ + { PIDF_XML, "presence", "application/pidf+xml", "pidf+xml" }, /* RFC 3863 */ + { XPIDF_XML, "presence", "application/xpidf+xml", "xpidf+xml" } /* Pre-RFC 3863 with MS additions */ +}; + enum sipmethod { SIP_UNKNOWN, SIP_RESPONSE, @@ -270,7 +292,7 @@ /* SIP Methods we support */ -#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY" +#define ALLOWED_METHODS "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY" /* SIP Extensions we support */ #define SUPPORTED_EXTENSIONS "replaces" @@ -282,6 +304,7 @@ #define DEFAULT_CONTEXT "default" static char default_context[AST_MAX_CONTEXT] = DEFAULT_CONTEXT; +static char default_subscribecontext[AST_MAX_CONTEXT]; #define DEFAULT_VMEXTEN "asterisk" static char global_vmexten[AST_MAX_EXTENSION] = DEFAULT_VMEXTEN; @@ -296,6 +319,7 @@ #define DEFAULT_NOTIFYMIME "application/simple-message-summary" static char default_notifymime[AST_MAX_EXTENSION] = DEFAULT_NOTIFYMIME; +static int global_notifyringing = 1; /* Send notifications on ringing */ static int default_qualify = 0; /* Default Qualify= setting */ @@ -544,6 +568,7 @@ char from[256]; /* The From: header */ char useragent[256]; /* User agent in SIP request */ char context[AST_MAX_CONTEXT]; /* Context for this call */ + char subscribecontext[AST_MAX_CONTEXT]; /* Subscribecontext */ char fromdomain[MAXHOSTNAMELEN]; /* Domain to show in the from field */ char fromuser[AST_MAX_EXTENSION]; /* User to show in the user field */ char fromname[AST_MAX_EXTENSION]; /* Name to show in the user field */ @@ -589,9 +614,9 @@ int rtptimeout; /* RTP timeout time */ int rtpholdtimeout; /* RTP timeout when on hold */ int rtpkeepalive; /* Send RTP packets for keepalive */ - - int subscribed; /* Is this call a subscription? */ + enum subscriptiontype subscribed; /* Is this call a subscription? */ int stateid; + int laststate; /* Last known extension state */ int dialogver; struct ast_dsp *vad; /* Voice Activation Detection dsp */ @@ -631,6 +656,7 @@ char secret[80]; /* Password */ char md5secret[80]; /* Password in md5 */ char context[AST_MAX_CONTEXT]; /* Default context for incoming calls */ + char subscribecontext[AST_MAX_CONTEXT]; /* Default context for subscriptions */ char cid_num[80]; /* Caller ID num */ char cid_name[80]; /* Caller ID name */ char accountcode[AST_MAX_ACCOUNT_CODE]; /* Account code */ @@ -662,6 +688,7 @@ char md5secret[80]; /* Password in MD5 */ struct sip_auth *auth; /* Realm authentication list */ char context[AST_MAX_CONTEXT]; /* Default context for incoming calls */ + char subscribecontext[AST_MAX_CONTEXT]; /* Default context for subscriptions */ char username[80]; /* Temporary username until registration */ char accountcode[AST_MAX_ACCOUNT_CODE]; /* Account code */ int amaflags; /* AMA Flags (for billing) */ @@ -836,6 +863,7 @@ static void append_date(struct sip_request *req); /* Append date to SIP packet */ static int determine_firstline_parts(struct sip_request *req); static void sip_dump_history(struct sip_pvt *dialog); /* Dump history to LOG_DEBUG at end of dialog, before destroying data */ +static struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype); /* Definition of this channel for channel registration */ static const struct ast_channel_tech sip_tech = { @@ -1100,7 +1128,7 @@ /* Too many retries */ if (pkt->owner && pkt->method != SIP_OPTIONS) { if (ast_test_flag(pkt, FLAG_FATAL) || sipdebug) /* Tell us if it's critical or if we're debugging */ - ast_log(LOG_WARNING, "Maximum retries exceeded on call %s for seqno %d (%s %s)\n", pkt->owner->callid, pkt->seqno, (ast_test_flag(pkt, FLAG_FATAL)) ? "Critical" : "Non-critical", (ast_test_flag(pkt, FLAG_RESPONSE)) ? "Response" : "Request"); + ast_log(LOG_WARNING, "Maximum retries exceeded on transmission %s for seqno %d (%s %s)\n", pkt->owner->callid, pkt->seqno, (ast_test_flag(pkt, FLAG_FATAL)) ? "Critical" : "Non-critical", (ast_test_flag(pkt, FLAG_RESPONSE)) ? "Response" : "Request"); } else { if (pkt->method == SIP_OPTIONS && sipdebug) ast_log(LOG_WARNING, "Cancelling retransmit of OPTIONs (call id %s) \n", pkt->owner->callid); @@ -2902,6 +2930,7 @@ p->method = intended_method; p->initid = -1; p->autokillid = -1; + p->subscribed = NONE; p->stateid = -1; p->prefs = prefs; if (intended_method != SIP_OPTIONS) /* Peerpoke has it's own system */ @@ -3806,7 +3835,7 @@ snprintf(tmp, sizeof(tmp), "%d", p->expiry); add_header(resp, "Expires", tmp); add_header(resp, "Contact", contact); - } else { + } else if (p->our_contact[0]) { add_header(resp, "Contact", p->our_contact); } if (p->maxforwards) { @@ -3817,7 +3846,7 @@ return 0; } -/*--- reqprep: Initialize a SIP request packet ---*/ +/*--- reqprep: Initialize a SIP request response packet ---*/ static int reqprep(struct sip_request *req, struct sip_pvt *p, int sipmethod, int seqno, int newbranch) { struct sip_request *orig = &p->initreq; @@ -3904,6 +3933,7 @@ return 0; } +/*--- __transmit_response: Base transmit response function */ static int __transmit_response(struct sip_pvt *p, char *msg, struct sip_request *req, int reliable) { struct sip_request resp; @@ -4555,71 +4585,153 @@ } /*--- transmit_state_notify: Used in the SUBSCRIBE notification subsystem ----*/ -static int transmit_state_notify(struct sip_pvt *p, int state, int full) +static int transmit_state_notify(struct sip_pvt *p, int state, int full, int substate) { char tmp[4000], from[256], to[256]; char *t = tmp, *c, *a, *mfrom, *mto; size_t maxbytes = sizeof(tmp); struct sip_request req; + char hint[AST_MAX_EXTENSION]; + char *statestring = "terminated"; + struct cfsubscription_types *subscriptiontype; + enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; + char *pidfstate = "--"; + char *pidfnote= "Ready"; + + switch (state) { + case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): + if (global_notifyringing) + statestring = "early"; + else + statestring = "confirmed"; + 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: + statestring = "confirmed"; + local_state = NOTIFY_CLOSED; + pidfstate = "away"; + pidfnote = "Unavailable"; + break; + 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)) { + /* If they are not registered, we will override notification and show no availability */ + if (ast_device_state(hint) == AST_DEVICE_UNAVAILABLE) { + local_state = NOTIFY_CLOSED; + pidfstate = "away"; + pidfnote = "Not online"; + } + } memset(from, 0, sizeof(from)); - memset(to, 0, sizeof(to)); ast_copy_string(from, get_header(&p->initreq, "From"), sizeof(from)); - c = get_in_brackets(from); if (strncmp(c, "sip:", 4)) { ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); return -1; } - if ((a = strchr(c, ';'))) { + if ((a = strchr(c, ';'))) *a = '\0'; - } mfrom = c; - reqprep(&req, p, SIP_NOTIFY, 0, 1); - - if (p->subscribed == 1) { - ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to)); + memset(to, 0, sizeof(to)); + ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to)); + c = get_in_brackets(to); + if (strncmp(c, "sip:", 4)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); + return -1; + } + if ((a = strchr(c, ';'))) + *a = '\0'; + mto = c; - c = get_in_brackets(to); - if (strncmp(c, "sip:", 4)) { - ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); - return -1; - } - if ((a = strchr(c, ';'))) { - *a = '\0'; - } - mto = c; + reqprep(&req, p, SIP_NOTIFY, 0, 1); - add_header(&req, "Event", "presence"); + + add_header(&req, "Event", subscriptiontype->event); + add_header(&req, "Content-Type", subscriptiontype->mediatype); + switch(state) { + case AST_EXTENSION_DEACTIVATED: + add_header(&req, "Subscription-State", "terminated;reason=probation"); + add_header(&req, "Retry-After", "60"); + break; + case AST_EXTENSION_REMOVED: + add_header(&req, "Subscription-State", "terminated;reason=noresource"); + break; + default: add_header(&req, "Subscription-State", "active"); - add_header(&req, "Content-Type", "application/xpidf+xml"); - - if ((state==AST_EXTENSION_UNAVAILABLE) || (state==AST_EXTENSION_BUSY)) - state = 2; - else if (state==AST_EXTENSION_INUSE) - state = 1; - else - state = 0; - + } + switch (p->subscribed) { + case XPIDF_XML: + case CPIM_PIDF_XML: ast_build_string(&t, &maxbytes, "\n"); ast_build_string(&t, &maxbytes, "\n"); ast_build_string(&t, &maxbytes, "\n"); ast_build_string(&t, &maxbytes, "\n", mfrom); ast_build_string(&t, &maxbytes, "\n", p->exten); ast_build_string(&t, &maxbytes, "
\n", mto); - ast_build_string(&t, &maxbytes, "\n", !state ? "open" : (state==1) ? "inuse" : "closed"); - ast_build_string(&t, &maxbytes, "\n", !state ? "online" : (state==1) ? "onthephone" : "offline"); + ast_build_string(&t, &maxbytes, "\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); + ast_build_string(&t, &maxbytes, "\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); ast_build_string(&t, &maxbytes, "
\n
\n
\n"); - } else { - add_header(&req, "Event", "dialog"); - add_header(&req, "Content-Type", "application/dialog-info+xml"); + break; + case PIDF_XML: /* Eyebeam supports this format */ + ast_build_string(&t, &maxbytes, "\n"); + ast_build_string(&t, &maxbytes, "\n", mfrom); + ast_build_string(&t, &maxbytes, "\n"); + if (pidfstate[0] != '-') + ast_build_string(&t, &maxbytes, "%s\n", pidfstate); + ast_build_string(&t, &maxbytes, "\n"); + ast_build_string(&t, &maxbytes, "%s\n", pidfnote); /* Note */ + ast_build_string(&t, &maxbytes, "\n", p->exten); /* Tuple start */ + ast_build_string(&t, &maxbytes, "%s\n", mto); + if (pidfstate[0] == 'b') /* Busy? Still open ... */ + ast_build_string(&t, &maxbytes, "open\n"); + else + ast_build_string(&t, &maxbytes, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); + ast_build_string(&t, &maxbytes, "\n\n"); + break; + case DIALOG_INFO_XML: /* SNOM subscribes in this format */ ast_build_string(&t, &maxbytes, "\n"); - ast_build_string(&t, &maxbytes, "\n", p->dialogver++, full ? "full":"partial", mfrom); - ast_build_string(&t, &maxbytes, "\n", p->exten); - ast_build_string(&t, &maxbytes, "%s\n", state ? "confirmed" : "terminated"); - ast_build_string(&t, &maxbytes, "\n\n"); + ast_build_string(&t, &maxbytes, "\n", p->dialogver++, full ? "full":"partial", mto); + if ((state & AST_EXTENSION_RINGING) && global_notifyringing) + ast_build_string(&t, &maxbytes, "\n", p->exten); + else + ast_build_string(&t, &maxbytes, "\n", p->exten); + ast_build_string(&t, &maxbytes, "%s\n", statestring); + ast_build_string(&t, &maxbytes, "\n\n"); + break; + case NONE: + default: + break; } + if (t > tmp + sizeof(tmp)) ast_log(LOG_WARNING, "Buffer overflow detected!! (Please file a bug report)\n"); @@ -5548,7 +5660,7 @@ #endif ) return 0; - if (sipmethod == SIP_REGISTER) { + if (sipmethod == SIP_REGISTER || sipmethod == SIP_SUBSCRIBE) { /* 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. */ @@ -5732,25 +5844,30 @@ return res; } -/*--- cb_extensionstate: Part of thte SUBSCRIBE support subsystem ---*/ -static int cb_extensionstate(char *context, char* exten, enum ast_extension_states state, void *data) +/*--- cb_extensionstate: Callback for the devicestate notification (SUBSCRIBE) support subsystem ---*/ +/* If you add an "hint" priority to the extension in the dial plan, + you will get notifications on device state changes */ +static int cb_extensionstate(char *context, char* exten, int state, void *data) { struct sip_pvt *p = data; - switch (state) { - case AST_EXTENSION_DEACTIVATED: - case AST_EXTENSION_REMOVED: - transmit_state_notify(p, state, 1); - sip_scheddestroy(p, 15000); + switch(state) { + case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ + case AST_EXTENSION_REMOVED: /* Extension is gone */ + sip_scheddestroy(p, 15000); /* Delete subscription in 15 secs */ + 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); p->stateid = -1; - return 0; - default: - transmit_state_notify(p, state, 1); - - if (option_debug > 1) - ast_verbose(VERBOSE_PREFIX_1 "Extension Changed %s new state %d for Notify User %s\n", exten, state, p->username); - return 0; + p->subscribed = NONE; + break; + default: /* Tell user */ + p->laststate = state; + break; } + transmit_state_notify(p, state, 1, 1); + + 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; } /*--- register_verify: Verify registration of user */ @@ -6378,6 +6495,7 @@ ast_copy_string(p->cid_name, user->cid_name, sizeof(p->cid_name)); ast_copy_string(p->username, user->name, sizeof(p->username)); ast_copy_string(p->peersecret, user->secret, sizeof(p->peersecret)); + ast_copy_string(p->subscribecontext, user->subscribecontext, sizeof(p->subscribecontext)); ast_copy_string(p->peermd5secret, user->md5secret, sizeof(p->peermd5secret)); ast_copy_string(p->accountcode, user->accountcode, sizeof(p->accountcode)); ast_copy_string(p->language, user->language, sizeof(p->language)); @@ -6440,6 +6558,7 @@ } ast_copy_string(p->peersecret, peer->secret, sizeof(p->peersecret)); p->peersecret[sizeof(p->peersecret)-1] = '\0'; + ast_copy_string(p->subscribecontext, peer->subscribecontext, sizeof(p->subscribecontext)); ast_copy_string(p->peermd5secret, peer->md5secret, sizeof(p->peermd5secret)); p->peermd5secret[sizeof(p->peermd5secret)-1] = '\0'; p->callingpres = peer->callingpres; @@ -6548,16 +6667,28 @@ /*--- receive_message: Receive SIP MESSAGE method messages ---*/ -/* we handle messages within current calls currently */ +/* We only handle messages within current calls currently */ +/* Reference: RFC 3428 */ static void receive_message(struct sip_pvt *p, struct sip_request *req) { char buf[1024]; struct ast_frame f; + char *content_type; + + content_type = get_header(req, "Content-Type"); + if (strcmp(content_type, "text/plain")) { /* No text/plain attachment */ + transmit_response(p, "415 Unsupported Media Type", req); /* Good enough, or? */ + ast_set_flag(p, SIP_NEEDDESTROY); + return; + } if (get_msg_text(buf, sizeof(buf), req)) { ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid); + transmit_response(p, "202 Accepted", req); + ast_set_flag(p, SIP_NEEDDESTROY); return; } + if (p->owner) { if (sip_debug_test_pvt(p)) ast_verbose("Message received: '%s'\n", buf); @@ -6568,7 +6699,13 @@ f.data = buf; f.datalen = strlen(buf); ast_queue_frame(p->owner, &f); + transmit_response(p, "202 Accepted", req); /* We respond 202 accepted, since we relay the message */ + } else { /* Message outside of a call, we do not support that */ + ast_log(LOG_WARNING,"Received message to %s from %s, dropped it...\n Content-Type:%s\n Message: %s\n", get_header(req,"To"), get_header(req,"From"), content_type, buf); + transmit_response(p, "405 Method Not Allowed", req); /* Good enough, or? */ } + ast_set_flag(p, SIP_NEEDDESTROY); + return; } /*--- sip_show_inuse: CLI Command to show calls within limits set by @@ -7194,6 +7331,7 @@ auth = auth->next; } ast_cli(fd, " Context : %s\n", peer->context); + ast_cli(fd, " Subscr.Cont. : %s\n", ast_strlen_zero(peer->subscribecontext)?"":peer->subscribecontext); ast_cli(fd, " Language : %s\n", peer->language); if (!ast_strlen_zero(peer->accountcode)) ast_cli(fd, " Accountcode : %s\n", peer->accountcode); @@ -7486,6 +7624,7 @@ ast_cli(fd, " Reg. default duration: %d secs\n", default_expiry); ast_cli(fd, " Outbound reg. timeout: %d secs\n", global_reg_timeout); ast_cli(fd, " Outbound reg. attempts: %d\n", global_regattempts_max); + ast_cli(fd, " Notify ringing state: %s\n", global_notifyringing ? "Yes" : "No"); ast_cli(fd, "\nDefault Settings:\n"); ast_cli(fd, "-----------------\n"); ast_cli(fd, " Context: %s\n", default_context); @@ -7513,6 +7652,30 @@ return RESULT_SUCCESS; } +/*--- subscription_type2str: Show subscription type in string format */ +static char *subscription_type2str(enum subscriptiontype subtype) { + int i; + + for (i = 1; (i < (sizeof(subscription_types) / sizeof(subscription_types[0]))); i++) { + if (subscription_types[i].type == subtype) { + return (char (*))subscription_types[i].text; + } + } + return (char (*))subscription_types[0].text; +} + +/*--- find_subscription_type: Find subscription type in array */ +static struct cfsubscription_types *find_subscription_type(enum subscriptiontype subtype) { + int i; + + for (i = 1; (i < (sizeof(subscription_types) / sizeof(subscription_types[0]))); i++) { + if (subscription_types[i].type == subtype) { + return (struct cfsubscription_types (*))&subscription_types[i]; + } + } + return (struct cfsubscription_types (*))&subscription_types[0]; +} + /* Forward declaration */ static int __sip_show_channels(int fd, int argc, char *argv[], int subscriptions); @@ -7530,9 +7693,9 @@ static int __sip_show_channels(int fd, int argc, char *argv[], int subscriptions) { -#define FORMAT3 "%-15.15s %-10.10s %-21.21s %-15.15s\n" -#define FORMAT2 "%-15.15s %-10.10s %-11.11s %-11.11s %-4.4s %-7.7s %s \n" -#define FORMAT "%-15.15s %-10.10s %-11.11s %5.5d/%5.5d %-4.4s %-7.7s%s %s\n" +#define FORMAT3 "%-15.15s %-10.10s %-11.11s %-15.15s %-13.13s %-15.15s\n" +#define FORMAT2 "%-15.15s %-10.10s %-11.11s %-11.11s %-4.4s %-7.7s %-15.15s\n" +#define FORMAT "%-15.15s %-10.10s %-11.11s %5.5d/%5.5d %-4.4s %-3.3s %-3.3s %-15.15s\n" struct sip_pvt *cur; char iabuf[INET_ADDRSTRLEN]; int numchans = 0; @@ -7541,11 +7704,11 @@ ast_mutex_lock(&iflock); cur = iflist; if (!subscriptions) - ast_cli(fd, FORMAT2, "Peer", "User/ANR", "Call ID", "Seq (Tx/Rx)", "Format", "Hold", "Last Msg"); + ast_cli(fd, FORMAT2, "Peer", "User/ANR", "Call ID", "Seq (Tx/Rx)", "Format", "Hold", "Last Message"); else - ast_cli(fd, FORMAT3, "Peer", "User", "Call ID", "URI"); + ast_cli(fd, FORMAT3, "Peer", "User", "Call ID", "Extension", "Last state", "Type"); while (cur) { - if (!cur->subscribed && !subscriptions) { + if (cur->subscribed == NONE && !subscriptions) { ast_cli(fd, FORMAT, ast_inet_ntoa(iabuf, sizeof(iabuf), cur->sa.sin_addr), ast_strlen_zero(cur->username) ? ( ast_strlen_zero(cur->cid_num) ? "(None)" : cur->cid_num ) : cur->username, cur->callid, @@ -7556,19 +7719,20 @@ cur->lastmsg ); numchans++; } - if (cur->subscribed && subscriptions) { - ast_cli(fd, FORMAT3, ast_inet_ntoa(iabuf, sizeof(iabuf), cur->sa.sin_addr), - ast_strlen_zero(cur->username) ? ( ast_strlen_zero(cur->cid_num) ? "(None)" : cur->cid_num ) : cur->username, - cur->callid, cur->uri); - - } + if (cur->subscribed != NONE && subscriptions) { + ast_cli(fd, FORMAT3, ast_inet_ntoa(iabuf, sizeof(iabuf), cur->sa.sin_addr), + ast_strlen_zero(cur->username) ? ( ast_strlen_zero(cur->cid_num) ? "(None)" : cur->cid_num ) : cur->username, + cur->callid, cur->exten, ast_extension_state2str(cur->laststate), + subscription_type2str(cur->subscribed)); + numchans++; + } cur = cur->next; } ast_mutex_unlock(&iflock); if (!subscriptions) - ast_cli(fd, "%d active SIP channel(s)\n", numchans); + ast_cli(fd, "%d active SIP channel%s\n", numchans, (numchans != 1) ? "s" : ""); else - ast_cli(fd, "%d active SIP subscriptions(s)\n", numchans); + ast_cli(fd, "%d active SIP subscription%s\n", numchans, (numchans != 1) ? "s" : ""); return RESULT_SUCCESS; #undef FORMAT #undef FORMAT2 @@ -7730,7 +7894,7 @@ while(cur) { if (!strncasecmp(cur->callid, argv[3],len)) { ast_cli(fd,"\n"); - if (cur->subscribed) + if (cur->subscribed != NONE) ast_cli(fd, " * Subscription\n"); else ast_cli(fd, " * SIP Call\n"); @@ -7761,7 +7925,7 @@ ast_cli(fd, " Promiscuous Redir: %s\n", ast_test_flag(cur, SIP_PROMISCREDIR) ? "Yes" : "No"); ast_cli(fd, " Route: %s\n", cur->route ? cur->route->hop : "N/A"); ast_cli(fd, " DTMF Mode: %s\n", dtmfmode2str(ast_test_flag(cur, SIP_DTMF))); - ast_cli(fd, " SIP Options : "); + ast_cli(fd, " SIP Options: "); if (cur->sipoptions) { int x; for (x=0 ; (x < (sizeof(sip_options) / sizeof(sip_options[0]))); x++) { @@ -7800,7 +7964,7 @@ while(cur) { if (!strncasecmp(cur->callid, argv[3], len)) { ast_cli(fd,"\n"); - if (cur->subscribed) + if (cur->subscribed != NONE) ast_cli(fd, " * Subscription\n"); else ast_cli(fd, " * SIP Call\n"); @@ -8901,7 +9065,7 @@ ast_log(LOG_WARNING, "Notify answer on an owned channel?\n"); ast_queue_hangup(p->owner); } else { - if (!p->subscribed) { + if (p->subscribed == NONE) { ast_set_flag(p, SIP_NEEDDESTROY); } } @@ -8923,13 +9087,15 @@ time(&p->ospstart); #endif ast_queue_control(p->owner, AST_CONTROL_ANSWER); + ast_setstate(p->owner, AST_STATE_UP); } else { struct ast_frame af = { AST_FRAME_NULL, }; ast_queue_frame(p->owner, &af); } - } else /* It's possible we're getting an ACK after we've tried to disconnect - by sending CANCEL */ - ast_set_flag(p, SIP_PENDINGBYE); + } else { /* It's possible we're getting an ACK after we've tried to disconnect by sending CANCEL */ + ast_set_flag(p, SIP_PENDINGBYE); + } + ast_device_state_changed("SIP/%s", p->peername); /* If I understand this right, the branch is different for a non-200 ACK only */ transmit_request(p, SIP_ACK, seqno, 0, 1); check_pendings(p); @@ -9699,16 +9865,12 @@ /*--- handle_request_message: Handle incoming MESSAGE request ---*/ static int handle_request_message(struct sip_pvt *p, struct sip_request *req, int debug, int ignore) { - if (p->lastinvite) { - if (!ignore) { - if (debug) - ast_verbose("Receiving message!\n"); - receive_message(p, req); - } - transmit_response(p, "200 OK", req); + if (!ignore) { + if (debug) + ast_verbose("Receiving message!\n"); + receive_message(p, req); } else { - transmit_response(p, "405 Method Not Allowed", req); - ast_set_flag(p, SIP_NEEDDESTROY); + transmit_response(p, "202 Accepted", req); } return 1; } @@ -9719,6 +9881,20 @@ int res = 0; struct ast_channel *c=NULL; + if (p->initreq.headers) { + /* We already have a dialog */ + if (p->initreq.method != SIP_SUBSCRIBE) { + /* This is a SUBSCRIBE within another SIP dialog, which we do not support */ + /* For transfers, this could happen, but since we haven't seen it happening, let us just refuse this */ + transmit_response(p, "403 Forbidden (within dialog)", req); + /* Do not destroy session, since we will break the call if we do */ + ast_log(LOG_DEBUG, "Got a subscription within the context of another call, can't handle that - %s (Method %s)\n", p->callid, sip_methods[p->initreq.method].text); + return 0; + } else { + if (debug) + ast_log(LOG_DEBUG, "Got a re-subscribe on existing subscription %s\n", p->callid); + } + } if (!ignore) { /* Use this as the basis */ if (debug) @@ -9744,7 +9920,9 @@ return 0; } /* Initialize the context if it hasn't been already */ - if (ast_strlen_zero(p->context)) + if (p->subscribecontext && !ast_strlen_zero(p->subscribecontext)) + ast_copy_string(p->context, p->subscribecontext, sizeof(p->context)); + else if (ast_strlen_zero(p->context)) strcpy(p->context, default_context); /* Get destination right away */ gotdest = get_destination(p, NULL); @@ -9753,15 +9931,38 @@ if (gotdest < 0) transmit_response(p, "404 Not Found", req); else - transmit_response(p, "484 Address Incomplete", req); + transmit_response(p, "484 Address Incomplete", req); /* Overlap dialing on SUBSCRIBE?? */ ast_set_flag(p, SIP_NEEDDESTROY); } else { + char *event = get_header(req, "Event"); /* Get Event package name */ + char *accept = get_header(req, "Accept"); + /* Initialize tag */ p->tag = rand(); - if (!strcmp(get_header(req, "Accept"), "application/dialog-info+xml")) - p->subscribed = 2; - else if (!strcmp(get_header(req, "Accept"), "application/simple-message-summary")) { - /* Looks like they actually want a mailbox */ + + if (!strcmp(event, "presence") || !strcmp(event, "dialog")) { /* Presence, RFC 3842 */ + + /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */ + if (strstr(accept, "application/pidf+xml")) { + p->subscribed = PIDF_XML; /* RFC 3863 format */ + } else if (strstr(accept, "application/dialog-info+xml")) { + p->subscribed = DIALOG_INFO_XML; + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + /* Should not be used for SUBSCRIBE, but anyway */ + } else if (strstr(accept, "application/cpim-pidf+xml")) { + p->subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ + } else if (strstr(accept, "application/xpidf+xml")) { + p->subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ + } else if (strstr(p->useragent, "Polycom")) { + p->subscribed = XPIDF_XML; /* Polycoms subscribe for "event: dialog" but don't include an "accept:" header */ + } else { + /* Can't find a format for events that we know about */ + transmit_response(p, "489 Bad Event", req); + ast_set_flag(p, SIP_NEEDDESTROY); + return 0; + } + } else if (!strcmp(event, "message-summary") && !strcmp(accept, "application/simple-message-summary")) { + /* Looks like they actually want a mailbox status */ /* At this point, we should check if they subscribe to a mailbox that has the same extension as the peer or the mailbox id. If we configure @@ -9783,13 +9984,18 @@ transmit_response(p, "200 OK", req); ast_set_flag(p, SIP_NEEDDESTROY); } else { - transmit_response(p, "403 Forbidden", req); + transmit_response(p, "404 Not found", req); ast_set_flag(p, SIP_NEEDDESTROY); } return 0; - } else - p->subscribed = 1; - if (p->subscribed) + } else { /* At this point, Asterisk does not understand the specified event */ + transmit_response(p, "489 Bad Event", req); + if (option_debug > 1) + ast_log(LOG_DEBUG, "Received SIP subscribe for unknown event package: %s\n", event); + ast_set_flag(p, SIP_NEEDDESTROY); + return 0; + } + if (p->subscribed != NONE) p->stateid = ast_extension_state_add(p->context, p->exten, cb_extensionstate, p); } } else @@ -9803,23 +10009,17 @@ ast_set_flag(p, SIP_NEEDDESTROY); return 0; } + /* TODO: Do we need this any longer? And what exactly to remove? */ /* The next line can be removed if the SNOM200 Expires bug is fixed */ - if (p->subscribed == 1) { - if (p->expiry>max_expiry) + if (p->subscribed == DIALOG_INFO_XML) { + if (p->expiry > max_expiry) p->expiry = max_expiry; } - /* Go ahead and free RTP port */ - if (p->rtp && !p->owner) { - ast_rtp_destroy(p->rtp); - p->rtp = NULL; - } - if (p->vrtp && !p->owner) { - ast_rtp_destroy(p->vrtp); - p->vrtp = NULL; - } + if (sipdebug || option_debug > 1) + ast_log(LOG_DEBUG, "Adding subscription for extension %s context %s for peer %s\n", p->exten, p->context, p->username); transmit_response(p, "200 OK", req); - sip_scheddestroy(p, (p->expiry+10)*1000); - transmit_state_notify(p, ast_extension_state(NULL, p->context, p->exten),1); + sip_scheddestroy(p, (p->expiry + 10) * 1000); /* Set timer for destruction of call at expiration */ + transmit_state_notify(p, ast_extension_state(NULL, p->context, p->exten), 1, 1); /* Send first notification */ } return 1; } @@ -10415,13 +10615,27 @@ if (p) { found++; res = AST_DEVICE_UNAVAILABLE; + if (option_debug > 2) + ast_log(LOG_DEBUG, "Checking device state for peer %s\n", dest); if ((p->addr.sin_addr.s_addr || p->defaddr.sin_addr.s_addr) && (!p->maxms || ((p->lastms > -1) && (p->lastms <= p->maxms)))) { - /* peer found and valid */ - res = AST_DEVICE_UNKNOWN; + /* Peer is registred, or has default IP address and a valid registration */ + /* Now check if we know anything about the state. The only way is by implementing + * call control with incominglimit=X in sip.conf where X > 0 + * Check if the device has incominglimit, and if qualify=on, if the device + * is reachable */ + if (p->incominglimit && (p->lastms == 0 || p->lastms <= p->maxms)) { /* Free for a call */ + res = AST_DEVICE_NOT_INUSE; + if (p->inUse) /* On a call */ + res = AST_DEVICE_BUSY; + } else { /* peer found and valid, state unknown */ + res = AST_DEVICE_UNKNOWN; + } } } if (!p && !found) { + if (option_debug > 2) + ast_log(LOG_DEBUG, "Checking device state for DNS host %s\n", dest); hp = ast_gethostbyname(host, &ahp); if (hp) res = AST_DEVICE_UNKNOWN; @@ -10751,6 +10965,8 @@ if (!strcasecmp(v->name, "context")) { ast_copy_string(user->context, v->value, sizeof(user->context)); + } else if (!strcasecmp(v->name, "subscribecontext")) { + ast_copy_string(user->subscribecontext, v->value, sizeof(user->subscribecontext)); } else if (!strcasecmp(v->name, "setvar")) { varname = ast_strdupa(v->value); if (varname && (varval = strchr(varname,'='))) { @@ -10835,6 +11051,7 @@ SIP_DTMF | SIP_NAT | SIP_REINVITE | SIP_INSECURE_PORT | SIP_INSECURE_INVITE | SIP_PROG_INBAND | SIP_OSPAUTH); strcpy(peer->context, default_context); + strcpy(peer->subscribecontext, default_subscribecontext); strcpy(peer->language, default_language); strcpy(peer->musicclass, global_musicclass); peer->addr.sin_port = htons(DEFAULT_SIP_PORT); @@ -10876,9 +11093,9 @@ /* Already in the list, remove it and it will be added back (or FREE'd) */ found++; } else { - peer = malloc(sizeof(struct sip_peer)); + peer = malloc(sizeof(*peer)); if (peer) { - memset(peer, 0, sizeof(struct sip_peer)); + memset(peer, 0, sizeof(*peer)); if (realtime) rpeerobjs++; else @@ -10909,6 +11126,7 @@ peer->chanvars = NULL; } strcpy(peer->context, default_context); + strcpy(peer->subscribecontext, default_subscribecontext); strcpy(peer->vmexten, global_vmexten); strcpy(peer->language, default_language); strcpy(peer->musicclass, global_musicclass); @@ -10959,6 +11177,8 @@ ast_callerid_split(v->value, peer->cid_name, sizeof(peer->cid_name), peer->cid_num, sizeof(peer->cid_num)); } else if (!strcasecmp(v->name, "context")) { ast_copy_string(peer->context, v->value, sizeof(peer->context)); + } else if (!strcasecmp(v->name, "subscribecontext")) { + ast_copy_string(peer->subscribecontext, v->value, sizeof(peer->subscribecontext)); } else if (!strcasecmp(v->name, "fromdomain")) ast_copy_string(peer->fromdomain, v->value, sizeof(peer->fromdomain)); else if (!strcasecmp(v->name, "usereqphone")) @@ -11155,6 +11375,7 @@ /* Initialize some reasonable defaults at SIP reload */ ast_copy_string(default_context, DEFAULT_CONTEXT, sizeof(default_context)); + default_subscribecontext[0] = '\0'; default_language[0] = '\0'; default_fromdomain[0] = '\0'; default_qualify = 0; @@ -11163,6 +11384,7 @@ externrefresh = 10; ast_copy_string(default_useragent, DEFAULT_USERAGENT, sizeof(default_useragent)); ast_copy_string(default_notifymime, DEFAULT_NOTIFYMIME, sizeof(default_notifymime)); + global_notifyringing = 1; ast_copy_string(global_realm, DEFAULT_REALM, sizeof(global_realm)); ast_copy_string(global_musicclass, "default", sizeof(global_musicclass)); ast_copy_string(default_callerid, DEFAULT_CALLERID, sizeof(default_callerid)); @@ -11258,6 +11480,8 @@ compactheaders = ast_true(v->value); } else if (!strcasecmp(v->name, "notifymimetype")) { ast_copy_string(default_notifymime, v->value, sizeof(default_notifymime)); + } else if (!strcasecmp(v->name, "notifyringing")) { + global_notifyringing = ast_true(v->value); } else if (!strcasecmp(v->name, "musicclass") || !strcasecmp(v->name, "musiconhold")) { ast_copy_string(global_musicclass, v->value, sizeof(global_musicclass)); } else if (!strcasecmp(v->name, "language")) { Index: pbx.c =================================================================== RCS file: /usr/cvsroot/asterisk/pbx.c,v retrieving revision 1.267 diff -u -r1.267 pbx.c --- pbx.c 27 Aug 2005 23:55:14 -0000 1.267 +++ pbx.c 28 Aug 2005 22:38:39 -0000 @@ -1738,7 +1738,7 @@ char *cur, *rest; int res = -1; int allunavailable = 1, allbusy = 1, allfree = 1; - int busy = 0; + int busy = 0, inuse = 0, ring = 0; if (!e) return -1; @@ -1749,7 +1749,7 @@ do { rest = strchr(cur, '&'); if (rest) { - *rest = 0; + *rest = 0; rest++; } @@ -1760,7 +1760,15 @@ allbusy = 0; break; case AST_DEVICE_INUSE: - return AST_EXTENSION_INUSE; + inuse = 1; + allunavailable = 0; + allfree = 0; + break; + case AST_DEVICE_RINGING: + ring = 1; + allunavailable = 0; + allfree = 0; + break; case AST_DEVICE_BUSY: allunavailable = 0; allfree = 0; @@ -1779,7 +1787,13 @@ cur = rest; } while (cur); - if (allfree) + if (!inuse && ring) + return AST_EXTENSION_RINGING; + if (inuse && ring) + return (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING); + if (inuse) + return AST_EXTENSION_INUSE; + if (allfree) return AST_EXTENSION_NOT_INUSE; if (allbusy) return AST_EXTENSION_BUSY; @@ -1791,6 +1805,18 @@ return AST_EXTENSION_NOT_INUSE; } +/*--- ast_extension_state2str: Return extension_state as string */ +char *ast_extension_state2str(int extension_state) +{ + int i; + + for (i = 0; (i < (sizeof(extension_states) / sizeof(extension_states[0]))); i++) { + if (extension_states[i].extension_state == extension_state) { + return (char (*))extension_states[i].text; + } + } + return "Unknown"; +} /*--- ast_extension_state: Check extension state for an extension by using hint */ int ast_extension_state(struct ast_channel *c, char *context, char *exten) Index: include/asterisk/pbx.h =================================================================== RCS file: /usr/cvsroot/asterisk/include/asterisk/pbx.h,v retrieving revision 1.50 diff -u -r1.50 pbx.h --- include/asterisk/pbx.h 27 Aug 2005 23:55:14 -0000 1.50 +++ include/asterisk/pbx.h 28 Aug 2005 21:39:37 -0000 @@ -42,11 +42,26 @@ /*! No device INUSE or BUSY */ AST_EXTENSION_NOT_INUSE = 0, /*! One or more devices INUSE */ - AST_EXTENSION_INUSE = 1, + AST_EXTENSION_INUSE = 1 << 0, /*! All devices BUSY */ - AST_EXTENSION_BUSY = 2, + AST_EXTENSION_BUSY = 1 << 1, /*! All devices UNAVAILABLE/UNREGISTERED */ - AST_EXTENSION_UNAVAILABLE = 3, + AST_EXTENSION_UNAVAILABLE = 1 << 2, + /*! All devices RINGING */ + AST_EXTENSION_RINGING = 1 << 3, +}; + + +static const struct cfextension_states { + int extension_state; + const char *text; +} extension_states[] = { + { AST_EXTENSION_NOT_INUSE, "Idle" }, + { AST_EXTENSION_INUSE, "InUse" }, + { AST_EXTENSION_BUSY, "Busy" }, + { AST_EXTENSION_UNAVAILABLE, "Unavailable" }, + { AST_EXTENSION_RINGING, "Ringing" }, + { AST_EXTENSION_INUSE | AST_EXTENSION_RINGING, "InUse&Ringing" } }; struct ast_context; @@ -257,6 +272,13 @@ */ int ast_extension_state(struct ast_channel *c, char *context, char *exten); +/*! Return string of the state of an extension */ +/*! + * \param extension_state is the numerical state delivered by ast_extension_state + * Returns the state of an extension as string + */ +extern char *ast_extension_state2str(int extension_state); + /*! Registers a state change callback */ /*! * \param context which context to look in