Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 372129) +++ channels/chan_sip.c (working copy) @@ -612,7 +612,8 @@ { 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 */ - { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" } /* RFC 3842: Mailbox notification */ + { MWI_NOTIFICATION, "message-summary", "application/simple-message-summary", "mwi" }, /* RFC 3842: Mailbox notification */ + { DIALOG_RLMI_XML, "", "application/rlmi+xml", "rlmi+xml" } /* RFC 4662 todohere fix boundary */ }; /*! \brief The core structure to setup dialogs. We parse incoming messages by using @@ -1110,6 +1111,9 @@ static struct ao2_container *peers; static struct ao2_container *peers_by_ip; +/*! \brief The list of resource lists... which sounds ridiculous */ +struct ao2_container *rlists; + /*! \brief The register list: Other SIP proxies we register with and receive calls from */ static struct ast_register_list { ASTOBJ_CONTAINER_COMPONENTS(struct sip_registry); @@ -1355,6 +1359,12 @@ static int attempt_transfer(struct sip_dual *transferer, struct sip_dual *target); static int do_magic_pickup(struct ast_channel *channel, const char *extension, const char *context); +/* add and remove resource list watchers */ +static int rlist_add_watcher(struct sip_pvt *p); +static int rlist_remove_watcher(struct sip_pvt *p); +static int rlist_start_monitor(struct sip_rlist *rlist); +static int rlist_stop_monitor(struct sip_rlist *rlist); + /*--- Device monitoring and Device/extension state/event handling */ static int cb_extensionstate(char *context, char* exten, int state, void *data); static int sip_devicestate(void *data); @@ -1367,6 +1377,7 @@ /*--- Applications, functions, CLI and manager command helpers */ static const char *sip_nat_mode(const struct sip_pvt *p); +static char *sip_show_rlists(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a); static char *transfermode2str(enum transfermodes mode) attribute_const; static int peer_status(struct sip_peer *peer, char *status, int statuslen); @@ -1430,6 +1441,7 @@ /*--- Device object handling */ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only); +static struct sip_rlist *build_rlist(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime); static int update_call_counter(struct sip_pvt *fup, int event); static void sip_destroy_peer(struct sip_peer *peer); static void sip_destroy_peer_fn(void *peer); @@ -1492,6 +1504,7 @@ static enum sip_get_dest_result get_destination(struct sip_pvt *p, struct sip_request *oreq, int *cc_recall_core_id); static int get_msg_text(char *buf, int len, struct sip_request *req); static int transmit_state_notify(struct sip_pvt *p, int state, int full, int timeout); +static int transmit_state_notify_rlist(struct sip_pvt *p, int state, int full, const char *exten); static void update_connectedline(struct sip_pvt *p, const void *data, size_t datalen); static void update_redirecting(struct sip_pvt *p, const void *data, size_t datalen); static int get_domain(const char *str, char *domain, int len); @@ -2980,6 +2993,10 @@ ast_extension_state_del(dialog->stateid, cb_extensionstate); dialog->stateid = -1; } + /*remove watcher from rlist */ + if (dialog->rlist) { + rlist_remove_watcher(dialog); + } /* Remove link from peer to subscription of MWI */ if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) { dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); @@ -4654,6 +4671,25 @@ destroy_mailbox(mailbox); } +/*! Destroy rlist subscriptions */ +static void destroy_rlist_resources(struct sip_rlist *rlist) { + struct sip_rlist_resource *resource = NULL; + /*since resources are being removed, make sure to del exten state callback. */ + rlist_stop_monitor(rlist); + while ((resource = AST_LIST_REMOVE_HEAD(&rlist->resources, entry))) { + ast_string_field_free_memory(resource); + } +} + +static void sip_destroy_rlist(void *obj) +{ + struct sip_rlist *rlist = obj; + //todohere do we need to handle the watchers here at all? + ast_debug(3, "Destroying rlist %s\n", rlist->name); + destroy_rlist_resources(rlist); + ast_string_field_free_memory(rlist); +} + static void sip_destroy_peer_fn(void *peer) { sip_destroy_peer(peer); @@ -5044,6 +5080,57 @@ return peer; } +/*static int print_rlists(void) // todohere remove +{ + struct sip_rlist *rlist; + struct sip_rlist_resource *resource; + struct ao2_iterator it; + + it = ao2_iterator_init(rlists, 0); + while ((rlist = ao2_iterator_next(&it))) { + ast_log(LOG_NOTICE,"RESOURCE LIST: %s CONTEXT: %s the_MARK:%d\n", rlist->name, rlist->context, rlist->the_mark); + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + ast_log(LOG_NOTICE, "\tMONITOR EXTEN: %s\n", resource->exten); + } + ao2_t_ref(rlist, -1, "print_rlist"); + } + return 0; +}*/ + +/*! + * \brief Locate resource list by name and context + * + * If sip_pvt *p is !NULL, then p->rlist is updated to point + * to the matching resource list if found. + */ +static int rlist_find(const char *name, const char *context, struct sip_pvt *p) +{ + struct sip_rlist tmp_rlist; + struct sip_rlist *rlist = NULL; + int found = 0; + ast_copy_string(tmp_rlist.name, name, sizeof(tmp_rlist.name)); + + rlist = ao2_t_find(rlists, &tmp_rlist, OBJ_POINTER, "ao2_find in rlist_find()"); + + if (rlist) { + if (!strcmp(rlist->context, context)) { + found = 1; + if (p) { + if (p->rlist) { /* if already associated with rlist, removebefore adding */ + rlist_remove_watcher(p); + } + p->rlist = rlist; + } else { + ao2_t_ref(rlist, -1, "unref rlist in rlist_find"); + } + } + } + + ast_debug(1, "RLIST_FIND result:%d\n", found); //todohere remove +// print_rlists(); //todohere remove + return found; +} + /* Function to assist finding peers by name only */ static int find_by_name(void *obj, void *arg, void *data, int flags) { @@ -5915,6 +6002,11 @@ p->directmediaha = NULL; } + if (p->rlist) { + ao2_t_ref(p->rlist, -1, "peer is destroyed, decrement ref of rlist."); + p->rlist = NULL; + } + ast_string_field_free_memory(p); ast_cc_config_params_destroy(p->cc_params); @@ -12942,6 +13034,293 @@ return send_request(p, &req, XMIT_RELIABLE, p->ocseq); } +/*! \brief Builds XML portion of NOTIFY messages for RLMI and dialog updates */ +static void state_notify_build_rlist_xml(int state, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto, int fullstate) +{ + enum state { NOTIFY_OPEN, NOTIFY_INUSE, NOTIFY_CLOSED } local_state = NOTIFY_OPEN; + const char *statestring = "terminated"; + const char *pidfstate = "--"; + const char *pidfnote= "Ready"; + char hint[AST_MAX_EXTENSION]; + struct sip_rlist_resource *resource; + + switch (state) { + case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): + statestring = (sip_cfg.notifyringing) ? "early" : "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 = "terminated"; + local_state = NOTIFY_CLOSED; + pidfstate = "away"; + pidfnote = "Unavailable"; + break; + case AST_EXTENSION_ONHOLD: + statestring = "confirmed"; + local_state = NOTIFY_CLOSED; + pidfstate = "busy"; + pidfnote = "On hold"; + break; + case AST_EXTENSION_NOT_INUSE: + default: + /* Default setting */ + break; + } + + /* Check which device/devices we are watching and if they are registered */ + if (subscribed != DIALOG_RLMI_XML) { + if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { + char *hint2 = hint, *individual_hint = NULL; + int hint_count = 0, unavailable_count = 0; + + while ((individual_hint = strsep(&hint2, "&"))) { + hint_count++; + + if (ast_device_state(individual_hint) == AST_DEVICE_UNAVAILABLE) + unavailable_count++; + } + + /* If none of the hinted devices are registered, we will + * override notification and show no availability. + */ + if (hint_count > 0 && hint_count == unavailable_count) { + local_state = NOTIFY_CLOSED; + pidfstate = "away"; + pidfnote = "Not online"; + } + } + } + + switch (subscribed) { + case DIALOG_RLMI_XML: + //todohere finish progress made in creating rlmi NOTIFY + //todohere add name attributes for list and elements within list + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\r\n", mto, p->dialogver, fullstate ? "true" : "false"); //is mto correct? + //populate list only for subscribe/resubscribe otherwise save the planet will may have to compare extens if this wouldn't work + AST_LIST_TRAVERSE(&p->rlist->resources, resource, entry) { + //notify only single ext + if ( fullstate || !strcmp(exten, resource->exten)){ + ast_str_append(tmp, 0, "", resource->exten, p->fromdomain); + ast_str_append(tmp, 0, "%s", resource->exten); //todohere name? username? add to conf? + ast_str_append(tmp, 0, "", ast_random(), resource->contentid, p->fromdomain); //todohere random has to go away + ast_str_append(tmp, 0, "\r\n"); + } + } + + ast_str_append(tmp, 0, "\r\n"); + ast_debug(1, "IN NOTIFY RLMI, mfrom:%s, mto:%s p->domain:%s \n", mfrom, mto, p->fromdomain); + break; + case DIALOG_INFO_XML: /* SNOM & LG-Nortel subscribes in this format */ + ast_str_append(tmp, 0, ""); + ast_str_append(tmp, 0, "", p->dialogver, full ? "full" : "partial", mto); + if ((state & AST_EXTENSION_RINGING) && sip_cfg.notifyringing) { + const char *local_display = exten; + char *local_target = ast_strdupa(mto); + + /* There are some limitations to how this works. The primary one is that the + callee must be dialing the same extension that is being monitored. Simply dialing + the hint'd device is not sufficient. */ + if (sip_cfg.notifycid) { + struct ast_channel *caller; + + if ((caller = ast_channel_callback(find_calling_channel, NULL, p, 0))) { + char *cid_num; + int need; + + ast_channel_lock(caller); + cid_num = S_COR(caller->caller.id.number.valid, + caller->caller.id.number.str, ""); + need = strlen(cid_num) + strlen(p->fromdomain) + sizeof("sip:@"); + local_target = alloca(need); + snprintf(local_target, need, "sip:%s@%s", cid_num, p->fromdomain); + local_display = ast_strdupa(S_COR(caller->caller.id.name.valid, + caller->caller.id.name.str, "")); + ast_channel_unlock(caller); + caller = ast_channel_unref(caller); + } + + /* We create a fake call-id which the phone will send back in an INVITE + Replaces header which we can grab and do some magic with. */ + if (sip_cfg.pedanticsipchecking) { + ast_str_append(tmp, 0, "\n", + exten, p->callid, p->theirtag, p->tag); + } else { + ast_str_append(tmp, 0, "\n", + exten, p->callid); + } + ast_str_append(tmp, 0, + "\n" + /* See the limitations of this above. Luckily the phone seems to still be + happy when these values are not correct. */ + "%s\n" + "\n" + "\n" + "\n" + "%s\n" + "\n" + "\n", + local_display, local_target, local_target, mto, mto); + } else { + ast_str_append(tmp, 0, "\n", exten); + } + + } else { + ast_str_append(tmp, 0, "", exten); + } + ast_str_append(tmp, 0, "%s\n", statestring); + if (state == AST_EXTENSION_ONHOLD) { + ast_str_append(tmp, 0, "\n\n" + "\n" + "\n\n", mto); + } + ast_str_append(tmp, 0, "\n\n"); + break; + case NONE: + default: + break; + } +} + +/*! \brief Used in the SUBSCRIBE notification subsystem RLMI (RFC4662) */ +static int transmit_state_notify_rlist(struct sip_pvt *p, int state, int full, const char *exten) +{ + struct ast_str *tmp = ast_str_alloca(20000); //must be sufficient for rlmi + struct ast_str *tmp2 = ast_str_alloca(15000); //must be sufficient for rlmi dialogs + struct sip_rlist *rlist = NULL; + struct sip_rlist_resource *resource = NULL; + //exten param is the key "" is full state and " " is expire-resubscribe + int fullstate = !strcmp(exten, ""); + //struct sip_rlist_resource *resource2 = NULL; + char *boundary = "NYFON-50UBfW7LSCVLtggUPe5z"; //todohere better boundary + char from[256], to[256]; + char *c, *mfrom, *mto; + struct sip_request req; + const struct cfsubscription_types *subscriptiontype; + + memset(from, 0, sizeof(from)); + memset(to, 0, sizeof(to)); + + subscriptiontype = find_subscription_type(p->subscribed); + + ast_copy_string(from, get_header(&p->initreq, "From"), sizeof(from)); + c = get_in_brackets(from); + if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); + return -1; + } + + mfrom = remove_uri_parameters(c); + + ast_copy_string(to, get_header(&p->initreq, "To"), sizeof(to)); + c = get_in_brackets(to); + if (strncasecmp(c, "sip:", 4) && strncasecmp(c, "sips:", 5)) { + ast_log(LOG_WARNING, "Huh? Not a SIP header (%s)?\n", c); + return -1; + } + mto = remove_uri_parameters(c); + + reqprep(&req, p, SIP_NOTIFY, 0, 1); + + if (p->expiry && strcmp(exten, " ")) { + add_header(&req, "Subscription-State", "active");//;expires=120"); + } else { /* Expired */ + add_header(&req, "Subscription-State", "terminated;reason=timeout"); + //should we end here? + //add_content(&req, tmp->str); + //p->pendinginvite = p->ocseq; /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */ + //return send_request(p, &req, XMIT_RELIABLE, p->ocseq); + } + + rlist = p->rlist; + if(rlist) { + struct ast_str *new_mto = ast_str_alloca(256); + /* force short expiration for testing */ + //add_header(&req, "Expires", "120"); + add_header(&req, "Require", "eventlist"); + /* since multipart/related is involved, boundary and start must be added to Content-Type */ + ast_str_append(&tmp2, 0, "multipart/related;start=<%s@%s>;boundary=%s;type=%s", rlist->contentid, p->fromdomain, boundary, subscriptiontype->mediatype); + add_header(&req, "Content-Type", ast_str_buffer(tmp2)); + + /* building the RLMI portion here, not calling state_notify_build_rlist_xml*/ + ast_str_reset(tmp2); + //todohere add name attributes for list and elements within list + ast_str_append(&tmp2, 0, "\n"); + ast_str_append(&tmp2, 0, "\r\n", mto, p->dialogver, fullstate ? "true" : "false"); //is mto correct? + //populate list only for subscribe/resubscribe otherwise save the planet will may have to compare extens if this wouldn't work + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + //build rlist portion only for changed ext if this is not a initial subscription + if ( fullstate || !strcmp(exten, resource->exten)){ + ast_str_append(&tmp2, 0, "", resource->exten, p->fromdomain); + ast_str_append(&tmp2, 0, "%s", resource->exten); //todohere name? username? add to conf? stateid for terminated subs? + ast_str_append(&tmp2, 0, "", ast_random(), resource->contentid, p->fromdomain); //todohere random has to go away + ast_str_append(&tmp2, 0, "\r\n"); + ast_debug(1, "IN NOTIFY RLMI - build resources list portion, exten:%s, resource->exten:%s, p->exten:%s, mfrom:%s, mto:%s p->domain:%s \n", exten, resource->exten, p->exten, mfrom, mto, p->fromdomain); + } + } + ast_str_append(&tmp2, 0, "\r\n"); + /* most of the Content of this NOTIFY is created in state_notify_build, + * but when multipart/related is involved, some over head must be done here */ + ast_str_append(&tmp, 0, "--%s\r\n", boundary); + ast_str_append(&tmp, 0, "Content-Type: %s\r\n", subscriptiontype->mediatype); //was %s; + ast_str_append(&tmp, 0, "Content-Length:%d\r\n", (int)tmp2->used); + ast_str_append(&tmp, 0, "Content-ID: <%s@%s>\r\n\r\n", rlist->contentid, p->fromdomain); + ast_str_append(&tmp, 0, "%s", ast_str_buffer(tmp2)); + + /* building the other part of the multipart/related RLMI */ + subscriptiontype = find_subscription_type(p->multipart_subscribed); + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + //todohere, if not full, then only transmit resources marked for update + if ( fullstate || !strcmp(exten, resource->exten)){ + //AST_LIST_TRAVERSE(resource->resources, resource2, entry2) { + ast_str_reset(tmp2); + ast_str_reset(new_mto); + ast_str_append(&new_mto, 0, "%s@%s", resource->exten, p->fromdomain); + state_notify_build_rlist_xml(resource->laststate, full, resource->exten, rlist->context, &tmp2, p, p->multipart_subscribed, mfrom, ast_str_buffer(new_mto),fullstate); + ast_str_append(&tmp, 0, "--%s\r\n", boundary); + ast_str_append(&tmp, 0, "Content-Type: %s\r\n", subscriptiontype->mediatype); + ast_str_append(&tmp, 0, "Content-Length:%d\r\n", (int)tmp2->used); + ast_str_append(&tmp, 0, "Content-ID: <%s@%s>\r\n\r\n", resource->contentid, p->fromdomain); + ast_str_append(&tmp, 0, "%s", ast_str_buffer(tmp2)); + } + } + ast_str_append(&tmp, 0, "\r\n--%s--\r\n", boundary); + + /* Event header matches the second part of the multipart/related */ + add_header(&req, "Event", subscriptiontype->event); + } else { + ast_log(LOG_WARNING, "transmit_state_notify() NOTIFY rlmi, but no resource list\n"); + return -1; + } + p->dialogver++; + + add_content(&req, tmp->str); + + p->pendinginvite = p->ocseq; /* Remember that we have a pending NOTIFY in order not to confuse the NOTIFY subsystem */ + + return send_request(p, &req, XMIT_RELIABLE, p->ocseq); +} + /*! \brief Notify user of messages waiting in voicemail (RFC3842) \note - Notification only works for registered peers with mailbox= definitions in sip.conf @@ -14682,6 +15061,68 @@ dialog_unref(p, "the extensionstate containing this dialog ptr was destroyed"); } +static int cb_rlist_extensionstate(char *context, char *exten, int state, void *data) +{ + struct sip_rlist *rlist = data; + struct sip_rlist_resource *resource; + struct sip_pvt *pvt; + int found = 0; + + + ast_debug(1, "cb_rlist_extensionstate CALLED BACK!!! exten: %s, state:%d, rlist_name:%s", exten, state, rlist->name); //todohere remove + /* check to see if there are any watchers for this list, if not stop monitoring */ + if (!rlist->num_watchers) { + rlist_stop_monitor(rlist); + return 0; + } + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + if (!strcmp(resource->exten, exten)) { + found = 1; + break; + } + } + + if (!found) { + return -1; + } + // rlist locking - may need to work on this + ao2_lock(rlist); + + switch(state) { + case AST_EXTENSION_DEACTIVATED: /* Retry after a while */ + case AST_EXTENSION_REMOVED: /* Extension is gone */ + //todohere how to handle this ? + resource->stateid = -1; + //for now we just set this to idle just in case of network error/etc + resource->laststate = 0; + break; + default: /* Tell user */ + resource->laststate = state; + break; + } + + AST_LIST_TRAVERSE(&rlist->watchers, pvt, rlist_entry) { + ast_debug(1, "SENDING NOTIFY FOR pvt\n"); //todohere remove + //don't notify itself - just waste. safe to do since not called with full state + //if (strcmp(pvt->exten, exten)) + //sip_pvt_lock(pvt); + //if (!pvt->pendinginvite){ + transmit_state_notify_rlist(pvt, state, 1, exten); + //} else { + /* We already have a NOTIFY sent that is not answered. Queue the state up. + if many state changes happen meanwhile, we will only send a notification of the last one */ + // ast_set_flag(&pvt->flags[1], SIP_PAGE2_STATECHANGEQUEUE); + //} + //ast_verb(2, "Extension Changed %s[%s] new state %s for Rlist Notify User %s %s\n", exten, context, ast_extension_state2str(state), pvt->username, + // ast_test_flag(&pvt->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); + //sip_pvt_unlock(pvt); + } + //locking test + ao2_unlock(rlist); + + return 0; +} + /*! \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 */ @@ -15542,6 +15983,15 @@ if (req->method == SIP_SUBSCRIBE) { char hint[AST_MAX_EXTENSION]; int which = 0; + + /* todohere: For now just for rlists */ + int res; + res = ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, p->exten) ? 0 : -1; + if (res) { /* if res is not 0, then hint not found, check rlists for destination. */ + res = rlist_find(p->exten, p->context, NULL) ? 0 : -1; + } + return res; + if (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, uri) || (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, p->context, decoded_uri) && (which = 1))) { if (!oreq) { @@ -16468,7 +16918,71 @@ sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); return; } +/*! \brief CLI Command to show Rlists */ +static char *sip_show_rlists(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ +#define FORMAT "%-25.25s %-15.15s %-15.15s \n" +#define FORMAT2 "%-25.25s %-15.15s %-15.15s \n" + char themark[40]; + //char iused[40]; + int showdetails = 0; + struct sip_pvt *pvt; + struct sip_rlist *rlist; + struct sip_rlist_resource *resource; + struct ao2_iterator it; + + switch (cmd) { + case CLI_INIT: + e->command = "sip show rlists"; + e->usage = + "Usage: sip show rlists [watchers|resources]\n" + " List all Rlists and watchers.\n" + " Add option \"watchers\" to show watchers or \"resources\", for RList to show details.\n"; + return NULL; + case CLI_GENERATE: + return NULL; + } + + if (a->argc < 3) + return CLI_SHOWUSAGE; + + if (a->argc == 4 && !strcmp(a->argv[3], "watchers")){ + showdetails = 1; + ast_cli(a->fd, FORMAT, "* Rlist name", "Context", "Watcher"); + } else if (a->argc == 4 && !strcmp(a->argv[3], "resources")){ + showdetails = 2; + ast_cli(a->fd, FORMAT, "* Rlist name", "Context", "Resource"); + }else{ + ast_cli(a->fd, FORMAT, "* Rlist name", "Context", "The mark"); + } + + it = ao2_iterator_init(rlists, 0); + while ((rlist = ao2_iterator_next(&it))) { + switch (showdetails) { + case 1: //watchers + AST_LIST_TRAVERSE(&rlist->watchers, pvt, rlist_entry) { + ast_cli(a->fd, FORMAT2, rlist->name, rlist->context, pvt->exten); + } + break; + case 2: //resources (extens) + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + ast_cli(a->fd, FORMAT2, rlist->name, rlist->context, resource->exten); + } + break; + default: + snprintf(themark, sizeof(themark), "%d", rlist->the_mark); + ast_cli(a->fd, FORMAT2, rlist->name, rlist->context, themark); + } + ao2_t_ref(rlist, -1, "print_rlist"); + } + ao2_iterator_destroy(&it); + + return CLI_SUCCESS; +#undef FORMAT +#undef FORMAT2 +} + /*! \brief CLI Command to show calls within limits set by call_limit */ static char *sip_show_inuse(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { @@ -17224,6 +17738,14 @@ return CMP_MATCH; } +/* this func is used with ao2_callback to unlink/delete all marked + rlists */ +static int rlist_is_marked(void *rlistobj, void *arg, int flags) +{ + struct sip_rlist *rlist = rlistobj; + return rlist->the_mark ? CMP_MATCH : 0; +} + /*! \brief Remove temporary realtime objects from memory (CLI) */ /*! \todo XXXX Propably needs an overhaul after removal of the devices */ static char *sip_prune_realtime(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) @@ -24883,77 +25405,99 @@ int start = 0; enum subscriptiontype subscribed = NONE; const char *unknown_acceptheader = NULL; - - /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */ + accept = __get_header(req, "Accept", &start); - while ((subscribed == NONE) && !ast_strlen_zero(accept)) { - pidf_xml = strstr(accept, "application/pidf+xml") ? 1 : 0; + + /*todohere temp for rlist + lip-68xx* && strstr(p->useragent, "LIP") */ + if (strstr(accept, "application/rlmi+xml") && strstr(accept, "multipart/related")){// && strstr(p->useragent, "LIP")) { + int i; + p->subscribed = DIALOG_RLMI_XML; + subscribed = DIALOG_RLMI_XML; + for (i = 1; i < ARRAY_LEN(subscription_types); i++) { + if (strstr(accept, subscription_types[i].mediatype) && subscription_types[i].type != p->subscribed) { + p->multipart_subscribed = subscription_types[i].type; + } + } + /* second part of multipart/related not present. send back bad event */ + if (!p->multipart_subscribed) { + transmit_response(p, "489 Bad Event", req); + ast_log(LOG_WARNING, "SUBSCRIBE failure: Multipart RLMI malformed Accept header: pvt: stateid: %d, laststate: %d, dialogver: %u, subscribecont: '%s', subscribeuri: '%s'\n", + p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri); + pvt_set_needdestroy(p, "Multipart RLMI malformed Accept header"); + return 0; + } + + } else { + /* Header from Xten Eye-beam Accept: multipart/related, application/rlmi+xml, application/pidf+xml, application/xpidf+xml */ + while ((subscribed == NONE) && !ast_strlen_zero(accept)) { + pidf_xml = strstr(accept, "application/pidf+xml") ? 1 : 0; - /* Older versions of Polycom firmware will claim pidf+xml, but really - * they only support xpidf+xml. */ - if (pidf_xml && strstr(p->useragent, "Polycom")) { - subscribed = XPIDF_XML; - } else if (pidf_xml) { - subscribed = PIDF_XML; /* RFC 3863 format */ - } else if (strstr(accept, "application/dialog-info+xml")) { - subscribed = DIALOG_INFO_XML; - /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ - } else if (strstr(accept, "application/cpim-pidf+xml")) { - subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ - } else if (strstr(accept, "application/xpidf+xml")) { - subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ - } else { - unknown_acceptheader = accept; + /* Older versions of Polycom firmware will claim pidf+xml, but really + * they only support xpidf+xml. */ + if (pidf_xml && strstr(p->useragent, "Polycom")) { + subscribed = XPIDF_XML; + } else if (pidf_xml) { + subscribed = PIDF_XML; /* RFC 3863 format */ + } else if (strstr(accept, "application/dialog-info+xml")) { + subscribed = DIALOG_INFO_XML; + /* IETF draft: draft-ietf-sipping-dialog-package-05.txt */ + } else if (strstr(accept, "application/cpim-pidf+xml")) { + subscribed = CPIM_PIDF_XML; /* RFC 3863 format */ + } else if (strstr(accept, "application/xpidf+xml")) { + subscribed = XPIDF_XML; /* Early pre-RFC 3863 format with MSN additions (Microsoft Messenger) */ + } else { + unknown_acceptheader = accept; + } + /* check to see if there is another Accept header present */ + accept = __get_header(req, "Accept", &start); } - /* check to see if there is another Accept header present */ - accept = __get_header(req, "Accept", &start); - } - if (!start) { - if (p->subscribed == NONE) { /* if the subscribed field is not already set, and there is no accept header... */ - transmit_response(p, "489 Bad Event", req); - ast_log(LOG_WARNING,"SUBSCRIBE failure: no Accept header: pvt: " - "stateid: %d, laststate: %d, dialogver: %u, subscribecont: " - "'%s', subscribeuri: '%s'\n", + if (!start) { + if (p->subscribed == NONE) { /* if the subscribed field is not already set, and there is no accept header... */ + transmit_response(p, "489 Bad Event", req); + ast_log(LOG_WARNING,"SUBSCRIBE failure: no Accept header: pvt: " + "stateid: %d, laststate: %d, dialogver: %u, subscribecont: " + "'%s', subscribeuri: '%s'\n", + p->stateid, + p->laststate, + p->dialogver, + p->subscribecontext, + p->subscribeuri); + pvt_set_needdestroy(p, "no Accept header"); + if (authpeer) { + unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 2)"); + } + return 0; + } + /* if p->subscribed is non-zero, then accept is not obligatory; according to rfc 3265 section 3.1.3, at least. + so, we'll just let it ride, keeping the value from a previous subscription, and not abort the subscription */ + } else if (subscribed == NONE) { + /* Can't find a format for events that we know about */ + char mybuf[200]; + if (!ast_strlen_zero(unknown_acceptheader)) { + snprintf(mybuf, sizeof(mybuf), "489 Bad Event (format %s)", unknown_acceptheader); + } else { + snprintf(mybuf, sizeof(mybuf), "489 Bad Event"); + } + transmit_response(p, mybuf, req); + ast_log(LOG_WARNING,"SUBSCRIBE failure: unrecognized format:" + "'%s' pvt: subscribed: %d, stateid: %d, laststate: %d," + "dialogver: %u, subscribecont: '%s', subscribeuri: '%s'\n", + unknown_acceptheader, + (int)p->subscribed, p->stateid, p->laststate, p->dialogver, p->subscribecontext, p->subscribeuri); - pvt_set_needdestroy(p, "no Accept header"); + pvt_set_needdestroy(p, "unrecognized format"); if (authpeer) { unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 2)"); } return 0; - } - /* if p->subscribed is non-zero, then accept is not obligatory; according to rfc 3265 section 3.1.3, at least. - so, we'll just let it ride, keeping the value from a previous subscription, and not abort the subscription */ - } else if (subscribed == NONE) { - /* Can't find a format for events that we know about */ - char mybuf[200]; - if (!ast_strlen_zero(unknown_acceptheader)) { - snprintf(mybuf, sizeof(mybuf), "489 Bad Event (format %s)", unknown_acceptheader); } else { - snprintf(mybuf, sizeof(mybuf), "489 Bad Event"); + p->subscribed = subscribed; } - transmit_response(p, mybuf, req); - ast_log(LOG_WARNING,"SUBSCRIBE failure: unrecognized format:" - "'%s' pvt: subscribed: %d, stateid: %d, laststate: %d," - "dialogver: %u, subscribecont: '%s', subscribeuri: '%s'\n", - unknown_acceptheader, - (int)p->subscribed, - p->stateid, - p->laststate, - p->dialogver, - p->subscribecontext, - p->subscribeuri); - pvt_set_needdestroy(p, "unrecognized format"); - if (authpeer) { - unref_peer(authpeer, "unref_peer, from handle_request_subscribe (authpeer 2)"); - } - return 0; - } else { - p->subscribed = subscribed; } } else if (!strncmp(eventheader, "message-summary", MAX(event_len, 15))) { int start = 0; @@ -25031,7 +25575,7 @@ } /* Add subscription for extension state from the PBX core */ - if (p->subscribed != MWI_NOTIFICATION && p->subscribed != CALL_COMPLETION && !resubscribe) { + if (p->subscribed != MWI_NOTIFICATION && p->subscribed != DIALOG_RLMI_XML && p->subscribed != CALL_COMPLETION && !resubscribe) { if (p->stateid != -1) { ast_extension_state_del(p->stateid, cb_extensionstate); } @@ -25092,6 +25636,19 @@ ao2_lock(p); unref_peer(peer, "release a peer ref now that MWI is sent"); } + } else if (p->subscribed == DIALOG_RLMI_XML) { /* resource lists extension states are handled differently */ + if (rlist_add_watcher(p) > 0) { + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + p->dialogver = 0; + ast_string_field_build(p, subscribeuri, "%s@%s", p->exten, p->context); + transmit_response(p, "200 OK", req); + transmit_state_notify_rlist(p, 0, 1, ""); + //todohere force first notify and delete any matching old subscriptions (they will time out, but why wait + } else { + transmit_response(p, "404 Not found", req); + pvt_set_needdestroy(p, "no extension for SUBSCRIBE"); + return 0; + } } else if (p->subscribed != CALL_COMPLETION) { if ((firststate = ast_extension_state(NULL, p->context, p->exten)) < 0) { @@ -27433,6 +27990,151 @@ } } +static int rlist_start_monitor(struct sip_rlist *rlist) +{ + struct sip_rlist_resource *resource; + + if (!rlist->num_watchers) { + if (rlist->startmonitor) { + rlist_stop_monitor(rlist); /* no watchers, no need to monitor */ + } + return 0; + } else if (rlist->startmonitor) { /* monitor already started */ + return 0; + } + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + if (resource->stateid > -1) { + ast_extension_state_del(resource->stateid, cb_rlist_extensionstate); + resource->stateid = -1; + } + resource->stateid = ast_extension_state_add(rlist->context, resource->exten, cb_rlist_extensionstate, rlist); + if (resource->stateid > -1) { + ao2_t_ref(rlist, 1, "monitoring new extension"); + } + } + rlist->startmonitor = 1; + return 1; +} + +static int rlist_stop_monitor(struct sip_rlist *rlist) +{ + struct sip_rlist_resource *resource; + + AST_LIST_TRAVERSE(&rlist->resources, resource, entry) { + if (resource->stateid > -1) { + ast_extension_state_del(resource->stateid, cb_rlist_extensionstate); + resource->stateid = -1; + ao2_t_ref(rlist, -1, "stopped monitoring extension"); + } + } + rlist->startmonitor = 0; + return 1; +} + +static int rlist_add_watcher(struct sip_pvt *p) +{ + int res = 0; + if (p) { + /* find and point p->rlist to match, rlist ref is incremented if found */ + rlist_find(p->exten, p->context, p); + if (p->rlist) { + dialog_ref(p, "add sip_pvt watcher to resource list notifications"); + AST_LIST_INSERT_TAIL(&p->rlist->watchers, p, rlist_entry); + p->rlist->num_watchers++; + rlist_start_monitor(p->rlist); + return 1; + } else { + res = -1; + } + } else { + res = -1; + } + + return res; +} + +static int rlist_remove_watcher(struct sip_pvt *p) +{ + if (!p) { + return -1; + } + if (p->rlist) { + AST_LIST_REMOVE(&p->rlist->watchers, p, rlist_entry); + p->rlist->num_watchers--; + if (!p->rlist->num_watchers) { /* num_watchers is decremented to zero, stop monitoring extensions. */ + rlist_stop_monitor(p->rlist); + } + ao2_t_ref(p->rlist, -1, "remove dialog's ref to resource list"); + p->rlist = NULL; + dialog_unref(p, "remove sip_pvt from resource list notifications"); + return 0; + } + return -1; +} + +static int rlist_add_resource(struct sip_rlist *rlist, const char *monitorexten) +{ + struct sip_rlist_resource *resource; + + if (!(resource = ast_calloc(1, sizeof(*resource)))) { + return -1; + } + ast_string_field_init(resource, 128); + ast_string_field_set(resource, exten, monitorexten); + ast_string_field_build(resource, contentid, "%08lx", ast_random()); + resource->stateid = -1; + AST_LIST_INSERT_TAIL(&rlist->resources, resource, entry); + + return 0; +} + +static struct sip_rlist *build_rlist(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime) +{ + struct sip_rlist *rlist = NULL; + + int found = 0; + struct sip_rlist tmp_rlist; + + ast_copy_string(tmp_rlist.name, name, sizeof(tmp_rlist.name)); + + rlist = ao2_t_find(rlists, &tmp_rlist, OBJ_POINTER, "ao2_find in rlists table"); + + /* does rlist already exist, if so mark as found. else create new */ + if (rlist) { + destroy_rlist_resources(rlist); /* rlist found, remove all monitor resources and rebuild */ + found++; + } else { + if (!(rlist = ao2_t_alloc(sizeof(*rlist), sip_destroy_rlist, "allocate a rlist struct"))) { + return NULL; + } + + if (ast_string_field_init(rlist, 256)) { + ao2_t_ref(rlist, -1, "rlist failed to init string field"); + return NULL; + } + rlist->startmonitor = 0; + ast_string_field_build(rlist, contentid, "%08lx", ast_random()); + ast_copy_string(rlist->name, name, sizeof(rlist->name)); + } + + for (; v || ((v = alt) && !(alt = NULL)); v = v->next) { + if (!strcasecmp(v->name, "monitor")) { + rlist_add_resource(rlist, v->value); + } else if (!strcasecmp(v->name, "context")) { + ast_string_field_set(rlist, context, v->value); + } + } + rlist->the_mark = 0; /* unmark this resource list for deletion */ + + if (found) { + /* monitor was stopped when resources were destroyed. now that they are added back, restart monitor. this only takes affect if rlist has watchers. */ + rlist_start_monitor(rlist); + ao2_t_ref(rlist, -1, "rlist found and updated, decrement ref and return NULL"); + return NULL; + } + return rlist; +} + /*! \brief Build peer from configuration (file or realtime static/dynamic) */ static struct sip_peer *build_peer(const char *name, struct ast_variable *v, struct ast_variable *alt, int realtime, int devstate_only) { @@ -28139,6 +28841,13 @@ } while(0)); } +static int rlist_markall_func(void *list, void *arg, int flags) +{ + struct sip_rlist *rlist = list; + rlist->the_mark = 1; + return 0; +} + /*! \brief Re-read SIP.conf config file \note This function reloads all config data, except for active peers (with registrations). They will only @@ -28150,6 +28859,7 @@ struct ast_config *cfg, *ucfg; struct ast_variable *v; struct sip_peer *peer; + struct sip_rlist *rlist; char *cat, *stringp, *context, *oldregcontext; char newcontexts[AST_MAX_CONTEXT], oldcontexts[AST_MAX_CONTEXT]; struct ast_flags dummy[2]; @@ -28224,6 +28934,7 @@ ASTOBJ_CONTAINER_DESTROYALL(®l, sip_registry_destroy); ast_debug(4, "--------------- Done destroying registry list\n"); ao2_t_callback(peers, OBJ_NODATA, peer_markall_func, NULL, "callback to mark all peers"); + ao2_t_callback(rlists, OBJ_NODATA, rlist_markall_func, NULL, "callback to mark all rlists"); } /* Reset certificate handling for TLS sessions */ @@ -29112,6 +29823,13 @@ ; } else if (!strcasecmp(utype, "peer")) { ; + } else if (!strcasecmp(utype, "resourcelist")) { + rlist = build_rlist(cat, ast_variable_browse(cfg, cat), NULL, 0); + if (rlist) { + ao2_t_link(rlists, rlist, "link rlist into rlists table"); + ao2_t_ref(rlist, -1, "unref the result of the build_rlist call. Now, the links from the tables are the only ones left,"); + } + continue; } else { ast_log(LOG_WARNING, "Unknown type '%s' for '%s' in %s\n", utype, cat, "sip.conf"); continue; @@ -29932,7 +30650,8 @@ start_poke = time(0); /* Prune peers who still are supposed to be deleted */ unlink_marked_peers_from_tables(); - + /* rlist */ + ao2_t_callback(rlists, OBJ_NODATA | OBJ_UNLINK | OBJ_MULTIPLE, rlist_is_marked, NULL, "callback to remove all marked rlists"); ast_debug(4, "--------------- Done destroying pruned peers\n"); /* Send qualify (OPTIONS) to all peers */ @@ -29942,7 +30661,8 @@ sip_send_all_registers(); sip_send_all_mwi_subscriptions(); - + //todo restart rlists monitor + //sip_send_all_rlists(); end_poke = time(0); ast_debug(4, "do_reload finished. peer poke/prune reg contact time = %d sec.\n", (int)(end_poke-start_poke)); @@ -30033,6 +30753,26 @@ } /*! \brief + * \note The only member of the rlist used here is the name field +*/ +static int rlist_hash_cb(const void *obj, const int flags) +{ + const struct sip_rlist *rlist = obj; + + return ast_str_case_hash(rlist->name); +} + +/*! +* \note The only member of the rlist used here is the name field +*/ +static int rlist_cmp_cb(void *obj, void *arg, int flags) +{ + struct sip_rlist *rlist = obj, *rlist2 = arg; + + return !strcasecmp(rlist->name, rlist2->name) ? CMP_MATCH | CMP_STOP : 0; +} + +/*! \brief * \note The only member of the peer used here is the name field */ static int peer_hash_cb(const void *obj, const int flags) @@ -30187,7 +30927,8 @@ AST_CLI_DEFINE(sip_do_debug, "Enable/Disable SIP debugging"), AST_CLI_DEFINE(sip_set_history, "Enable/Disable SIP history"), AST_CLI_DEFINE(sip_reload, "Reload SIP configuration"), - AST_CLI_DEFINE(sip_show_tcp, "List TCP Connections") + AST_CLI_DEFINE(sip_show_tcp, "List TCP Connections"), + AST_CLI_DEFINE(sip_show_rlists, "List Rlists and watchers") }; /*! \brief SIP test registration */ @@ -30600,12 +31341,15 @@ /* the fact that ao2_containers can't resize automatically is a major worry! */ /* if the number of objects gets above MAX_XXX_BUCKETS, things will slow down */ + /* rlists uses the same size as hash_peer_size, this may need to change */ + + rlists = ao2_t_container_alloc(HASH_PEER_SIZE, rlist_hash_cb, rlist_cmp_cb, "allocate rlists"); peers = ao2_t_container_alloc(HASH_PEER_SIZE, peer_hash_cb, peer_cmp_cb, "allocate peers"); peers_by_ip = ao2_t_container_alloc(HASH_PEER_SIZE, peer_iphash_cb, peer_ipcmp_cb, "allocate peers_by_ip"); dialogs = ao2_t_container_alloc(HASH_DIALOG_SIZE, dialog_hash_cb, dialog_cmp_cb, "allocate dialogs"); dialogs_to_destroy = ao2_t_container_alloc(1, NULL, NULL, "allocate dialogs_to_destroy"); threadt = ao2_t_container_alloc(HASH_DIALOG_SIZE, threadt_hash_cb, threadt_cmp_cb, "allocate threadt table"); - if (!peers || !peers_by_ip || !dialogs || !dialogs_to_destroy || !threadt) { + if (!peers || !peers_by_ip || !dialogs || !dialogs_to_destroy || !threadt || !rlists) { ast_log(LOG_ERROR, "Unable to create primary SIP container(s)\n"); return AST_MODULE_LOAD_FAILURE; } @@ -30684,7 +31428,8 @@ sip_send_all_registers(); sip_send_all_mwi_subscriptions(); initialize_escs(); - + //todo send rlists subscriptions + if (sip_epa_register(&cc_epa_static_data)) { return AST_MODULE_LOAD_DECLINE; } @@ -30810,7 +31555,7 @@ ao2_iterator_destroy(&i); unlink_all_peers_from_tables(); - + //todo remover rlists ast_mutex_lock(&monlock); if (monitor_thread && (monitor_thread != AST_PTHREADT_STOP) && (monitor_thread != AST_PTHREADT_NULL)) { pthread_cancel(monitor_thread); @@ -30878,6 +31623,7 @@ ast_debug(2, "TCP/TLS thread container did not become empty :(\n"); } + ao2_t_ref(rlists, -1, "unref the rlists table"); ao2_t_ref(peers, -1, "unref the peers table"); ao2_t_ref(peers_by_ip, -1, "unref the peers_by_ip table"); ao2_t_ref(dialogs, -1, "unref the dialogs table"); @@ -30900,7 +31646,7 @@ sip_reqresp_parser_exit(); sip_unregister_tests(); - + //todo unregister rlists monitor return 0; } Index: channels/sip/include/sip.h =================================================================== --- channels/sip/include/sip.h (revision 372129) +++ channels/sip/include/sip.h (working copy) @@ -427,6 +427,7 @@ PIDF_XML, MWI_NOTIFICATION, CALL_COMPLETION, + DIALOG_RLMI_XML, }; /*! \brief The number of media types in enum \ref media_type below. */ @@ -1076,10 +1077,14 @@ int t38id; /*!< T.38 Response ID */ struct sip_refer *refer; /*!< REFER: SIP transfer data structure */ enum subscriptiontype subscribed; /*!< SUBSCRIBE: Is this dialog a subscription? */ + enum subscriptiontype multipart_subscribed; /*!< SUBSCRIBE: is this dialog a multipart/related subscription? */ int stateid; /*!< SUBSCRIBE: ID for devicestate subscriptions */ int laststate; /*!< SUBSCRIBE: Last known extension state */ uint32_t dialogver; /*!< SUBSCRIBE: Version for subscription dialog-info */ + struct sip_rlist *rlist; /*!< SUBSCRIBE: Resource list to get notifications from */ + AST_LIST_ENTRY(sip_pvt) rlist_entry; /* SUBSCRIBE: entry into resource list's list of watchers */ + struct ast_dsp *dsp; /*!< Inband DTMF or Fax CNG tone Detection dsp */ struct sip_peer *relatedpeer; /*!< If this dialog is related to a peer, which one @@ -1172,6 +1177,32 @@ char mailbox[2]; }; +struct sip_rlist_resource { + int stateid; + int laststate; + + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(exten); /*!< This is an extension to watch within rlist */ + AST_STRING_FIELD(contentid); /*!< Dialog Content ID */ + ); + + AST_LIST_ENTRY(sip_rlist_resource) entry; +}; + +/* todohere add brief */ +struct sip_rlist { + char name[80]; /*!< This is the extension to subscribe too for list */ + AST_DECLARE_STRING_FIELDS ( + AST_STRING_FIELD(context); + AST_STRING_FIELD(contentid); /*!< Dialog Content ID */ + ); + int the_mark; + int num_watchers; + int startmonitor; + AST_LIST_HEAD_NOLOCK(, sip_pvt) watchers; + AST_LIST_HEAD_NOLOCK(, sip_rlist_resource) resources; +}; + /*! \brief Structure for SIP peer data, we place calls to peers if registered or fixed IP address (host) */ /* XXX field 'name' must be first otherwise sip_addrcmp() will fail, as will astobj2 hashing of the structure */ @@ -1757,7 +1788,7 @@ /* RFC3959: SIP Early session support */ { SIP_OPT_EARLY_SESSION, NOT_SUPPORTED, "early-session" }, /* SIMPLE events: RFC4662 */ - { SIP_OPT_EVENTLIST, NOT_SUPPORTED, "eventlist" }, + { SIP_OPT_EVENTLIST, SUPPORTED, "eventlist" }, /* RFC 4916- Connected line ID updates */ { SIP_OPT_FROMCHANGE, NOT_SUPPORTED, "from-change" }, /* GRUU: Globally Routable User Agent URI's */