diff -durN asterisk-16.12.0.orig/channels/chan_sip.c asterisk-16.12.0/channels/chan_sip.c --- asterisk-16.12.0.orig/channels/chan_sip.c 2020-07-28 15:08:00.356838978 +1200 +++ asterisk-16.12.0/channels/chan_sip.c 2020-07-28 15:22:32.868256528 +1200 @@ -363,6 +363,67 @@ application is only available if TEST_FRAMEWORK is defined. + + + Page a series of Cisco USECALLMANAGER phones + + + + + Name of the SIP peer to page + + + Name of the second peer to page, additional peers are + specified as peer&peer2&peer3... + + + + + + + + + + + + + + + + Using the RTP streaming API, send a request to the specified peers to + receive RTP audio. Supported codecs are G711 (mulaw and alaw), G722 and + G729a. RTP is transmitted as unicast unless the m() option is used. + + Gets the specified SIP header from an incoming INVITE message. @@ -505,6 +566,24 @@ Preferred codec index number x (beginning with zero). + + Is DoNotDisturb set on this peer (yes/no). + + + The call forwarding extension for this peer. + + + Is HuntGroup login set on this peer (yes/no). + + + The Call-ID of the REGISTER dialog. + + + The device name of the Cisco USECALLMANAGER peer + + + The line index of the Cisco USECALLMANAGER peer + @@ -720,7 +799,9 @@ { 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 */ + { FEATURE_EVENTS, "as-feature-event", "application/x-as-feature-event+xml", "as-feature-event" }, /* EMCA-323 application server feature events */ + { REMOTECC_XML, "refer", "application/x-cisco-remotecc-request+xml", "remotecc" } /* Cisco remotecc request/respones */ }; /*! \brief The core structure to setup dialogs. We parse incoming messages by using @@ -884,6 +965,7 @@ static struct stasis_subscription *network_change_sub; /*!< subscription id for network change events */ static struct stasis_subscription *acl_change_sub; /*!< subscription id for named ACL system change events */ static int network_change_sched_id = -1; +static struct stasis_subscription *pickup_notify_sub; /*!< subscription id for named ACL system change events */ static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */ @@ -918,6 +1000,68 @@ */ static int sipdebug_text; +/*! \brief Remotecc applications */ +enum { + REMOTECC_CONFLIST = 1, + REMOTECC_CALLBACK = 2 +}; + +/*! \brief Contains the parsed-out xml elements from a remotecc request */ +struct remotecc_dialog { + char *callid; + char *localtag; + char *remotetag; +}; + +struct remotecc_data { + char *softkeyevent; + struct remotecc_dialog dialogid; + struct remotecc_dialog consultdialogid; + struct remotecc_dialog joindialogid; + int applicationid; + int confid; + char *usercalldata; +}; + +/*! \brief Information required to start or join an ad-hoc conference */ +struct conference_data { + struct sip_pvt *pvt; + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; + int joining:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + AST_STRING_FIELD(join_callid); + AST_STRING_FIELD(join_tag); + AST_STRING_FIELD(join_theirtag); + ); +}; + +/*! \brief Informtion required to park a call */ +struct park_data { + struct sip_pvt *pvt; + struct ast_channel *chan; + int monitor:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(context); + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + AST_STRING_FIELD(uniqueid); + ); +}; + +/*! \brief Information required to record a call */ +struct record_data { + int outgoing:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + ); +}; + static const struct _map_x_s referstatusstrings[] = { { REFER_IDLE, "" }, { REFER_SENT, "Request sent" }, @@ -971,11 +1115,17 @@ #ifdef HAVE_LIBXML2 static int cc_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry); +static int presence_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry); static const struct sip_esc_publish_callbacks cc_esc_publish_callbacks = { .initial_handler = cc_esc_publish_handler, .modify_handler = cc_esc_publish_handler, }; + +static const struct sip_esc_publish_callbacks presence_esc_publish_callbacks = { + .initial_handler = presence_esc_publish_handler, + .modify_handler = presence_esc_publish_handler, +}; #endif /*! @@ -998,6 +1148,7 @@ } event_state_compositors [] = { #ifdef HAVE_LIBXML2 {CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks}, + {PRESENCE, "presence", &presence_esc_publish_callbacks}, #endif }; @@ -1063,6 +1214,10 @@ static int temp_pvt_init(void *); static void temp_pvt_cleanup(void *); +/*! \brief The ad-hoc conference list */ +static AST_LIST_HEAD_STATIC(conferences, sip_conference); +static int next_confid = 0; + /*! \brief A per-thread temporary pvt structure */ AST_THREADSTORAGE_CUSTOM(ts_temp_pvt, temp_pvt_init, temp_pvt_cleanup); @@ -1169,6 +1324,7 @@ /*--- PBX interface functions */ static struct ast_channel *sip_request_call(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause); static int sip_devicestate(const char *data); +static int sip_presencestate(const char *data, char **subtype, char **message); static int sip_sendtext(struct ast_channel *ast, const char *text); static int sip_call(struct ast_channel *ast, const char *dest, int timeout); static int sip_sendhtml(struct ast_channel *chan, int subclass, const char *data, int datalen); @@ -1194,7 +1350,8 @@ static int sipsock_read(int *id, int fd, short events, void *ignore); static int __sip_xmit(struct sip_pvt *p, struct ast_str *data); static int __sip_reliable_xmit(struct sip_pvt *p, uint32_t seqno, int resp, struct ast_str *data, int fatal, int sipmethod); -static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp); +static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p); +static void add_security_call_info(struct sip_request *req, struct sip_pvt *p); static int __transmit_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); static int retrans_pkt(const void *data); static int transmit_response_using_temp(ast_string_field callid, struct ast_sockaddr *addr, int useglobal_nat, const int intended_method, const struct sip_request *req, const char *msg); @@ -1204,6 +1361,7 @@ static int transmit_response_with_sdp(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable, int oldsdp, int rpid); static int transmit_response_with_unsupported(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *unsupported); static int transmit_response_with_auth(struct sip_pvt *p, const char *msg, const struct sip_request *req, const char *rand, enum xmittype reliable, const char *header, int stale); +static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req); static int transmit_provisional_response(struct sip_pvt *p, const char *msg, const struct sip_request *req, int with_sdp); static int transmit_response_with_allow(struct sip_pvt *p, const char *msg, const struct sip_request *req, enum xmittype reliable); static void transmit_fake_auth_response(struct sip_pvt *p, struct sip_request *req, enum xmittype reliable); @@ -1217,6 +1375,7 @@ static int transmit_info_with_vidupdate(struct sip_pvt *p); static int transmit_message(struct sip_pvt *p, int init, int auth); static int transmit_refer(struct sip_pvt *p, const char *dest); +static int transmit_refer_with_content(struct sip_pvt *p, const char *type, const char *content); static int transmit_notify_with_mwi(struct sip_pvt *p, int newmsgs, int oldmsgs, const char *vmexten); static int transmit_notify_with_sipfrag(struct sip_pvt *p, int cseq, char *message, int terminate); static int transmit_cc_notify(struct ast_cc_agent *agent, struct sip_pvt *subscription, enum sip_cc_notify_state state); @@ -1226,7 +1385,11 @@ static void copy_request(struct sip_request *dst, const struct sip_request *src); static void receive_message(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, const char *e); static void parse_moved_contact(struct sip_pvt *p, struct sip_request *req, char **name, char **number, int set_call_forward); -static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only); +static int sip_send_mwi(struct sip_peer *peer, int cache_only); +static int sip_send_bulkupdate(struct sip_peer *peer); +static void extensionstate_subscriptions(struct sip_peer *peer); +static void register_peer_aliases(struct sip_peer *peer); +static void expire_peer_aliases(struct sip_peer *peer); /* Misc dialog routines */ static int __sip_autodestruct(const void *data); @@ -1241,6 +1404,7 @@ static int build_path(struct sip_pvt *p, struct sip_peer *peer, struct sip_request *req, const char *pathbuf); static enum check_auth_result register_verify(struct sip_pvt *p, struct ast_sockaddr *addr, struct sip_request *req, const char *uri); +struct sip_pvt *get_sip_pvt(const char *callid, const char *totag, const char *fromtag); static int get_sip_pvt_from_replaces(const char *callid, const char *totag, const char *fromtag, struct sip_pvt **out_pvt, struct ast_channel **out_chan); static void check_pendings(struct sip_pvt *p); @@ -1251,6 +1415,7 @@ static int sip_sipredirect(struct sip_pvt *p, const char *dest); static int is_method_allowed(unsigned int *allowed_methods, enum sipmethod method); +static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing); /*--- Codec handling / SDP */ static void try_suggested_sip_codec(struct sip_pvt *p); @@ -1264,9 +1429,9 @@ static int process_sdp_a_ice(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance, int rtcp_mux); static int process_sdp_a_rtcp_mux(const char *a, struct sip_pvt *p, int *requested); static int process_sdp_a_dtls(const char *a, struct sip_pvt *p, struct ast_rtp_instance *instance); -static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec); -static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec); -static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec); +static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs); +static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs); +static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs); static int process_sdp_a_image(const char *a, struct sip_pvt *p); static void add_ice_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); static void add_dtls_to_sdp(struct ast_rtp_instance *instance, struct ast_str **a_buf); @@ -1308,7 +1473,7 @@ static int expire_register(const void *data); static void *do_monitor(void *data); static int restart_monitor(void); -static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer); +static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer); static struct ast_variable *copy_vars(struct ast_variable *src); static int dialog_find_multiple(void *obj, void *arg, int flags); static struct ast_channel *sip_pvt_lock_full(struct sip_pvt *pvt); @@ -1330,6 +1495,7 @@ static void mwi_event_cb(void *, struct stasis_subscription *, struct stasis_message *); static void network_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static void acl_change_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); +static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message); static void sip_keepalive_all_peers(void); #define peer_in_destruction(peer) (ao2_ref(peer, 0) == 0) @@ -1394,6 +1560,8 @@ static inline int sip_debug_test_pvt(struct sip_pvt *p); static void append_history_full(struct sip_pvt *p, const char *fmt, ...); static void sip_dump_history(struct sip_pvt *dialog); +static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req); +static void send_qrt_url(struct sip_peer *peer); /*--- 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); @@ -1404,7 +1572,7 @@ static struct sip_peer *temp_peer(const char *name); static void register_peer_exten(struct sip_peer *peer, int onoff); static int sip_poke_peer_s(const void *data); -static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req); +static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *p, struct sip_request *req, int *addrchanged); static void reg_source_db(struct sip_peer *peer); static void destroy_association(struct sip_peer *peer); static void set_insecure_flags(struct ast_flags *flags, const char *value, int lineno); @@ -1459,9 +1627,14 @@ static int transmit_state_notify(struct sip_pvt *p, struct state_notify_data *data, int full, int timeout); 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 sip_send_donotdisturb(struct sip_peer *peer); +static int sip_send_huntgroup(struct sip_peer *peer); +static int sip_send_callforward(struct sip_peer *peer); static int get_domain(const char *str, char *domain, int len); static void get_realm(struct sip_pvt *p, const struct sip_request *req); -static char *get_content(struct sip_request *req); +static char *get_content(struct sip_request *req, int start, int end); +static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done); +const char *find_content_type(struct sip_request *req); /*-- TCP connection handling ---*/ static void *_sip_tcp_helper_thread(struct ast_tcptls_session_instance *tcptls_session); @@ -1508,7 +1681,7 @@ static int handle_incoming(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, int *recount, int *nounlock); static int handle_request_update(struct sip_pvt *p, struct sip_request *req); static int handle_request_invite(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, int *recount, const char *e, int *nounlock); -static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock); +static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock); static int handle_request_bye(struct sip_pvt *p, struct sip_request *req); static int handle_request_register(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *sin, const char *e); static int handle_request_cancel(struct sip_pvt *p, struct sip_request *req); @@ -1573,6 +1746,7 @@ .properties = AST_CHAN_TP_WANTSJITTER | AST_CHAN_TP_CREATESJITTER, .requester = sip_request_call, /* called with chan unlocked */ .devicestate = sip_devicestate, /* called with chan unlocked (not chan-specific) */ + .presencestate = sip_presencestate, /* called with chan unlocked (not chan-specific) */ .call = sip_call, /* called with chan locked */ .send_html = sip_sendhtml, .hangup = sip_hangup, /* called with chan locked */ @@ -3398,9 +3572,23 @@ if (dialog->relatedpeer && dialog->relatedpeer->mwipvt == dialog) { dialog->relatedpeer->mwipvt = dialog_unref(dialog->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); } + /* Remove link from peer to subscription for Feature Events */ + if (dialog->relatedpeer && dialog->relatedpeer->fepvt == dialog) { + dialog->relatedpeer->fepvt = dialog_unref(dialog->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); + } if (dialog->relatedpeer && dialog->relatedpeer->call == dialog) { dialog->relatedpeer->call = dialog_unref(dialog->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); } + if (dialog->conference) { + ao2_ref(dialog->conference, -1); + dialog->conference = NULL; + } + if (dialog->recordoutpvt) { + dialog->recordoutpvt = dialog_unref(dialog->recordoutpvt, "delete ->recordoutpvt"); + } + if (dialog->recordinpvt) { + dialog->recordinpvt = dialog_unref(dialog->recordinpvt, "delete ->recordinpvt"); + } dialog_ref(dialog, "Stop scheduled items for unlink action"); if (ast_sched_add(sched, 0, __dialog_unlink_sched_items, dialog) < 0) { @@ -5299,6 +5487,85 @@ } } +static void destroy_alias(struct sip_alias *alias) +{ + if (alias->peer) { + alias->peer->lastms = 0; + + if (alias->peer->socket.tcptls_session) { + ao2_ref(alias->peer->socket.tcptls_session, -1); + } else if (alias->peer->socket.ws_session) { + ast_websocket_unref(alias->peer->socket.ws_session); + } + + ast_string_field_set(alias->peer, fullcontact, ""); + ast_string_field_set(alias->peer, username, ""); + ast_string_field_set(alias->peer, useragent, ""); + + if (!ast_sockaddr_isnull(&alias->peer->addr)) { + manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unregistered\r\nCause: Expired\r\n", + alias->peer->name); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + register_peer_exten(alias->peer, FALSE); + + memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); + } + sip_unref_peer(alias->peer, "destroy_alias removing peer ref"); + } + ast_free(alias->name); + ast_free(alias); +} + +static void clear_peer_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + + while ((alias = AST_LIST_REMOVE_HEAD(&peer->aliases, entry))) + destroy_alias(alias); +} + +/*! Destroy extension state subscription */ +static void destroy_subscription(struct sip_subscription *subscription) +{ + if (subscription->pvt) { + dialog_unlink_all(subscription->pvt); + dialog_unref(subscription->pvt, "destroying subscription"); + } + ast_string_field_free_memory(subscription); + ast_free(subscription); +} + +/* Destroy all peer-related extension state subscriptions */ +static void clear_peer_subscriptions(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + while ((subscription = AST_LIST_REMOVE_HEAD(&peer->subscriptions, entry))) + destroy_subscription(subscription); +} + +static void destroy_callback(struct sip_peer *peer) +{ + ast_extension_state_del(peer->callback->stateid, NULL); + sip_unref_peer(peer, "destroy_callback: removing callback ref"); + ast_free(peer->callback->exten); + ast_free(peer->callback); +} + +static void destroy_selected(struct sip_selected *selected) +{ + ast_string_field_free_memory(selected); + ast_free(selected); +} + +static void clear_peer_selected(struct sip_peer *peer) +{ + struct sip_selected *selected; + + while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) + destroy_selected(selected); +} + static void sip_destroy_peer_fn(void *peer) { sip_destroy_peer(peer); @@ -5315,6 +5582,9 @@ * happening right now. */ clear_peer_mailboxes(peer); + clear_peer_aliases(peer); + clear_peer_subscriptions(peer); + clear_peer_selected(peer); if (peer->outboundproxy) { ao2_ref(peer->outboundproxy, -1); @@ -5332,6 +5602,16 @@ peer->mwipvt = dialog_unref(peer->mwipvt, "unreffing peer->mwipvt"); } + if (peer->fepvt) { /* We have an active subscription, delete it */ + dialog_unlink_all(peer->fepvt); + peer->fepvt = dialog_unref(peer->fepvt, "unreffing peer->fepvt"); + } + + if (peer->callback) { + destroy_callback(peer); + peer->callback = NULL; + } + if (peer->chanvars) { ast_variables_destroy(peer->chanvars); peer->chanvars = NULL; @@ -5999,6 +6279,29 @@ } } +static void copy_pvt_data(struct sip_pvt *to_pvt, const struct sip_pvt *from_pvt) +{ + to_pvt->sa = from_pvt->sa; + to_pvt->recv = from_pvt->recv; + copy_socket_data(&to_pvt->socket, &from_pvt->socket); + ast_copy_flags(&to_pvt->flags[0], &from_pvt->flags[0], SIP_FLAGS_TO_COPY); + ast_copy_flags(&to_pvt->flags[1], &from_pvt->flags[1], SIP_PAGE2_FLAGS_TO_COPY); + ast_copy_flags(&to_pvt->flags[2], &from_pvt->flags[2], SIP_PAGE3_FLAGS_TO_COPY); + + /* Recalculate our side, and recalculate Call ID */ + ast_sip_ouraddrfor(&to_pvt->sa, &to_pvt->ourip, to_pvt); + change_callid_pvt(to_pvt, NULL); + + ast_string_field_set(to_pvt, tohost, from_pvt->tohost); + to_pvt->portinuri = from_pvt->portinuri; + to_pvt->fromdomainport = from_pvt->fromdomainport; + + ast_string_field_set(to_pvt, fullcontact, from_pvt->fullcontact); + ast_string_field_set(to_pvt, username, from_pvt->username); + ast_string_field_set(to_pvt, fromuser, from_pvt->fromuser); + ast_string_field_set(to_pvt, fromname, from_pvt->fromname); +} + /*! \brief Initialize DTLS-SRTP support on an RTP instance */ static int dialog_initialize_dtls_srtp(const struct sip_pvt *dialog, struct ast_rtp_instance *rtp, struct ast_sdp_srtp **srtp) { @@ -6300,6 +6603,10 @@ dialog->fromdomainport = peer->fromdomainport; } dialog->callingpres = peer->callingpres; + dialog->donotdisturb = peer->donotdisturb; + if (!ast_strlen_zero(peer->callforward)) { + ast_string_field_set(dialog, callforward, peer->callforward); + } return 0; } @@ -6470,6 +6777,15 @@ } } + if (p->donotdisturb && ast_test_flag(&p->flags[2], SIP_PAGE3_DND_BUSY)) { + ast_queue_control(p->owner, AST_CONTROL_BUSY); + return 0; + } else if (!ast_strlen_zero(p->callforward)) { + ast_channel_call_forward_set(ast, p->callforward); + ast_queue_control(p->owner, AST_CONTROL_BUSY); + return 0; + } + /* Check whether there is vxml_url, distinctive ring variables */ headp = ast_channel_varshead(ast); AST_LIST_TRAVERSE(headp, current, entries) { @@ -6692,6 +7008,9 @@ /* Remove link from peer to subscription of MWI */ if (p->relatedpeer && p->relatedpeer->mwipvt == p) p->relatedpeer->mwipvt = dialog_unref(p->relatedpeer->mwipvt, "delete ->relatedpeer->mwipvt"); + /* Remove link from peer to subscription for Feature Events */ + if (p->relatedpeer && p->relatedpeer->fepvt) + p->relatedpeer->fepvt = dialog_unref(p->relatedpeer->fepvt, "delete ->relatedpeer->fepvt"); if (p->relatedpeer && p->relatedpeer->call == p) p->relatedpeer->call = dialog_unref(p->relatedpeer->call, "unset the relatedpeer->call field in tandem with relatedpeer field itself"); @@ -7230,6 +7549,11 @@ if (sipdebug) ast_debug(1, "update_call_counter(%s) - decrement call limit counter on hangup\n", p->username); update_call_counter(p, DEC_CALL_LIMIT); + if (!ast_test_flag(&p->flags[0], SIP_INC_COUNT) && p->relatedpeer) { + ao2_lock(p->relatedpeer); + clear_peer_selected(p->relatedpeer); + ao2_unlock(p->relatedpeer); + } } /* Determine how to disconnect */ @@ -8119,6 +8443,7 @@ case AST_CONTROL_UPDATE_RTP_PEER: /* Absorb this since it is handled by the bridge */ break; case AST_CONTROL_FLASH: /* We don't currently handle AST_CONTROL_FLASH here, but it is expected, so we don't need to warn either. */ + case AST_CONTROL_MCID: res = -1; break; case AST_CONTROL_PVT_CAUSE_CODE: /* these should be handled by the code in channel.c */ @@ -8591,14 +8916,12 @@ return __get_header(req, name, &start); } - AST_THREADSTORAGE(sip_content_buf); /*! \brief Get message body content */ -static char *get_content(struct sip_request *req) +static char *get_content(struct sip_request *req, int start, int end) { struct ast_str *str; - int i; if (!(str = ast_str_thread_get(&sip_content_buf, 128))) { return NULL; @@ -8606,8 +8929,8 @@ ast_str_reset(str); - for (i = 0; i < req->lines; i++) { - if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[i])) < 0) { + while (start < req->lines && start <= end) { + if (ast_str_append(&str, 0, "%s\n", REQ_OFFSET_TO_STR(req, line[start++])) < 0) { return NULL; } } @@ -8615,6 +8938,68 @@ return ast_str_buffer(str); } +/*! \brief find the content boundary */ +static int find_boundary(struct sip_request *req, const char *boundary, int start, int *done) +{ + char *line; + int len = strlen(boundary); + + for (*done = 0; start < req->lines; start++) { + line = REQ_OFFSET_TO_STR(req, line[start]); + if (strncmp(line, "--", 2)) { + continue; + } + if (!strncmp(boundary, line + 2, len)) { + if (!strcmp(line + 2 + len, "--")) { + *done = 1; + } else if (strcmp(line + 2 + len, "")) { + continue; + } + return start; + } + } + + return -1; +} + +/*! \brief get the content type from either the Content-Type header or the Content-Type of the first part */ +const char *find_content_type(struct sip_request *req) +{ + const char *type = sip_get_header(req, "Content-Type"); + char *boundary; + const char *line; + int start, done; + + if (ast_strlen_zero(type) || strncasecmp(type, "multipart/mixed", 15)) { + return type; + } + + if ((boundary = strcasestr(type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(type, "; boundary="))) { + boundary += 11; + } else { + return ""; + } + boundary = ast_strdupa(boundary); + boundary = strsep(&boundary, ";"); + + if ((start = find_boundary(req, boundary, 0, &done)) == -1) { + return ""; + } + + for (++start; start < req->lines; start++) { + line = REQ_OFFSET_TO_STR(req, line[start]); + if (!strncasecmp(line, "Content-Type:", 13)) { + return ast_skip_blanks(line + 13); + } else if (!strcasecmp(line, "")) { + break; + } + } + + return ""; +} + /*! \brief Read RTP from network */ static struct ast_frame *sip_rtp_read(struct ast_channel *ast, struct sip_pvt *p, int *faxdetect) { @@ -10298,7 +10683,9 @@ /* Others */ int sendonly = -1; unsigned int numberofports; - int last_rtpmap_codec = 0; + int audio_codec = 255; + int video_codec = 255; + int rtpmap_codecs = 0; int red_data_pt[10]; /* For T.140 RED */ int red_num_gen = 0; /* For T.140 RED */ char red_fmtp[100] = "empty"; /* For T.140 RED */ @@ -10366,11 +10753,11 @@ if (process_sdp_a_sendonly(value, &sendonly)) { processed = TRUE; } - else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) + else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) processed = TRUE; - else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) + else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) processed = TRUE; - else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) + else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) processed = TRUE; else if (process_sdp_a_image(value, p)) processed = TRUE; @@ -10827,7 +11214,7 @@ ast_log(AST_LOG_NOTICE, "Processed audio crypto attribute without SAVP specified; accepting anyway\n"); secure_audio = TRUE; } - } else if (process_sdp_a_audio(value, p, &newaudiortp, &last_rtpmap_codec)) { + } else if (process_sdp_a_audio(value, p, &newaudiortp, &audio_codec, &rtpmap_codecs)) { processed = TRUE; } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_audio)) { processed = TRUE; @@ -10850,7 +11237,7 @@ ast_log(AST_LOG_NOTICE, "Processed video crypto attribute without SAVP specified; accepting anyway\n"); secure_video = TRUE; } - } else if (process_sdp_a_video(value, p, &newvideortp, &last_rtpmap_codec)) { + } else if (process_sdp_a_video(value, p, &newvideortp, &video_codec, &rtpmap_codecs)) { processed = TRUE; } else if (process_sdp_a_rtcp_mux(value, p, &remote_rtcp_mux_video)) { processed = TRUE; @@ -10860,7 +11247,7 @@ else if (text) { if (process_sdp_a_ice(value, p, p->trtp, rtcp_mux_offered)) { processed = TRUE; - } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &last_rtpmap_codec)) { + } else if (process_sdp_a_text(value, p, &newtextrtp, red_fmtp, &red_num_gen, red_data_pt, &rtpmap_codecs)) { processed = TRUE; } else if (!processed_crypto && process_crypto(p, p->trtp, &p->tsrtp, value)) { processed_crypto = TRUE; @@ -11542,11 +11929,11 @@ return found; } -static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *last_rtpmap_codec) +static int process_sdp_a_audio(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newaudiortp, int *audio_codec, int *rtpmap_codecs) { int found = FALSE; unsigned int codec; - char mimeSubtype[128]; + char mime_subtype[128]; char fmtp_string[256]; unsigned int sample_rate; int debug = sip_debug_test_pvt(p); @@ -11569,24 +11956,24 @@ ast_rtp_codecs_set_framing(newaudiortp, framing); } found = TRUE; - } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { + } else if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ - if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { - if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mimeSubtype, + if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { + if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newaudiortp, NULL, codec, "audio", mime_subtype, ast_test_flag(&p->flags[0], SIP_G726_NONSTANDARD) ? AST_RTP_OPT_G726_NONSTANDARD : 0, sample_rate))) { if (debug) - ast_verbose("Found audio description format %s for ID %u\n", mimeSubtype, codec); - //found_rtpmap_codecs[last_rtpmap_codec] = codec; - (*last_rtpmap_codec)++; + ast_verbose("Found audio description format %s for ID %u\n", mime_subtype, codec); + *audio_codec = codec; + (*rtpmap_codecs)++; found = TRUE; } else { ast_rtp_codecs_payloads_unset(newaudiortp, NULL, codec); if (debug) - ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); + ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); } } else { if (debug) - ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); + ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); } } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { struct ast_format *format; @@ -11622,36 +12009,37 @@ return found; } -static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *last_rtpmap_codec) +static int process_sdp_a_video(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newvideortp, int *video_codec, int *rtpmap_codecs) { int found = FALSE; unsigned int codec; - char mimeSubtype[128]; + char mime_subtype[128]; unsigned int sample_rate; int debug = sip_debug_test_pvt(p); char fmtp_string[256]; + char imageattr[256]; - if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { + if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ - if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { + if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { /* Note: should really look at the '#chans' params too */ - if (!strncasecmp(mimeSubtype, "H26", 3) || !strncasecmp(mimeSubtype, "MP4", 3) - || !strncasecmp(mimeSubtype, "VP8", 3)) { - if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mimeSubtype, 0, sample_rate))) { + if (!strncasecmp(mime_subtype, "H26", 3) || !strncasecmp(mime_subtype, "MP4", 3) + || !strncasecmp(mime_subtype, "VP8", 3)) { + if (!(ast_rtp_codecs_payloads_set_rtpmap_type_rate(newvideortp, NULL, codec, "video", mime_subtype, 0, sample_rate))) { if (debug) - ast_verbose("Found video description format %s for ID %u\n", mimeSubtype, codec); - //found_rtpmap_codecs[last_rtpmap_codec] = codec; - (*last_rtpmap_codec)++; + ast_verbose("Found video description format %s for ID %u\n", mime_subtype, codec); + *video_codec = codec; + (*rtpmap_codecs)++; found = TRUE; } else { ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); if (debug) - ast_verbose("Found unknown media description format %s for ID %u\n", mimeSubtype, codec); + ast_verbose("Found unknown media description format %s for ID %u\n", mime_subtype, codec); } } } else { if (debug) - ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); + ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); } } else if (sscanf(a, "fmtp: %30u %255[^\t\n]", &codec, fmtp_string) == 2) { struct ast_format *format; @@ -11671,41 +12059,61 @@ } ao2_ref(format, -1); } + } else if (sscanf(a, "imageattr: %30u recv %255[^\t\n]", &codec, imageattr) == 2 + || (codec = *video_codec, sscanf(a, "imageattr:* recv %255[^\t\n]", imageattr) == 1)) { + struct ast_format *format; + + if ((format = ast_rtp_codecs_get_payload_format(newvideortp, codec))) { + struct ast_format *format_parsed; + + format_parsed = ast_format_attribute_set(format, "imageattr", imageattr); + + if (format_parsed) { + ast_rtp_codecs_payload_replace_format(newvideortp, codec, format_parsed); + ao2_replace(format, format_parsed); + ao2_ref(format_parsed, -1); + found = TRUE; + } else { + ast_rtp_codecs_payloads_unset(newvideortp, NULL, codec); + } + ao2_ref(format, -1); + } } return found; } -static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *last_rtpmap_codec) +static int process_sdp_a_text(const char *a, struct sip_pvt *p, struct ast_rtp_codecs *newtextrtp, char *red_fmtp, int *red_num_gen, int *red_data_pt, int *rtpmap_codecs) { int found = FALSE; unsigned int codec; - char mimeSubtype[128]; + char mime_subtype[128]; unsigned int sample_rate; char *red_cp; int debug = sip_debug_test_pvt(p); - if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mimeSubtype, &sample_rate) == 3) { + if (sscanf(a, "rtpmap: %30u %127[^/]/%30u", &codec, mime_subtype, &sample_rate) == 3) { /* We have a rtpmap to handle */ - if (*last_rtpmap_codec < SDP_MAX_RTPMAP_CODECS) { - if (!strncasecmp(mimeSubtype, "T140", 4)) { /* Text */ + if (*rtpmap_codecs < SDP_MAX_RTPMAP_CODECS) { + if (!strncasecmp(mime_subtype, "T140", 4)) { /* Text */ if (p->trtp) { - /* ast_verbose("Adding t140 mimeSubtype to textrtp struct\n"); */ - ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); + /* ast_verbose("Adding t140 mime_subtype to textrtp struct\n"); */ + ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); found = TRUE; } - } else if (!strncasecmp(mimeSubtype, "RED", 3)) { /* Text with Redudancy */ + } else if (!strncasecmp(mime_subtype, "RED", 3)) { /* Text with Redudancy */ if (p->trtp) { - ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mimeSubtype, 0, sample_rate); + ast_rtp_codecs_payloads_set_rtpmap_type_rate(newtextrtp, NULL, codec, "text", mime_subtype, 0, sample_rate); sprintf(red_fmtp, "fmtp:%u ", codec); if (debug) ast_verbose("RED submimetype has payload type: %u\n", codec); found = TRUE; } } + (*rtpmap_codecs)++; } else { if (debug) - ast_verbose("Discarded description format %s for ID %u\n", mimeSubtype, codec); + ast_verbose("Discarded description format %s for ID %u\n", mime_subtype, codec); } } else if (!strncmp(a, red_fmtp, strlen(red_fmtp))) { char *rest = NULL; @@ -11846,15 +12254,20 @@ * is supported for this dialog. */ static int add_supported(struct sip_pvt *pvt, struct sip_request *req) { - char supported_value[SIPBUFSIZE]; - int res; + struct ast_str *supported = ast_str_alloca(128); - sprintf(supported_value, "replaces%s%s", - (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) ? ", timer" : "", - ast_test_flag(&pvt->flags[0], SIP_USEPATH) ? ", path" : ""); - res = add_header(req, "Supported", supported_value); + ast_str_append(&supported, -1, "replaces"); + if (st_get_mode(pvt, 0) != SESSION_TIMER_MODE_REFUSE) { + ast_str_append(&supported, -1, ",timer"); + } + if (ast_test_flag(&pvt->flags[0], SIP_USEPATH)) { + ast_str_append(&supported, -1, ",path"); + } + if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + ast_str_append(&supported, -1, ",X-cisco-sis-7.0.0"); + } - return res; + return add_header(req, "Supported", ast_str_buffer(supported)); } /*! \brief Add header to SIP message */ @@ -12513,8 +12926,9 @@ ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND); add_rpid(&resp, p); } + add_security_call_info(&resp, p); if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { - add_cc_call_info_to_response(p, &resp); + add_cc_call_info(&resp, p); } /* If we are sending a 302 Redirect we can add a diversion header if the redirect information is set */ @@ -12761,6 +13175,35 @@ return send_response(p, &resp, reliable, seqno); } +/*! \brief Respond with an optionind response */ +static int transmit_response_with_optionsind(struct sip_pvt *p, const struct sip_request *req) +{ + struct sip_request resp; + + respprep(&resp, p, "200 OK", req); + + add_header(&resp, "Content-Type", "application/x-cisco-remotecc-response+xml"); + add_date(&resp); + + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "200\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + add_content(&resp, "\n"); + + return send_response(p, &resp, XMIT_UNRELIABLE, 0); +} + /*! \brief Extract domain from SIP To/From header \return -1 on error, 1 if domain string is empty, 0 if domain was properly extracted @@ -12985,15 +13428,14 @@ { struct ast_str *tmp = ast_str_alloca(256); char tmp2[256]; - char lid_name_buf[128]; char *lid_num; char *lid_name; int lid_pres; const char *fromdomain; - const char *privacy = NULL; - const char *screen = NULL; + const char *privacy; + const char *screen; + const char *callback_num; struct ast_party_id connected_id; - const char *anonymous_string = "\"Anonymous\" "; if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { return 0; @@ -13004,16 +13446,12 @@ } connected_id = ast_channel_connected_effective_id(p->owner); lid_num = S_COR(connected_id.number.valid, connected_id.number.str, NULL); - if (!lid_num) { - return 0; - } lid_name = S_COR(connected_id.name.valid, connected_id.name.str, NULL); - if (!lid_name) { - lid_name = lid_num; + if (!lid_num && !lid_name) { + return 0; } - ast_escape_quoted(lid_name, lid_name_buf, sizeof(lid_name_buf)); - lid_pres = ast_party_id_presentation(&connected_id); + lid_pres = ast_party_id_presentation(&connected_id); if (((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) && (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) == SIP_PAGE2_TRUST_ID_OUTBOUND_NO)) { /* If pres is not allowed and we don't trust the peer, we don't apply an RPID header */ @@ -13029,67 +13467,67 @@ fromdomain = ast_sockaddr_stringify_host_remote(&p->ourip); } - lid_num = ast_uri_encode(lid_num, tmp2, sizeof(tmp2), ast_uri_sip_user); + if (!ast_strlen_zero(lid_name)) { + /* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */ + if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && !strcmp(lid_name, "Conference") && ast_strlen_zero(lid_num) && p->conference) { + lid_name = "\2004"; + } + + ast_escape_quoted(lid_name, tmp2, sizeof(tmp2)); + ast_str_append(&tmp, -1, "\"%s\" ", tmp2); + } + ast_str_append(&tmp, -1, "owner, "CISCO_CALLBACK_NUMBER"); + if (!ast_strlen_zero(callback_num)) { + ast_uri_encode(callback_num, tmp2, sizeof(tmp2), ast_uri_sip_user); + ast_str_append(&tmp, -1, ";x-cisco-callback-number=%s", tmp2); + } + ast_str_append(&tmp, -1, ">"); if (ast_test_flag(&p->flags[0], SIP_SENDRPID_PAI)) { if (ast_test_flag(&p->flags[1], SIP_PAGE2_TRUST_ID_OUTBOUND) != SIP_PAGE2_TRUST_ID_OUTBOUND_LEGACY) { /* trust_id_outbound = yes - Always give full information even if it's private, but append a privacy header * When private data is included */ - ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { add_header(req, "Privacy", "id"); } } else { /* trust_id_outbound = legacy - behave in a non RFC-3325 compliant manner and send anonymized data when * when handling private data. */ - if ((lid_pres & AST_PRES_RESTRICTION) == AST_PRES_ALLOWED) { - ast_str_set(&tmp, -1, "\"%s\" ", lid_name_buf, lid_num, fromdomain); - } else { - ast_str_set(&tmp, -1, "%s", anonymous_string); + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + ast_str_set(&tmp, -1, "\"Anonymous\" "); } } add_header(req, "P-Asserted-Identity", ast_str_buffer(tmp)); } else { - ast_str_set(&tmp, -1, "\"%s\" ;party=%s", lid_name_buf, lid_num, fromdomain, p->outgoing_call ? "calling" : "called"); + ast_str_append(&tmp, -1, ";party=%s", p->outgoing_call ? "calling" : "called"); - switch (lid_pres) { - case AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED: - case AST_PRES_ALLOWED_USER_NUMBER_FAILED_SCREEN: - privacy = "off"; - screen = "no"; - break; - case AST_PRES_ALLOWED_USER_NUMBER_PASSED_SCREEN: - case AST_PRES_ALLOWED_NETWORK_NUMBER: - privacy = "off"; - screen = "yes"; - break; - case AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED: - case AST_PRES_PROHIB_USER_NUMBER_FAILED_SCREEN: - privacy = "full"; - screen = "no"; - break; - case AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN: - case AST_PRES_PROHIB_NETWORK_NUMBER: + if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { privacy = "full"; - screen = "yes"; - break; - case AST_PRES_NUMBER_NOT_AVAILABLE: - break; - default: - if ((lid_pres & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { - privacy = "full"; - } - else - privacy = "off"; - screen = "no"; - break; + } else { + privacy = "off"; } - if (!ast_strlen_zero(privacy) && !ast_strlen_zero(screen)) { - ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); + if (lid_pres & AST_PRES_USER_NUMBER_PASSED_SCREEN) { + screen = "yes"; + } else { + screen = "no"; } + ast_str_append(&tmp, -1, ";privacy=%s;screen=%s", privacy, screen); add_header(req, "Remote-Party-ID", ast_str_buffer(tmp)); + + if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + ast_str_reset(tmp); + ast_str_append(&tmp, -1, "; orientation=%s", p->outgoing_call ? "from" : "to"); + + add_header(req, "Call-Info", ast_str_buffer(tmp)); + } } return 0; } @@ -13309,6 +13747,12 @@ } ast_str_append(m_buf, 0, " %d", rtp_code); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + /* Needs to be a large number otherwise video quality is poor */ + ast_str_append(a_buf, 0, "b=TIAS:4000000\r\n"); + } + ast_str_append(a_buf, 0, "a=rtpmap:%d %s/%u\r\n", rtp_code, subtype, rate); /* VP8: add RTCP FIR support */ if (ast_format_cmp(format, ast_format_vp8) == AST_FORMAT_CMP_EQUAL) { @@ -13316,6 +13760,16 @@ } ast_format_generate_sdp_fmtp(format, rtp_code, a_buf); + + if (ast_format_cmp(format, ast_format_h264) == AST_FORMAT_CMP_EQUAL && ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + const char *imageattr = (const char *) ast_format_attribute_get(format, "imageattr"); + + if (ast_strlen_zero(imageattr)) { + imageattr = "[x=640,y=480,q=0.50]"; + } + + ast_str_append(a_buf, 0, "a=imageattr:%d recv %s\r\n", rtp_code, imageattr); + } } /*! \brief Add text codec offer to SDP offer/answer body in INVITE or 200 OK */ @@ -13692,7 +14146,7 @@ ast_test_flag(&p->flags[2], SIP_PAGE3_FORCE_AVP))); /* Build max bitrate string */ - if (p->maxcallbitrate) + if (p->maxcallbitrate && !ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) snprintf(bandwidth, sizeof(bandwidth), "b=CT:%d\r\n", p->maxcallbitrate); if (debug) { ast_verbose("Video is at %s\n", ast_sockaddr_stringify(&vdest)); @@ -13732,6 +14186,11 @@ } /* Start building generic SDP headers */ + if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { + ast_str_append(&a_audio, 0, "a=label:X-relay-nearend\r\n"); + } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { + ast_str_append(&a_audio, 0, "a=label:X-relay-farend\r\n"); + } /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ @@ -14100,7 +14559,7 @@ } } -static void add_cc_call_info_to_response(struct sip_pvt *p, struct sip_request *resp) +static void add_cc_call_info(struct sip_request *resp, struct sip_pvt *p) { char uri[SIPBUFSIZE]; struct ast_str *header = ast_str_alloca(SIPBUFSIZE); @@ -14132,6 +14591,29 @@ ao2_ref(agent, -1); } +static void add_security_call_info(struct sip_request *req, struct sip_pvt *p) +{ + struct ast_str *header = ast_str_alloca(SIPBUFSIZE); + + if (!ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + return; + } + + ast_str_set(&header, 0, "; security="); + + if (p->socket.type & AST_TRANSPORT_TLS) { + if (p->srtp) { + ast_str_append(&header, 0, "Encrypted"); + } else { + ast_str_append(&header, 0, "Authenticated"); + } + } else { + ast_str_append(&header, 0, "NotAuthenticated"); + } + + add_header(req, "Call-Info", ast_str_buffer(header)); +} + /*! \brief Used for 200 OK and 183 early media \return Will return XMIT_ERROR for network errors. */ @@ -14147,8 +14629,9 @@ if (rpid == TRUE) { add_rpid(&resp, p); } + add_security_call_info(&resp, p); if (ast_test_flag(&p->flags[0], SIP_OFFER_CC)) { - add_cc_call_info_to_response(p, &resp); + add_cc_call_info(&resp, p); } if (p->rtp) { ast_rtp_instance_activate(p->rtp); @@ -14433,8 +14916,6 @@ const char *d = NULL; /* domain in from header */ const char *urioptions = ""; int ourport; - int cid_has_name = 1; - int cid_has_num = 1; struct ast_party_id connected_id; int ret; @@ -14484,7 +14965,7 @@ /* Hey, it's a NOTIFY! See if they've configured a mwi_from. * XXX Right now, this logic works because the only place that mwi_from - * is set on the sip_pvt is in sip_send_mwi_to_peer. If things changed, then + * is set on the sip_pvt is in sip_send_mwi. If things changed, then * we might end up putting the mwi_from setting into other types of NOTIFY * messages as well. */ @@ -14493,13 +14974,8 @@ } if (ast_strlen_zero(l)) { - cid_has_num = 0; l = default_callerid; } - if (ast_strlen_zero(n)) { - cid_has_name = 0; - n = l; - } /* Allow user to be overridden */ if (!ast_strlen_zero(p->fromuser)) @@ -14542,7 +15018,7 @@ } /* If a caller id name was specified, prefix a display name, if there is enough room. */ - if (cid_has_name || !cid_has_num) { + if (!ast_strlen_zero(n)) { size_t written = ast_str_strlen(from); size_t name_len; if (sip_cfg.pedanticsipchecking) { @@ -14718,9 +15194,45 @@ quote_str, reason, quote_str); } + if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + int presentation = ast_party_id_presentation(&diverting_from); + int header_len = strlen(header_text); + const char *privacy; + const char *screen; + + if ((presentation & AST_PRES_RESTRICTION) != AST_PRES_ALLOWED) { + privacy = "full"; + } else { + privacy = "off"; + } + + if (presentation & AST_PRES_USER_NUMBER_PASSED_SCREEN) { + screen = "yes"; + } else { + screen = "no"; + } + + snprintf(header_text + header_len, sizeof(header_text) - header_len, + ";privacy=%s;screen=%s", privacy, screen); + } + add_header(req, "Diversion", header_text); } +static void add_join(struct sip_request *req, struct sip_pvt *pvt) +{ + char tmp[256]; + + if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_NEAREND) && !ast_test_flag(&pvt->flags[2], SIP_PAGE3_RELAY_FAREND)) + return; + + if (ast_strlen_zero(pvt->join_callid) || ast_strlen_zero(pvt->join_tag) || ast_strlen_zero(pvt->join_theirtag)) + return; + + snprintf(tmp, sizeof(tmp), "%s;from-tag=%s;to-tag=%s", pvt->join_callid, pvt->join_tag, pvt->join_theirtag); + add_header(req, "Join", tmp); +} + static int transmit_publish(struct sip_epa_entry *epa_entry, enum sip_publish_type publish_type, const char * const explicit_uri) { struct sip_pvt *pvt; @@ -14787,12 +15299,25 @@ } add_date(&req); if (sipmethod == SIP_REFER && p->refer) { /* Call transfer */ + if (!ast_strlen_zero(p->refer->require)) { + add_header(&req, "Require", p->refer->require); + } if (!ast_strlen_zero(p->refer->refer_to)) { add_header(&req, "Refer-To", p->refer->refer_to); } if (!ast_strlen_zero(p->refer->referred_by)) { add_header(&req, "Referred-By", p->refer->referred_by); } + if (!ast_strlen_zero(p->refer->content_id)) { + add_header(&req, "Content-Id", p->refer->content_id); + } + if (!ast_strlen_zero(p->refer->content_type)) { + add_header(&req, "Content-Type", p->refer->content_type); + } + if (ast_str_strlen(p->refer->content)) { + add_content(&req, ast_str_buffer(p->refer->content)); + } + add_expires(&req, p->expiry); } else if (sipmethod == SIP_SUBSCRIBE) { if (p->subscribed == MWI_NOTIFICATION) { add_header(&req, "Event", "message-summary"); @@ -14884,6 +15409,8 @@ add_rpid(&req, p); if (sipmethod == SIP_INVITE) { add_diversion(&req, p); + add_join(&req, p); + add_security_call_info(&req, p); } if (sdp) { offered_media_list_destroy(p); @@ -15179,6 +15706,9 @@ struct ast_channel *c = NULL; struct timeval tv = {0,}; + if (!device_state_info) + return NULL; + /* iterate ringing devices and get the oldest of all causing channels */ citer = ao2_iterator_init(device_state_info, 0); for (; (device_state = ao2_iterator_next(&citer)); ao2_ref(device_state, -1)) { @@ -15197,93 +15727,9 @@ return c ? ast_channel_ref(c) : NULL; } -/* XXX Candidate for moving into its own file */ -static int allow_notify_user_presence(struct sip_pvt *p) -{ - return (strstr(p->useragent, "Digium")) ? 1 : 0; -} - /*! \brief Builds XML portion of NOTIFY messages for presence or dialog updates */ static void state_notify_build_xml(struct state_notify_data *data, int full, const char *exten, const char *context, struct ast_str **tmp, struct sip_pvt *p, int subscribed, const char *mfrom, const char *mto) { - 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]; - - switch (data->state) { - case (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE): - statestring = (sip_cfg.notifyringing == NOTIFYRINGING_ENABLED) ? "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 (ast_get_hint(hint, sizeof(hint), NULL, 0, NULL, context, exten)) { - char *hint2; - char *individual_hint = NULL; - int hint_count = 0, unavailable_count = 0; - - /* strip off any possible PRESENCE providers from hint */ - if ((hint2 = strrchr(hint, ','))) { - *hint2 = '\0'; - } - hint2 = hint; - - 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 XPIDF_XML: case CPIM_PIDF_XML: @@ -15294,46 +15740,91 @@ ast_str_append(tmp, 0, "\n", mfrom); ast_str_append(tmp, 0, "\n", exten); ast_str_append(tmp, 0, "
\n", mto); - ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "open" : (local_state == NOTIFY_INUSE) ? "inuse" : "closed"); - ast_str_append(tmp, 0, "\n", (local_state == NOTIFY_OPEN) ? "online" : (local_state == NOTIFY_INUSE) ? "onthephone" : "offline"); + if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE) || data->presence_state == AST_PRESENCE_DND) { + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + } else if (data->state & (AST_EXTENSION_BUSY | AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_ONHOLD)) { + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + } else { + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + } ast_str_append(tmp, 0, "
\n
\n\n"); break; case PIDF_XML: /* Eyebeam supports this format */ - ast_str_append(tmp, 0, - "\n" - "\n", mfrom); - ast_str_append(tmp, 0, "\n"); - if (pidfstate[0] != '-') { - ast_str_append(tmp, 0, "\n", pidfstate); - } - ast_str_append(tmp, 0, "\n"); - ast_str_append(tmp, 0, "%s\n", pidfnote); /* Note */ - ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ - ast_str_append(tmp, 0, "%s\n", mto); - if (pidfstate[0] == 'b') /* Busy? Still open ... */ - ast_str_append(tmp, 0, "open\n"); - else - ast_str_append(tmp, 0, "%s\n", (local_state != NOTIFY_CLOSED) ? "open" : "closed"); - - if (allow_notify_user_presence(p) && (data->presence_state != AST_PRESENCE_INVALID) - && (data->presence_state != AST_PRESENCE_NOT_SET)) { + if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + ast_str_append(tmp, 0, + "\n", mfrom); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { + ast_str_append(tmp, 0, "\n"); + } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { + ast_str_append(tmp, 0, "\n"); + } else if (data->presence_state == AST_PRESENCE_DND) { + ast_str_append(tmp, 0, "\n"); + } + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n", p->dialogver); + if (data->state == AST_EXTENSION_UNAVAILABLE) { + ast_str_append(tmp, 0, "closed\n"); + } else { + ast_str_append(tmp, 0, "open\n"); + } ast_str_append(tmp, 0, "\n"); - ast_str_append(tmp, 0, "\n"); - ast_str_append(tmp, 0, "\n"); - ast_str_append(tmp, 0, "%s\n", - ast_presence_state2str(data->presence_state), - S_OR(data->presence_subtype, ""), - S_OR(data->presence_message, "")); - ast_str_append(tmp, 0, "\n"); - ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", + ast_str_append(tmp, 0, "\n"); + } else { + ast_str_append(tmp, 0, + "\n" + "\n", mfrom); + ast_str_append(tmp, 0, "\n"); + if (data->state == AST_EXTENSION_UNAVAILABLE) { + ast_str_append(tmp, 0, "\n"); + } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { + ast_str_append(tmp, 0, "\n"); + } + ast_str_append(tmp, 0, "\n"); + if (data->state & AST_EXTENSION_RINGING) { + ast_str_append(tmp, 0, "Ringing\n"); + } else if (data->state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY)) { + ast_str_append(tmp, 0, "On the phone\n"); + } else if (data->state == AST_EXTENSION_ONHOLD) { + ast_str_append(tmp, 0, "On hold\n"); + } else if (data->state == AST_EXTENSION_UNAVAILABLE) { + ast_str_append(tmp, 0, "Unavailable\n"); + } else if (data->presence_state == AST_PRESENCE_DND) { + ast_str_append(tmp, 0, "Do not disturb\n"); + } else { + ast_str_append(tmp, 0, "Ready\n"); + } + ast_str_append(tmp, 0, "\n", exten); /* Tuple start */ + ast_str_append(tmp, 0, "%s\n", mto); + if (data->state & (AST_EXTENSION_UNAVAILABLE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD)) { + ast_str_append(tmp, 0, "closed\n"); + } else { + ast_str_append(tmp, 0, "open\n"); + } + if (ast_test_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS) && (data->presence_state > 0)) { + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "\n"); + ast_str_append(tmp, 0, "%s\n", + ast_presence_state2str(data->presence_state), + S_OR(data->presence_subtype, ""), + S_OR(data->presence_message, "")); + ast_str_append(tmp, 0, "\n"); + ast_test_suite_event_notify("DIGIUM_PRESENCE_SENT", "PresenceState: %s\r\n" "Subtype: %s\r\n" "Message: %s", ast_presence_state2str(data->presence_state), S_OR(data->presence_subtype, ""), S_OR(data->presence_message, "")); + } + ast_str_append(tmp, 0, "\n\n"); } - ast_str_append(tmp, 0, "\n\n"); break; case DIALOG_INFO_XML: /* SNOM subscribes in this format */ ast_str_append(tmp, 0, "\n"); @@ -15443,7 +15934,13 @@ } else { ast_str_append(tmp, 0, "\n", exten); } - ast_str_append(tmp, 0, "%s\n", statestring); + if (data->state & AST_EXTENSION_RINGING && sip_cfg.notifyringing) { + ast_str_append(tmp, 0, "early\n"); + } else if (data->state & (AST_EXTENSION_RINGING | AST_EXTENSION_INUSE | AST_EXTENSION_BUSY | AST_EXTENSION_ONHOLD) || data->presence_state == AST_PRESENCE_DND) { + ast_str_append(tmp, 0, "confirmed\n"); + } else { + ast_str_append(tmp, 0, "terminated\n"); + } if (data->state == AST_EXTENSION_ONHOLD) { ast_str_append(tmp, 0, "\n\n" "\n" @@ -15538,36 +16035,22 @@ add_header(&req, "Subscription-State", "terminated;reason=noresource"); break; default: - if (p->expiry) + if (p->expiry > 0) { + char tmp[64]; + + snprintf(tmp, sizeof(tmp), "active;expires=%d", p->expiry); add_header(&req, "Subscription-State", "active"); - else /* Expired */ + } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE)) { + add_header(&req, "Subscription-State", "active"); + } else { /* Expired */ add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } } - switch (p->subscribed) { - case XPIDF_XML: - case CPIM_PIDF_XML: - add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); - add_header(&req, "Content-Type", subscriptiontype->mediatype); - p->dialogver++; - break; - case PIDF_XML: /* Eyebeam supports this format */ - add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); - add_header(&req, "Content-Type", subscriptiontype->mediatype); - p->dialogver++; - break; - case DIALOG_INFO_XML: /* SNOM subscribes in this format */ - add_header(&req, "Event", subscriptiontype->event); - state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); - add_header(&req, "Content-Type", subscriptiontype->mediatype); - p->dialogver++; - break; - case NONE: - default: - break; - } + add_header(&req, "Event", subscriptiontype->event); + state_notify_build_xml(data, full, p->exten, p->context, &tmp, p, p->subscribed, mfrom, mto); + add_header(&req, "Content-Type", subscriptiontype->mediatype); + p->dialogver++; add_content(&req, ast_str_buffer(tmp)); @@ -15628,7 +16111,7 @@ (0/0) notification. This can temporarily be disabled in sip.conf with the "buggymwi" option */ ast_str_append(&out, 0, "Voice-Message: %d/%d%s\r\n", - newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_BUGGY_MWI) ? "" : " (0/0)")); + newmsgs, oldmsgs, (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) ? "" : " (0/0)")); if (p->subscribed) { if (p->expiry) { @@ -15787,10 +16270,6 @@ if (!ast_test_flag(&p->flags[0], SIP_SENDRPID)) { return; } - if (!connected_id.number.valid - || ast_strlen_zero(connected_id.number.str)) { - return; - } append_history(p, "ConnectedLine", "%s party is now %s <%s>", ast_test_flag(&p->flags[0], SIP_OUTGOING) ? "Calling" : "Called", @@ -15800,7 +16279,12 @@ if (ast_channel_state(p->owner) == AST_STATE_UP || ast_test_flag(&p->flags[0], SIP_OUTGOING)) { struct sip_request req; - if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { + if (is_method_allowed(&p->allowed_methods, SIP_UPDATE) && !ast_strlen_zero(p->okcontacturi)) { + reqprep(&req, p, SIP_UPDATE, 0, 1); + add_rpid(&req, p); + add_header(&req, "X-Asterisk-rpid-update", "Yes"); + send_request(p, &req, XMIT_CRITICAL, p->ocseq); + } else if (!p->pendinginvite && (p->invitestate == INV_CONFIRMED || p->invitestate == INV_TERMINATED)) { reqprep(&req, p, ast_test_flag(&p->flags[0], SIP_REINVITE_UPDATE) ? SIP_UPDATE : SIP_INVITE, 0, 1); add_header(&req, "Allow", ALLOWED_METHODS); @@ -15812,11 +16296,6 @@ p->lastinvite = p->ocseq; ast_set_flag(&p->flags[0], SIP_OUTGOING); send_request(p, &req, XMIT_CRITICAL, p->ocseq); - } else if ((is_method_allowed(&p->allowed_methods, SIP_UPDATE)) && (!ast_strlen_zero(p->okcontacturi))) { - reqprep(&req, p, SIP_UPDATE, 0, 1); - add_rpid(&req, p); - add_header(&req, "X-Asterisk-rpid-update", "Yes"); - send_request(p, &req, XMIT_CRITICAL, p->ocseq); } else { /* We cannot send the update yet, so we have to wait until we can */ ast_set_flag(&p->flags[0], SIP_NEEDREINVITE); @@ -15845,6 +16324,159 @@ } } +/*! \brief Notify peer that the do not disturb status has changed */ +static int sip_send_donotdisturb(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return -1; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed in sip_send_donotdisturb. Unref dialog"); + return -1; + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); + ast_str_append(&content, 0, "\n", ast_test_flag(&pvt->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char tmp[512]; + + reqprep(&req, pvt, SIP_NOTIFY, 0, 1); + + add_header(&req, "Event", "as-feature-event"); + if (pvt->expiry) { + add_header(&req, "Subscription-State", "active"); + } else { + add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } + add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); + + add_content(&req, "\n"); + add_content(&req, "\n"); + snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); + add_content(&req, tmp); + add_content(&req, "\n"); + + send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); + } + + return 0; +} + +/*! \brief Notify peer that the huntgroup login state has changed */ +static int sip_send_huntgroup(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return -1; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed in sip_send_huntgroup. Unref dialog"); + return -1; + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } + + return 0; +} + +/*! \brief Notify peer that the call forwarding extension has changed */ +static int sip_send_callforward(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return -1; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed in sip_send_callforward. Unref dialog"); + return -1; + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); + ast_str_append(&content, 0, "%s\n", peer->callforward); + ast_str_append(&content, 0, "%s\n", !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char tmp[512]; + + reqprep(&req, pvt, SIP_NOTIFY, 0, 1); + + add_header(&req, "Event", "as-feature-event"); + if (pvt->expiry) { + add_header(&req, "Subscription-State", "active"); + } else { + add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } + add_header(&req, "Content-Type", "application/x-as-feature-event+xml"); + + add_content(&req, "\n"); + add_content(&req, "\n"); + add_content(&req, "\nforwardImmediate\n"); + snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); + add_content(&req, tmp); + add_content(&req, "\n"); + + send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); + } + + return 0; +} + static const struct _map_x_s regstatestrings[] = { { REG_STATE_FAILED, "Failed" }, { REG_STATE_UNREGISTERED, "Unregistered"}, @@ -16258,8 +16890,8 @@ ast_string_field_set(p, username, r->username); } /* Save extension in packet */ - if (!ast_strlen_zero(r->callback)) { - ast_string_field_set(p, exten, r->callback); + if (!ast_strlen_zero(r->exten)) { + ast_string_field_set(p, exten, r->exten); } /* Set transport and port so the correct contact is built */ @@ -16396,6 +17028,9 @@ { sip_refer_destroy(p); p->refer = ast_calloc_with_stringfields(1, struct sip_refer, 512); + if (p->refer) { + p->refer->content = ast_str_create(128); + } return p->refer ? 1 : 0; } @@ -16404,6 +17039,7 @@ { if (p->refer) { ast_string_field_free_memory(p->refer); + ast_free(p->refer->content); ast_free(p->refer); p->refer = NULL; } @@ -16483,6 +17119,28 @@ */ } +/*! \brief Send an out-of-dialog SIP REFER message with content */ +static int transmit_refer_with_content(struct sip_pvt *pvt, const char *content_type, const char *content) +{ + /* Refer is outgoing call */ + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + sip_refer_alloc(pvt); + pvt->refer->status = REFER_SENT; + + ast_string_field_set(pvt->refer, require, "norefersub"); + ast_string_field_set(pvt->refer, referred_by, pvt->our_contact); + ast_string_field_build(pvt->refer, content_id, "%08lx", ast_random()); + ast_string_field_build(pvt->refer, refer_to, "cid:%s", pvt->refer->content_id); + ast_string_field_set(pvt->refer, content_type, content_type); + + ast_str_set(&pvt->refer->content, 0, "%s", content); + + sip_scheddestroy(pvt, SIP_TRANS_TIMEOUT); + sip_alreadygone(pvt); + + return transmit_invite(pvt, SIP_REFER, 0, 2, NULL); +} + /*! \brief Send SIP INFO advice of charge message */ static int transmit_info_with_aoc(struct sip_pvt *p, struct ast_aoc_decoded *decoded) { @@ -16565,6 +17223,10 @@ if (sipmethod == SIP_ACK) { p->invitestate = INV_CONFIRMED; + + if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { + add_sdp(&resp, p, FALSE, TRUE, FALSE); + } } return send_request(p, &resp, reliable, seqno ? seqno : p->ocseq); @@ -16671,6 +17333,7 @@ static int expire_register(const void *data) { struct sip_peer *peer = (struct sip_peer *)data; + struct sip_subscription *subscription; if (!peer) { /* Hmmm. We have no peer. Weird. */ return 0; @@ -16678,6 +17341,7 @@ peer->expire = -1; peer->portinuri = 0; + ast_string_field_set(peer, regcallid, ""); destroy_association(peer); /* remove registration data from storage */ set_socket_transport(&peer->socket, peer->default_outbound_transport); @@ -16690,6 +17354,14 @@ peer->socket.ws_session = NULL; } + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + if (subscription->pvt) { + dialog_unlink_all(subscription->pvt); + dialog_unref(subscription->pvt, "destroying subscription"); + subscription->pvt = NULL; + } + } + if (peer->endpoint) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_OFFLINE); @@ -16722,6 +17394,7 @@ /* Only clear the addr after we check for destruction. The addr must remain * in order to unlink from the peers_by_ip container correctly */ memset(&peer->addr, 0, sizeof(peer->addr)); + expire_peer_aliases(peer); sip_unref_peer(peer, "removing peer ref for expire_register"); @@ -16983,7 +17656,7 @@ } /*! \brief Parse contact header and save registration (peer registration) */ -static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req) +static enum parse_register_result parse_register_contact(struct sip_pvt *pvt, struct sip_peer *peer, struct sip_request *req, int *addrchanged) { char contact[SIPBUFSIZE]; char data[SIPBUFSIZE]; @@ -17060,6 +17733,7 @@ } return PARSE_REGISTER_QUERY; } else if (!strcasecmp(curi, "*") || !expire) { /* Unregister this peer */ + pvt->expiry = 0; /* This means remove all registrations and return OK */ AST_SCHED_DEL_UNREF(sched, peer->expire, sip_unref_peer(peer, "remove register expire ref")); @@ -17197,15 +17871,34 @@ } /* Is this a new IP address for us? */ - if (ast_sockaddr_cmp(&peer->addr, &oldsin)) { + if ((*addrchanged = ast_sockaddr_cmp(&peer->addr, &oldsin))) { ast_verb(3, "Registered SIP '%s' at %s\n", peer->name, ast_sockaddr_stringify(&peer->addr)); + + /* Clear off-hook counter in case of the on-hook notification not being received */ + peer->offhook = 0; } sip_pvt_unlock(pvt); sip_poke_peer(peer, 0); sip_pvt_lock(pvt); register_peer_exten(peer, 1); + /* Save REGISTER dialog Call-ID */ + ast_string_field_set(peer, regcallid, pvt->callid); + + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + char *devicename; + + ast_copy_string(contact, sip_get_header(req, "Contact"), sizeof(contact)); + devicename = strcasestr(contact, ";+u.sip!devicename.ccm.cisco.com=\""); + + if (!ast_strlen_zero(devicename)) { + devicename += 34; + devicename = strsep(&devicename, "\""); + ast_string_field_set(peer, cisco_devicename, devicename); + } + } + /* Save User agent */ useragent = sip_get_header(req, "User-Agent"); if (strcasecmp(useragent, peer->useragent)) { @@ -17392,6 +18085,8 @@ /* Always OK if no secret */ if (ast_strlen_zero(secret) && ast_strlen_zero(md5secret)) { return AUTH_SUCCESSFUL; + } else if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && (sipmethod == SIP_REFER || sipmethod == SIP_PUBLISH)) { + return AUTH_SUCCESSFUL; /* Buggy Cisco USECALLMANAGER phones can't auth REFER or PUBLISH correctly */ } /* Always auth with WWW-auth since we're NOT a proxy */ @@ -17565,7 +18260,7 @@ } if (ast_mwi_state_type() == stasis_message_type(msg)) { - sip_send_mwi_to_peer(peer, 0); + sip_send_mwi(peer, 0); } } @@ -17622,6 +18317,218 @@ } } +struct pickup_notify_args { + ast_group_t callgroup; + struct ast_namedgroups *named_callgroups; + time_t now; +}; + +static int pickup_notify_cb(void *obj, void *arg, int flags) +{ + struct sip_peer *peer = obj; + struct pickup_notify_args *args = arg; + + ao2_lock(peer); + + if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) || + !ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { + ao2_unlock(peer); + return 0; + } + + if (ast_sockaddr_isnull(&peer->addr) || peer->ringing || peer->inuse || peer->donotdisturb) { + ao2_unlock(peer); + return 0; + } + + if ((peer->pickupgroup & args->callgroup) || ast_namedgroups_intersect(peer->named_pickupgroups, args->named_callgroups)) { + if (args->now - peer->cisco_pickupnotify_sent > peer->cisco_pickupnotify_timer) { + peer->cisco_pickupnotify_sent = args->now; + ao2_unlock(peer); + + return CMP_MATCH; + } + } + + ao2_unlock(peer); + + return 0; +} + +static void *pickup_notify_thread(void *obj) +{ + char *device = obj; + char match[AST_CHANNEL_NAME]; + struct ast_channel_iterator *chaniter; + struct ast_channel *pickupchan = NULL, *chan; + struct timeval creationtime = { 0, }; + struct pickup_notify_args args; + char *cid_num, *lid_num; + struct ao2_iterator *peeriter; + struct sip_peer *peer; + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + + snprintf(match, sizeof(match), "%s-", device); + chaniter = ast_channel_iterator_by_name_new(match, strlen(match)); + ast_free(device); + + while ((chan = ast_channel_iterator_next(chaniter))) { + ast_channel_lock(chan); + + /* Pick the youngest ringing channel */ + if (ast_channel_state(chan) == AST_STATE_RINGING && ast_tvcmp(ast_channel_creationtime(chan), creationtime) > 0) { + if (pickupchan) { + ast_channel_unref(pickupchan); + } + pickupchan = ast_channel_ref(chan); + creationtime = ast_channel_creationtime(chan); + } + + ast_channel_unlock(chan); + ast_channel_unref(chan); + } + + ast_channel_iterator_destroy(chaniter); + + if (!pickupchan) { + return NULL; + } + + ast_channel_lock(pickupchan); + + args.callgroup = ast_channel_callgroup(pickupchan); + args.named_callgroups = ast_ref_namedgroups(ast_channel_named_callgroups(pickupchan)); + + cid_num = ast_strdupa(S_COR(ast_channel_caller(pickupchan)->id.number.valid, ast_channel_caller(pickupchan)->id.number.str, "")); + lid_num = ast_strdupa(S_COR(ast_channel_connected(pickupchan)->id.number.valid, ast_channel_connected(pickupchan)->id.number.str, CALLERID_UNKNOWN)); + + ast_channel_unlock(pickupchan); + ast_channel_unref(pickupchan); + + if (!args.callgroup && !args.named_callgroups) { + return NULL; + } + + args.now = time(NULL); + /* We use ao2_callback here so that we don't hold the lock on the peers container while sending the notify dialogs */ + ao2_lock(peers); + + if (!(peeriter = ao2_callback(peers, OBJ_MULTIPLE, pickup_notify_cb, &args))) { + ast_log(LOG_ERROR, "Unable to create iterator for peers container in pickup_notify_thread\n"); + ao2_unlock(peers); + return NULL; + } + + ao2_unlock(peers); + ast_unref_namedgroups(args.named_callgroups); + + while ((peer = ao2_iterator_next(peeriter))) { + if ((!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { + sip_unref_peer(peer, "remove iterator ref"); + continue; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed in pickup_notify_thread. Unref dialog"); + sip_unref_peer(peer, "remove iterator ref"); + continue; + } + + ast_str_reset(content); + + if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, ""); + if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM)) { + ast_str_append(&content, 0, "From %s", lid_num); + } + if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO)) { + ast_str_append(&content, 0, "%s %s", + ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM) ? " to" : "To", cid_num); + } + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", peer->cisco_pickupnotify_timer); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + if (ast_test_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + + sip_unref_peer(peer, "remove iterator ref"); + } + + ao2_iterator_destroy(peeriter); + + return NULL; +} + +static void pickup_notify_stasis_subscribe(void) +{ + if (!pickup_notify_sub) { + pickup_notify_sub = stasis_subscribe(ast_device_state_topic_all(), pickup_notify_stasis_cb, NULL); + } +} + +static void pickup_notify_stasis_unsubscribe(void) +{ + if (pickup_notify_sub) { + pickup_notify_sub = stasis_unsubscribe(pickup_notify_sub); + } +} + +static void pickup_notify_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) +{ + struct ast_device_state_message *device_state; + char *device; + pthread_t threadid; + + if (stasis_message_type(message) != ast_device_state_message_type()) { + return; + } + + device_state = stasis_message_data(message); + + if (device_state->state != AST_DEVICE_RINGING) { + return; + } + + if ((device = ast_strdup(device_state->device)) == NULL) { + return; + } + + if (ast_pthread_create_detached_background(&threadid, NULL, pickup_notify_thread, device)) { + ast_free(device); + } +} + static void cb_extensionstate_destroy(int id, void *data) { struct sip_pvt *p = data; @@ -17649,8 +18556,8 @@ /* we must skip the next two checks for a queued state change or resubscribe */ } else if ((p->laststate == data->state && (~data->state & AST_EXTENSION_RINGING)) && (p->last_presence_state == data->presence_state && - !strcmp(p->last_presence_subtype, data->presence_subtype) && - !strcmp(p->last_presence_message, data->presence_message))) { + !strcmp(p->last_presence_subtype, S_OR(data->presence_subtype, "")) && + !strcmp(p->last_presence_message, S_OR(data->presence_message, "")))) { /* don't notify unchanged state or unchanged early-state causing parties again */ sip_pvt_unlock(p); return 0; @@ -17702,7 +18609,7 @@ } if (!force) { - ast_verb(2, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, + ast_debug(1, "Extension Changed %s[%s] new state %s for Notify User %s %s\n", exten, context, ast_extension_state2str(data->state), p->username, ast_test_flag(&p->flags[1], SIP_PAGE2_STATECHANGEQUEUE) ? "(queued)" : ""); } @@ -17725,12 +18632,126 @@ .presence_message = info->presence_message, }; - if ((info->reason == AST_HINT_UPDATE_PRESENCE) && !(allow_notify_user_presence(p))) { - /* ignore a presence triggered update if we know the useragent doesn't care */ - return 0; + return extensionstate_update(context, exten, ¬ify_data, p, FALSE); +} + +/*! + * \brief Send initial subscription state updates to peer + */ +static void extensionstate_subscriptions(struct sip_peer *peer) +{ + struct sip_subscription *subscription; + + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return; } - return extensionstate_update(context, exten, ¬ify_data, p, FALSE); + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + struct sip_request req; + struct ao2_container *device_state_info = NULL; + struct state_notify_data notify_data = { 0, }; + char *subtype = NULL, *message = NULL; + + if (subscription->pvt) { + /* Peer hasn't changed, keep original pvt */ + if (!ast_sockaddr_cmp(&peer->addr, &subscription->pvt->sa)) { + continue; + } + + dialog_unlink_all(subscription->pvt); + dialog_unref(subscription->pvt, "drop pvt"); + subscription->pvt = NULL; + } + + if (!subscription->pvt) { + if (!(subscription->pvt = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { + return; + } + } + + /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ + if (!ast_strlen_zero(peer->tohost)) { + ast_string_field_set(subscription->pvt, tohost, peer->tohost); + } else { + ast_string_field_set(subscription->pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); + } + + subscription->pvt->portinuri = peer->portinuri; + subscription->pvt->fromdomainport = peer->fromdomainport; + + ast_string_field_set(subscription->pvt, fullcontact, peer->fullcontact); + ast_string_field_set(subscription->pvt, username, peer->username); + ast_string_field_set(subscription->pvt, fromuser, subscription->exten); + ast_string_field_set(subscription->pvt, fromname, ""); + + ast_string_field_set(subscription->pvt, context, subscription->context); + ast_string_field_set(subscription->pvt, exten, subscription->exten); + ast_string_field_build(subscription->pvt, subscribeuri, "%s@%s", subscription->exten, subscription->context); + + ast_copy_flags(&subscription->pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); + ast_copy_flags(&subscription->pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); + ast_copy_flags(&subscription->pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); + + /* Notify is outgoing call */ + ast_set_flag(&subscription->pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&subscription->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + ast_set_flag(&subscription->pvt->flags[2], SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE); + + subscription->pvt->subscribed = PIDF_XML; /* Needs to be configurable */ + subscription->pvt->expiry = 0; + + subscription->pvt->sa = peer->addr; + subscription->pvt->recv = peer->addr; + copy_socket_data(&subscription->pvt->socket, &peer->socket); + + /* Recalculate our side, and recalculate Call ID */ + ast_sip_ouraddrfor(&subscription->pvt->sa, &subscription->pvt->ourip, subscription->pvt); + change_callid_pvt(subscription->pvt, NULL); + + initreqprep(&req, subscription->pvt, SIP_NOTIFY, NULL); + initialize_initreq(subscription->pvt, &req); + + /* Because we only use this req to initialize the pvt's initreq we have to manually deallocate it */ + deinit_req(&req); + + subscription->pvt->stateid = ast_extension_state_add_extended(subscription->pvt->context, subscription->pvt->exten, cb_extensionstate, subscription->pvt); + + if (subscription->pvt->stateid == -1) { + dialog_unlink_all(subscription->pvt); + dialog_unref(subscription->pvt, "drop pvt"); + subscription->pvt = NULL; + + continue; + } + + ast_debug(1, "Adding subscription for %s@%s (%s)\n", subscription->exten, subscription->context, subscription->pvt->callid); + + notify_data.state = ast_extension_state_extended(NULL, subscription->context, subscription->exten, &device_state_info); + if (notify_data.state < 0) { + ao2_cleanup(device_state_info); + continue; + } + + notify_data.presence_state = ast_hint_presence_state(NULL, subscription->context, subscription->exten, &subtype, &message); + notify_data.presence_subtype = subtype; + notify_data.presence_message = message; + notify_data.device_state_info = device_state_info; + + if (notify_data.state & AST_EXTENSION_RINGING) { + struct ast_channel *ringing = find_ringing_channel(notify_data.device_state_info, NULL); + + if (ringing) { + subscription->pvt->last_ringing_channel_time = ast_channel_creationtime(ringing); + ao2_ref(ringing, -1); + } + } + + extensionstate_update(subscription->context, subscription->exten, ¬ify_data, subscription->pvt, TRUE); + + ao2_cleanup(device_state_info); + ast_free(subtype); + ast_free(message); + } } /*! \brief Send a fake 401 Unauthorized response when the administrator @@ -17891,7 +18912,7 @@ char tmp[256]; char *c, *name, *unused_password, *domain; char *uri2 = ast_strdupa(uri); - int send_mwi = 0; + int addrchanged, registered = 0; terminate_uri(uri2); @@ -17961,6 +18982,16 @@ } peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, FALSE, 0); + /* Cisco USECALLMANAGER failover */ + if (peer && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + const char *contact = sip_get_header(req, "Contact"); + + if (strcasestr(contact, ";expires=0;cisco-keep-alive")) { + transmit_response_with_date(p, "200 OK", req); + return 0; + } + } + /* If we don't want username disclosure, use the bogus_peer when a user * is not found. */ if (!peer && sip_cfg.alwaysauthreject && sip_cfg.autocreatepeer == AUTOPEERS_DISABLED) { @@ -18000,7 +19031,7 @@ /* We have a successful registration attempt with proper authentication, now, update the peer */ - switch (parse_register_contact(p, peer, req)) { + switch (parse_register_contact(p, peer, req, &addrchanged)) { case PARSE_REGISTER_DENIED: ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); transmit_response_with_date(p, "603 Denied", req); @@ -18015,7 +19046,6 @@ ast_string_field_set(p, fullcontact, peer->fullcontact); transmit_response_with_date(p, "200 OK", req); res = 0; - send_mwi = 1; break; case PARSE_REGISTER_UPDATE: ast_string_field_set(p, fullcontact, peer->fullcontact); @@ -18023,9 +19053,14 @@ if (p->expiry != 0) { update_peer(peer, p->expiry); } + ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); /* Say OK and ask subsystem to retransmit msg counter */ - transmit_response_with_date(p, "200 OK", req); - send_mwi = 1; + if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { + transmit_response_with_optionsind(p, req); + } else { + transmit_response_with_date(p, "200 OK", req); + } + registered = 1; res = 0; break; } @@ -18049,7 +19084,7 @@ } ao2_lock(peer); sip_cancel_destroy(p); - switch (parse_register_contact(p, peer, req)) { + switch (parse_register_contact(p, peer, req, &addrchanged)) { case PARSE_REGISTER_DENIED: ast_log(LOG_WARNING, "Registration denied because of contact ACL\n"); transmit_response_with_date(p, "403 Forbidden", req); @@ -18063,13 +19098,17 @@ case PARSE_REGISTER_QUERY: ast_string_field_set(p, fullcontact, peer->fullcontact); transmit_response_with_date(p, "200 OK", req); - send_mwi = 1; res = 0; break; case PARSE_REGISTER_UPDATE: ast_string_field_set(p, fullcontact, peer->fullcontact); + ast_set2_flag(&p->flags[1], ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER), SIP_PAGE2_CISCO_USECALLMANAGER); /* Say OK and ask subsystem to retransmit msg counter */ - transmit_response_with_date(p, "200 OK", req); + if (p->expiry != 0 && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && addrchanged) { + transmit_response_with_optionsind(p, req); + } else { + transmit_response_with_date(p, "200 OK", req); + } if (peer->endpoint) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); @@ -18078,7 +19117,7 @@ "address", ast_sockaddr_stringify(addr)); ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); } - send_mwi = 1; + registered = 1; res = 0; break; } @@ -18086,10 +19125,19 @@ } } if (!res) { - if (send_mwi) { - sip_pvt_unlock(p); - sip_send_mwi_to_peer(peer, 0); - sip_pvt_lock(p); + if (p->expiry != 0 && registered) { + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + /* We only need to do an update if the peer addr has changed */ + if (addrchanged) { + register_peer_aliases(peer); + sip_send_bulkupdate(peer); + extensionstate_subscriptions(peer); + } + } else { + sip_pvt_unlock(p); + sip_send_mwi(peer, 0); + sip_pvt_lock(p); + } } else { update_peer_lastmsgssent(peer, -1, 0); } @@ -18208,82 +19256,6 @@ } } -/*! \brief Parse the parts of the P-Asserted-Identity header - * on an incoming packet. Returns 1 if a valid header is found - * and it is different from the current caller id. - */ -static int get_pai(struct sip_pvt *p, struct sip_request *req) -{ - char pai[256]; - char privacy[64]; - char *cid_num = NULL; - char *cid_name = NULL; - char emptyname[1] = ""; - int callingpres = AST_PRES_ALLOWED_USER_NUMBER_NOT_SCREENED; - char *uri = NULL; - int is_anonymous = 0, do_update = 1, no_name = 0; - - ast_copy_string(pai, sip_get_header(req, "P-Asserted-Identity"), sizeof(pai)); - - if (ast_strlen_zero(pai)) { - return 0; - } - - /* use the reqresp_parser function get_name_and_number*/ - if (get_name_and_number(pai, &cid_name, &cid_num)) { - return 0; - } - - if (global_shrinkcallerid && ast_is_shrinkable_phonenumber(cid_num)) { - ast_shrink_phone_number(cid_num); - } - - uri = get_in_brackets(pai); - if (!strncasecmp(uri, "sip:anonymous@anonymous.invalid", 31)) { - callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; - /*XXX Assume no change in cid_num. Perhaps it should be - * blanked? - */ - ast_free(cid_num); - is_anonymous = 1; - cid_num = (char *)p->cid_num; - } - - ast_copy_string(privacy, sip_get_header(req, "Privacy"), sizeof(privacy)); - if (!ast_strlen_zero(privacy) && strcasecmp(privacy, "none")) { - callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; - } - if (!cid_name) { - no_name = 1; - cid_name = (char *)emptyname; - } - /* Only return true if the supplied caller id is different */ - if (!strcasecmp(p->cid_num, cid_num) && !strcasecmp(p->cid_name, cid_name) && p->callingpres == callingpres) { - do_update = 0; - } else { - - ast_string_field_set(p, cid_num, cid_num); - ast_string_field_set(p, cid_name, cid_name); - p->callingpres = callingpres; - - if (p->owner) { - ast_set_callerid(p->owner, cid_num, cid_name, NULL); - ast_channel_caller(p->owner)->id.name.presentation = callingpres; - ast_channel_caller(p->owner)->id.number.presentation = callingpres; - } - } - - /* get_name_and_number allocates memory for cid_num and cid_name so we have to free it */ - if (!is_anonymous) { - ast_free(cid_num); - } - if (!no_name) { - ast_free(cid_name); - } - - return do_update; -} - /*! \brief Get name, number and presentation from remote party id header, * returns true if a valid header was found and it was different from the * current caller id. @@ -18298,6 +19270,7 @@ char *privacy = ""; char *screen = ""; char *start, *end; + int pai; if (!ast_test_flag(&p->flags[0], SIP_TRUSTRPID)) return 0; @@ -18306,7 +19279,13 @@ req = &p->initreq; ast_copy_string(tmp, sip_get_header(req, "Remote-Party-ID"), sizeof(tmp)); if (ast_strlen_zero(tmp)) { - return get_pai(p, req); + ast_copy_string(tmp, sip_get_header(req, "P-Asserted-Identity"), sizeof(tmp)); + if (ast_strlen_zero(tmp)) { + return 0; + } + pai = 1; + } else { + pai = 0; } /* @@ -18358,7 +19337,13 @@ if (!end) return 0; *end++ = '\0'; - if (*end) { + if (pai) { + if (!strcasecmp(sip_get_header(req, "Privacy"), "id")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_PASSED_SCREEN; + } else if (!strcasecmp(cid_num, "anonymous@anonymous.invalid")) { + callingpres = AST_PRES_PROHIB_USER_NUMBER_NOT_SCREENED; + } + } else if (*end) { start = end; if (*start != ';') return 0; @@ -18698,6 +19683,14 @@ return SIP_GET_DEST_EXTEN_NOT_FOUND; } +/*! \brief Find a companion dialog */ +struct sip_pvt *get_sip_pvt(const char *callid, const char *tag, const char *theirtag) +{ + struct sip_pvt dialog = { .callid = callid, .tag = tag, .theirtag = theirtag }; + + return ao2_find(dialogs, &dialog, OBJ_POINTER); +} + /*! \brief Find a companion dialog based on Replaces information * * This information may come from a Refer-To header in a REFER or from @@ -19280,10 +20273,10 @@ /* Then find devices based on IP */ if (!peer) { - char *uri_tmp, *callback = NULL, *dummy; + char *uri_tmp, *callbackexten = NULL, *dummy; uri_tmp = ast_strdupa(uri2); - parse_uri(uri_tmp, "sip:,sips:,tel:", &callback, &dummy, &dummy, &dummy); - if (!ast_strlen_zero(callback) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callback, p->socket.type))) { + parse_uri(uri_tmp, "sip:,sips:,tel:", &callbackexten, &dummy, &dummy, &dummy); + if (!ast_strlen_zero(callbackexten) && (peer = sip_find_peer_by_ip_and_exten(&p->recv, callbackexten, p->socket.type))) { ; /* found, fall through */ } else { peer = sip_find_peer(NULL, &p->recv, TRUE, FINDPEERS, FALSE, p->socket.type); @@ -19351,6 +20344,7 @@ do_setnat(p); + ast_string_field_set(p, authname, peer->name); ast_string_field_set(p, peersecret, peer->secret); ast_string_field_set(p, peermd5secret, peer->md5secret); ast_string_field_set(p, subscribecontext, peer->subscribecontext); @@ -19378,12 +20372,17 @@ p->allowtransfer = peer->allowtransfer; + /* Cisco peers only auth using the credentials of the primary peer */ + if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && peer->cisco_lineindex > 1) { + ast_string_field_set(p, authname, peer->cisco_authname); + } + if (ast_test_flag(&peer->flags[0], SIP_INSECURE_INVITE)) { /* Pretend there is no required authentication */ ast_string_field_set(p, peersecret, NULL); ast_string_field_set(p, peermd5secret, NULL); } - if (!(res = check_auth(p, req, peer->name, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { + if (!(res = check_auth(p, req, p->authname, p->peersecret, p->peermd5secret, sipmethod, uri2, reliable))) { /* build_peer, called through sip_find_peer, is not able to check the * sip_pvt->natdetected flag in order to determine if the peer is behind @@ -19751,7 +20750,7 @@ return; } - if (!(buf = get_content(req))) { + if (!(buf = get_content(req, 0, req->lines))) { ast_log(LOG_WARNING, "Unable to retrieve text from %s\n", p->callid); transmit_response(p, "500 Internal Server Error", req); if (!p->owner) { @@ -21179,7 +22178,7 @@ } /*! \brief list peer mailboxes to CLI */ -static void peer_mailboxes_to_str(struct ast_str **mailbox_str, struct sip_peer *peer) +static void get_peer_mailboxes(struct ast_str **mailbox_str, struct sip_peer *peer) { struct sip_mailbox *mailbox; @@ -21212,6 +22211,8 @@ struct ast_variable *v; int x = 0, load_realtime; int realtimepeers; + struct sip_alias *alias; + struct sip_subscription *subscription; realtimepeers = ast_check_realtime("sippeers"); @@ -21292,7 +22293,7 @@ print_named_groups(fd, peer->named_callgroups, 0); ast_cli(fd, " Nam. Pickupgr: "); print_named_groups(fd, peer->named_pickupgroups, 0); - peer_mailboxes_to_str(&mailbox_str, peer); + get_peer_mailboxes(&mailbox_str, peer); ast_cli(fd, " MOH Suggest : %s\n", peer->mohsuggest); ast_cli(fd, " Mailbox : %s\n", ast_str_buffer(mailbox_str)); ast_cli(fd, " VM Extension : %s\n", peer->vmexten); @@ -21371,9 +22372,9 @@ ast_cli(fd, " Qualify Freq : %d ms\n", peer->qualifyfreq); ast_cli(fd, " Keepalive : %d ms\n", peer->keepalive * 1000); if (peer->chanvars) { - ast_cli(fd, " Variables :\n"); + ast_cli(fd, " Variables : "); for (v = peer->chanvars ; v ; v = v->next) - ast_cli(fd, " %s = %s\n", v->name, v->value); + ast_cli(fd, "%s%s = %s\n", v != peer->chanvars ? " " : "", v->name, v->value); } ast_cli(fd, " Sess-Timers : %s\n", stmode2str(peer->stimer.st_mode_oper)); @@ -21385,6 +22386,26 @@ ast_cli(fd, " Use Reason : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON))); ast_cli(fd, " Encryption : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[1], SIP_PAGE2_USE_SRTP))); ast_cli(fd, " RTCP Mux : %s\n", AST_CLI_YESNO(ast_test_flag(&peer->flags[2], SIP_PAGE3_RTCP_MUX))); + ast_cli(fd, " DND : %s\n", AST_CLI_YESNO(peer->donotdisturb)); + ast_cli(fd, " CallFwd Ext. : %s\n", peer->callforward); + ast_cli(fd, " Hunt Group : %s\n", AST_CLI_YESNO(peer->huntgroup)); + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + ast_cli(fd, " Device Name : %s\n", peer->cisco_devicename); + + if (!AST_LIST_EMPTY(&peer->aliases)) { + ast_cli(fd, " BulkReg.Peers: "); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + ast_cli(fd, "%s%s (Line %d)\n", alias != AST_LIST_FIRST(&peer->aliases) ? " " : "", alias->name, alias->lineindex); + } + } + + if (!AST_LIST_EMPTY(&peer->subscriptions)) { + ast_cli(fd, " Subscriptions: "); + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + ast_cli(fd, "%s%s@%s\n", subscription != AST_LIST_FIRST(&peer->subscriptions) ? " " : "", subscription->exten, subscription->context); + } + } + } ast_cli(fd, "\n"); peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer ptr"); } else if (peer && type == 1) { /* manager listing */ @@ -21421,7 +22442,7 @@ astman_append(s, "%s\r\n", ast_print_namedgroups(&tmp_str, peer->named_pickupgroups)); ast_str_reset(tmp_str); astman_append(s, "MOHSuggest: %s\r\n", peer->mohsuggest); - peer_mailboxes_to_str(&tmp_str, peer); + get_peer_mailboxes(&tmp_str, peer); astman_append(s, "VoiceMailbox: %s\r\n", ast_str_buffer(tmp_str)); astman_append(s, "TransferMode: %s\r\n", transfermode2str(peer->allowtransfer)); astman_append(s, "LastMsgsSent: %d\r\n", peer->lastmsgssent); @@ -21480,6 +22501,17 @@ } astman_append(s, "SIP-Use-Reason-Header: %s\r\n", (ast_test_flag(&peer->flags[1], SIP_PAGE2_Q850_REASON)) ? "Y" : "N"); astman_append(s, "Description: %s\r\n", peer->description); + astman_append(s, "DoNotDisturb: %s\r\n", peer->donotdisturb ? "Y" : "N"); + astman_append(s, "CallForward: %s\r\n", peer->callforward); + astman_append(s, "HuntGroup: %s\r\n", peer->huntgroup ? "Y" : "N"); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + astman_append(s, "Register: %s\r\n", alias->name); + } + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + astman_append(s, "Subscribe: %s@%s\r\n", subscription->exten, subscription->context); + } + astman_append(s, "CiscoDevicename: %s\r\n", peer->cisco_devicename); + astman_append(s, "CiscoLineIndex: %d\r\n", peer->cisco_lineindex); peer = sip_unref_peer(peer, "sip_show_peer: sip_unref_peer: done with peer"); @@ -21595,9 +22627,9 @@ ast_cli(a->fd, " Auto-Framing: %s \n", AST_CLI_YESNO(user->autoframing)); if (user->chanvars) { - ast_cli(a->fd, " Variables :\n"); + ast_cli(a->fd, " Variables : "); for (v = user->chanvars ; v ; v = v->next) - ast_cli(a->fd, " %s = %s\n", v->name, v->value); + ast_cli(a->fd, "%s%s = %s\n", v != user->chanvars ? " " : "", v->name, v->value); } ast_cli(a->fd, "\n"); @@ -22205,7 +23237,7 @@ if (cur->subscribed != NONE && arg->subscriptions) { struct ast_str *mailbox_str = ast_str_alloca(512); if (cur->subscribed == MWI_NOTIFICATION && cur->relatedpeer) - peer_mailboxes_to_str(&mailbox_str, cur->relatedpeer); + get_peer_mailboxes(&mailbox_str, cur->relatedpeer); ast_cli(arg->fd, FORMAT4, ast_sockaddr_stringify_addr(dst), S_OR(cur->username, S_OR(cur->cid_num, "(None)")), cur->callid, @@ -22679,7 +23711,7 @@ } } else { /* Type is application/dtmf, simply use what's in the message body */ - buf = get_content(req); + buf = get_content(req, 0, req->lines); } /* An empty message body requires us to send a 200 OK */ @@ -22907,6 +23939,8 @@ static char *sip_cli_notify(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ast_variable *varlist; + struct ast_channel *chan; + struct ast_str *str; int i; switch (cmd) { @@ -22936,10 +23970,21 @@ return CLI_FAILURE; } + if (!(chan = ast_dummy_channel_alloc())) { + ast_cli(a->fd, "Cannot allocate the channel for variables substitution\n"); + return CLI_FAILURE; + } + + if (!(str = ast_str_create(32))) { + ast_channel_release(chan); + ast_cli(a->fd, "Cannot allocate the string for variables substitution\n"); + return CLI_FAILURE; + } + for (i = 3; i < a->argc; i++) { struct sip_pvt *p; char buf[512]; - struct ast_variable *header, *var; + struct ast_variable *var, *header, *headers = NULL; if (!(p = sip_alloc(NULL, NULL, 0, SIP_NOTIFY, NULL, 0))) { ast_log(LOG_WARNING, "Unable to build sip pvt data for notify (memory/socket error)\n"); @@ -22958,36 +24003,214 @@ /* Notify is outgoing call */ ast_set_flag(&p->flags[0], SIP_OUTGOING); sip_notify_alloc(p); - p->notify->headers = header = ast_variable_new("Subscription-State", "terminated", ""); + + /* Recalculate our side, and recalculate Call ID */ + ast_sip_ouraddrfor(&p->sa, &p->ourip, p); + change_callid_pvt(p, NULL); + + /* Set the name of the peer being sent the notification so it can be used in ${} functions */ + pbx_builtin_setvar_helper(chan, "PEERNAME", p->peername); for (var = varlist; var; var = var->next) { ast_copy_string(buf, var->value, sizeof(buf)); ast_unescape_semicolon(buf); + ast_str_substitute_variables(&str, 0, chan, buf); if (!strcasecmp(var->name, "Content")) { if (ast_str_strlen(p->notify->content)) ast_str_append(&p->notify->content, 0, "\r\n"); - ast_str_append(&p->notify->content, 0, "%s", buf); + ast_str_append(&p->notify->content, 0, "%s", ast_str_buffer(str)); } else if (!strcasecmp(var->name, "Content-Length")) { ast_log(LOG_WARNING, "it is not necessary to specify Content-Length in sip_notify.conf, ignoring\n"); } else { - header->next = ast_variable_new(var->name, buf, ""); - header = header->next; + header = ast_variable_new(var->name, ast_str_buffer(str), ""); + if (headers) { + headers->next = header; + } else { + p->notify->headers = header; + } + headers = header; } } - /* Now that we have the peer's address, set our ip and change callid */ - ast_sip_ouraddrfor(&p->sa, &p->ourip, p); - build_via(p); - - change_callid_pvt(p, NULL); - ast_cli(a->fd, "Sending NOTIFY of type '%s' to '%s'\n", a->argv[2], a->argv[i]); sip_scheddestroy(p, SIP_TRANS_TIMEOUT); transmit_invite(p, SIP_NOTIFY, 0, 2, NULL); dialog_unref(p, "bump down the count of p since we're done with it."); } + ast_channel_release(chan); + ast_free(str); + + return CLI_SUCCESS; +} + +/*! \brief Enable/Disable DoNotDisturb on a peer */ +static char *sip_cli_donotdisturb(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int donotdisturb; + + switch (cmd) { + case CLI_INIT: + e->command = "sip donotdisturb {on|off}"; + e->usage = + "Usage: sip donotdisturb {on|off} \n" + " Enables/Disables do not disturb on a SIP peer\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_sip_peer(a->word, a->n, 0); + } + return NULL; + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(a->argv[2], "on")) { + donotdisturb = 1; + } else if (!strcasecmp(a->argv[2], "off")) { + donotdisturb = 0; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { + ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Do Not Disturb on '%s' %s\n", peer->name, donotdisturb ? "enabled" : "disabled"); + peer->donotdisturb = donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, donotdisturb ? "yes" : "no"); + } + sip_send_donotdisturb(peer); + peer = sip_unref_peer(peer, "unref after sip_find_peer"); + + return CLI_SUCCESS; +} + +/*! \brief Login to/Logout from huntgroup for a peer */ +static char *sip_cli_huntgroup(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct sip_peer *peer; + struct sip_alias *alias; + int huntgroup; + + switch (cmd) { + case CLI_INIT: + e->command = "sip huntgroup {on|off}"; + e->usage = + "Usage: sip huntgroup {on|off} \n" + " Login to/Logout from huntgroup for a SIP peer\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_sip_peer(a->word, a->n, 0); + } + return NULL; + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(a->argv[2], "on")) { + huntgroup = 1; + } else if (!strcasecmp(a->argv[2], "off")) { + huntgroup = 0; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { + ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); + return CLI_FAILURE; + } + + ast_cli(a->fd, "Hunt Group %s for '%s'\n", huntgroup ? "login" : "logout", peer->name); + peer->huntgroup = huntgroup; + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, huntgroup ? "yes" : "no"); + } + sip_send_huntgroup(peer); + peer = sip_unref_peer(peer, "unref after sip_find_peer"); + + return CLI_SUCCESS; +} + +/*! \brief Sets/Removes the call fowarding extension for a peer */ +static char *sip_cli_callforward(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) +{ + struct sip_peer *peer; + const char *callforward; + + switch (cmd) { + case CLI_INIT: + e->command = "sip callforward {on|off}"; + e->usage = + "Usage: sip callforward {on |off }\n" + " Sets/Clears the call forwarding extension for a SIP peer\n"; + return NULL; + case CLI_GENERATE: + if (a->pos == 3) { + return complete_sip_peer(a->word, a->n, 0); + } + return NULL; + } + + if (a->argc < 4) { + return CLI_SHOWUSAGE; + } + + if (!strcasecmp(a->argv[2], "on")) { + if (a->argc < 5) { + return CLI_SHOWUSAGE; + } + callforward = a->argv[4]; + } else if (!strcasecmp(a->argv[2], "off")) { + callforward = ""; + } else { + return CLI_SHOWUSAGE; + } + + if (!(peer = sip_find_peer(a->argv[3], NULL, TRUE, FINDPEERS, FALSE, 0))) { + ast_cli(a->fd, "No such peer '%s'\n", a->argv[3]); + return CLI_FAILURE; + } + + if (ast_strlen_zero(callforward)) { + ast_cli(a->fd, "Call forwarding on '%s' cleared\n", peer->name); + } else { + ast_cli(a->fd, "Call forwarding on '%s' set to %s\n", peer->name, callforward); + } + ast_string_field_set(peer, callforward, callforward); + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, callforward); + } + } + sip_send_callforward(peer); + peer = sip_unref_peer(peer, "unref after sip_find_peer"); + return CLI_SUCCESS; } @@ -23422,7 +24645,7 @@ }; /*! \brief ${SIPPEER()} Dialplan function - reads peer data */ -static int function_sippeer(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) +static int function_sippeer_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) { struct sip_peer *peer; char *colname; @@ -23476,7 +24699,7 @@ ast_copy_string(buf, peer->useragent, len); } else if (!strcasecmp(colname, "mailbox")) { struct ast_str *mailbox_str = ast_str_alloca(512); - peer_mailboxes_to_str(&mailbox_str, peer); + get_peer_mailboxes(&mailbox_str, peer); ast_copy_string(buf, ast_str_buffer(mailbox_str), len); } else if (!strcasecmp(colname, "context")) { ast_copy_string(buf, peer->context, len); @@ -23517,11 +24740,80 @@ } else { buf[0] = '\0'; } + } else if (!strncasecmp(colname, "donotdisturb", 12)) { + ast_copy_string(buf, peer->donotdisturb ? "yes" : "no", len); + } else if (!strncasecmp(colname, "callforward", 11)) { + ast_copy_string(buf, peer->callforward, len); + } else if (!strncasecmp(colname, "huntgroup", 9)) { + ast_copy_string(buf, peer->huntgroup ? "yes" : "no", len); + } else if (!strncasecmp(colname, "regcallid", 9)) { + ast_copy_string(buf, peer->regcallid, len); + } else if (!strncasecmp(colname, "ciscodevicename", 15)) { + ast_copy_string(buf, peer->cisco_devicename, len); + } else if (!strncasecmp(colname, "ciscolineindex", 14)) { + snprintf(buf, len, "%d", peer->cisco_lineindex); } else { buf[0] = '\0'; } - sip_unref_peer(peer, "sip_unref_peer from function_sippeer, just before return"); + sip_unref_peer(peer, "sip_unref_peer from function_sippeer_read, just before return"); + + return 0; +} + +static int function_sippeer_write(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + struct sip_peer *peer; + struct sip_alias *alias; + char *colname; + + if ((colname = strchr(data, ','))) { + *colname++ = '\0'; + } else { + colname = ""; + } + + if (!(peer = sip_find_peer(data, NULL, TRUE, FINDPEERS, FALSE, 0))) { + return -1; + } + + if (!strncasecmp(colname, "donotdisturb", 12)) { + peer->donotdisturb = ast_true(value); + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } + sip_send_donotdisturb(peer); + } else if (!strncasecmp(colname, "huntgroup", 9)) { + peer->huntgroup = ast_true(value); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); + } + sip_send_huntgroup(peer); + } else if (!strncasecmp(colname, "callforward", 11)) { + ast_string_field_set(peer, callforward, value); + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, peer->callforward); + } + } + sip_send_callforward(peer); + } + + sip_unref_peer(peer, "sip_unref_peer from function_sippeer_write, just before return"); return 0; } @@ -23529,7 +24821,8 @@ /*! \brief Structure to declare a dialplan function: SIPPEER */ static struct ast_custom_function sippeer_function = { .name = "SIPPEER", - .read = function_sippeer, + .read = function_sippeer_read, + .write = function_sippeer_write }; /*! \brief update redirecting information for a channel based on headers @@ -24391,6 +25684,20 @@ ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); xmitres = transmit_request(p, SIP_ACK, seqno, XMIT_UNRELIABLE, TRUE); sched_check_pendings(p); + + if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND) || ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND)) { + if (ast_test_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND)) { + ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_NEAREND); + start_record_thread(p->join_callid, p->join_tag, p->join_theirtag, 0); + } else { + ast_clear_flag(&p->flags[2], SIP_PAGE3_RELAY_FAREND); + } + ast_set_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); + transmit_invite(p, SIP_INVITE, FALSE, 1, NULL); + } else if (ast_test_flag(&p->flags[2], SIP_PAGE3_SDP_ACK)) { + ast_clear_flag(&p->flags[2], SIP_PAGE3_SDP_ACK); + ast_set_flag(&p->flags[2], SIP_PAGE3_CISCO_RECORDING); + } break; case 407: /* Proxy authentication */ @@ -24723,6 +26030,7 @@ return; switch (resp) { + case 200: /* Out of Dialog REFER */ case 202: /* Transfer accepted */ /* We need to do something here */ /* The transferee is now sending INVITE to target */ @@ -24974,6 +26282,8 @@ struct sip_peer *peer = /* sip_ref_peer( */ p->relatedpeer /* , "bump refcount on p, as it is being used in this function(handle_response_peerpoke)")*/ ; /* hope this is already refcounted! */ int statechanged, is_reachable, was_reachable; int pingtime = ast_tvdiff_ms(ast_tvnow(), peer->ps); + const char *status; + char lastms[20]; /* * Compute the response time to a ping (goes in peer->lastms.) @@ -24997,26 +26307,24 @@ is_reachable = pingtime <= peer->maxms; statechanged = peer->lastms == 0 /* yes, unknown before */ || was_reachable != is_reachable; + status = is_reachable ? "Reachable" : "Lagged"; + snprintf(lastms, sizeof(lastms), "%d", pingtime); peer->lastms = pingtime; peer->call = dialog_unref(peer->call, "unref dialog peer->call"); - if (statechanged) { - const char *s = is_reachable ? "Reachable" : "Lagged"; - char str_lastms[20]; - - snprintf(str_lastms, sizeof(str_lastms), "%d", pingtime); + if (statechanged) { ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", - peer->name, s, pingtime, peer->maxms); + peer->name, status, pingtime, peer->maxms); ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); if (sip_cfg.peer_rtupdate) { - ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", str_lastms, SENTINEL); + ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", peer->name, "lastms", lastms, SENTINEL); } if (peer->endpoint) { RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); ast_endpoint_set_state(peer->endpoint, AST_ENDPOINT_ONLINE); blob = ast_json_pack("{s: s, s: i}", - "peer_status", s, + "peer_status", status, "time", pingtime); ast_endpoint_blob_publish(peer->endpoint, ast_endpoint_state_type(), blob); } @@ -25026,6 +26334,38 @@ } } + if (!AST_LIST_EMPTY(&peer->aliases)) { + struct sip_alias *alias; + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (!alias->peer) { + continue; + } + alias->peer->lastms = pingtime; + + if (statechanged) { + ast_log(LOG_NOTICE, "Peer '%s' is now %s. (%dms / %dms)\n", + alias->peer->name, status, pingtime, alias->peer->maxms); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + if (sip_cfg.peer_rtupdate) { + ast_update_realtime(ast_check_realtime("sipregs") ? "sipregs" : "sippeers", "name", alias->peer->name, "lastms", lastms, SENTINEL); + } + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: i}", + "peer_status", status, + "time", pingtime); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + if (is_reachable && sip_cfg.regextenonqualify) { + register_peer_exten(alias->peer, TRUE); + } + } + } + } + pvt_set_needdestroy(p, "got OPTIONS response"); /* Try again eventually */ @@ -25558,6 +26898,9 @@ /* Wait for 487, then destroy */ } else if (sipmethod == SIP_BYE) { + if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { + parse_rtp_stats(p, req); + } pvt_set_needdestroy(p, "transaction completed"); } break; @@ -25656,6 +26999,2237 @@ return 0; } +/*! \brief Handle idivert request */ +static int handle_remotecc_idivert(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *targetcall_pvt; + struct ast_channel *chan, *bridged; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + sip_pvt_lock(targetcall_pvt); + + if (!(chan = targetcall_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + return -1; + } + + ast_channel_ref(chan); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + transmit_response(pvt, "202 Accepted", req); + + if (ast_channel_state(chan) == AST_STATE_RINGING) { + ast_queue_control(chan, AST_CONTROL_BUSY); + } else if (ast_channel_state(chan) == AST_STATE_UP) { + if ((bridged = ast_channel_bridge_peer(chan))) { + pbx_builtin_setvar_helper(bridged, "IDIVERT_PEERNAME", peer->name); + ast_async_goto(bridged, peer->context, "idivert", 1); + ast_channel_unref(bridged); + } + } + + ast_channel_unref(chan); + + return 0; +} + +/*! \brief Handle hlog request */ +static int handle_remotecc_hlog(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_alias *alias; + + transmit_response(pvt, "202 Accepted", req); + + peer->huntgroup = !peer->huntgroup; + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->huntgroup = peer->huntgroup; + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/HuntGroup", peer->name, peer->huntgroup ? "yes" : "no"); + } + sip_send_huntgroup(peer); + + return 0; +} + +/*! \brief destroy conference callback for ao2_alloc */ +static void destroy_conference(void *obj) +{ + struct sip_conference *conference = obj; + + ast_verb(3, "Destroying ad-hoc conference %d\n", conference->confid); + + if (conference->bridge) { + ast_bridge_destroy(conference->bridge, 0); + conference->bridge = NULL; + } + + AST_LIST_LOCK(&conferences); + AST_LIST_REMOVE(&conferences, conference, entry); + AST_LIST_UNLOCK(&conferences); +} + +/*! \brief create conference and assign it to a sip_pvt */ +static int create_conference(struct sip_pvt *pvt) +{ + struct sip_conference *conference; + + if (!(conference = ao2_alloc(sizeof(*conference), destroy_conference))) { + return -1; + } + + if (!(conference->bridge = ast_bridge_base_new(AST_BRIDGE_CAPABILITY_MULTIMIX, AST_BRIDGE_FLAG_TRANSFER_BRIDGE_ONLY, "SIP", "Conference", NULL))) { + ao2_ref(conference, -1); + return -1; + } + + ast_bridge_set_internal_sample_rate(conference->bridge, 8000); + ast_bridge_set_mixing_interval(conference->bridge, 20); + + ast_bridge_set_talker_src_video_mode(conference->bridge); + + conference->confid = ++next_confid; + conference->keep = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE) ? 1 : 0; + conference->multiadmin = ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE) ? 1 : 0; + AST_LIST_HEAD_INIT_NOLOCK(&conference->participants); + + pvt->conference = conference; + + AST_LIST_LOCK(&conferences); + AST_LIST_INSERT_TAIL(&conferences, conference, entry); + AST_LIST_UNLOCK(&conferences); + + ast_verb(3, "Creating ad-hoc conference %d\n", conference->confid); + + return 0; +} + +static int talk_detector(struct ast_bridge_channel *chan, void *hook_pvt, int talking) +{ + struct sip_participant *participant = hook_pvt; + struct sip_conference *conference = participant->conference; + + ast_debug(1, "%s %s talking in ad-hoc conference %d\n", ast_channel_name(participant->chan), talking ? "started" : "stopped", conference->confid); + participant->talking = talking; + + return 0; +} + +/*! \brief cleanup participant structure after leaving bridge */ +static int leave_conference(struct ast_bridge_channel *chan, void *hook_pvt) +{ + struct sip_participant *participant = hook_pvt; + struct sip_conference *conference = participant->conference; + + ast_verb(3, "%s left ad-hoc conference %d\n", ast_channel_name(participant->chan), conference->confid); + + ao2_lock(conference); + AST_LIST_REMOVE(&conference->participants, participant, entry); + if (participant->administrator) { + conference->administrators--; + } else { + conference->users--; + } + ao2_unlock(conference); + + if (conference->administrators + conference->users > 1) { + struct sip_participant *participant; + + AST_LIST_TRAVERSE(&conference->participants, participant, entry) { + ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-leave", NULL); + } + } + + if (conference->administrators + conference->users == 1) { + struct sip_participant *participant; + + ast_verb(3, "Only one participant left in ad-hoc conference %d, removing.\n", conference->confid); + + participant = AST_LIST_FIRST(&conference->participants); + ast_bridge_remove(conference->bridge, participant->chan); + } else if (conference->users && !conference->administrators && !conference->keep) { + struct sip_participant *participant; + + ast_verb(3, "No more administrators in ad-hoc conference %d\n", conference->confid); + + AST_LIST_TRAVERSE(&conference->participants, participant, entry) { + ast_bridge_remove(conference->bridge, participant->chan); + } + } + + ast_channel_unref(participant->chan); + ao2_ref(participant->conference, -1); + ast_free(participant); + + return -1; +} + +/*! \brief allocate participant structure and move channel into conference bridge */ +static int join_conference(struct sip_conference *conference, struct ast_channel *chan, int administrator) +{ + struct sip_participant *participant; + struct ast_bridge_channel *bridgechan; + + if (!administrator && conference->multiadmin) { + ast_channel_lock(chan); + if (IS_SIP_TECH(ast_channel_tech(chan))) { + struct sip_pvt *pvt = ast_channel_tech_pvt(chan); + + sip_pvt_lock(pvt); + if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE)) { + ao2_ref(conference, +1); + pvt->conference = conference; + administrator = 1; + } + sip_pvt_unlock(pvt); + } + ast_channel_unlock(chan); + } + + if (!(participant = ast_calloc(1, sizeof(*participant)))) { + return -1; + } + + ast_channel_ref(chan); + participant->chan = chan; + + ao2_ref(conference, +1); + participant->conference = conference; + + participant->callid = ++conference->next_callid; + participant->administrator = administrator; + + bridgechan = ast_channel_internal_bridge_channel(chan); + ao2_ref(bridgechan, +1); + + bridgechan->inhibit_colp = 1; + + if (ast_bridge_move(conference->bridge, ast_channel_internal_bridge(chan), chan, NULL, 0)) { + ao2_ref(bridgechan, -1); + ao2_ref(conference, -1); + ast_channel_unref(chan); + ast_free(participant); + return -1; + } + + ast_bridge_features_remove(bridgechan->features, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ast_bridge_leave_hook(bridgechan->features, leave_conference, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ast_bridge_talk_detector_hook(bridgechan->features, talk_detector, participant, NULL, AST_BRIDGE_HOOK_REMOVE_ON_PULL); + ao2_ref(bridgechan, -1); + + ao2_lock(conference); + AST_LIST_INSERT_HEAD(&conference->participants, participant, entry); + if (administrator) { + conference->administrators++; + } else { + conference->users++; + } + ao2_unlock(conference); + + if (conference->administrators + conference->users > 2) { + struct sip_participant *participant; + + AST_LIST_TRAVERSE(&conference->participants, participant, entry) { + ast_bridge_channel_queue_playfile(ast_channel_internal_bridge_channel(participant->chan), NULL, "confbridge-join", NULL); + } + } + + ast_verb(3, "%s joined ad-hoc conference %d\n", ast_channel_name(chan), conference->confid); + + if (administrator) { + struct ast_party_connected_line connected; + + ast_party_connected_line_init(&connected); + + ast_channel_lock(chan); + ast_party_connected_line_copy(&connected, ast_channel_connected(chan)); + ast_channel_unlock(chan); + + ast_free(connected.id.name.str); + connected.id.name.str = ast_strdup("Conference"); + connected.id.name.valid = 1; + + ast_free(connected.id.number.str); + connected.id.number.str = ast_strdup(""); + connected.id.number.valid = 1; + + ast_channel_update_connected_line(chan, &connected, NULL); + ast_party_connected_line_free(&connected); + } + + return 0; +} + +/*! \brief add channels to conference */ +static void *conference_thread(void *obj) +{ + struct conference_data *conference_data = obj; + struct sip_pvt *pvt; + struct ast_channel *chan, *bridged; + struct sip_conference *conference = NULL; + struct ast_str *content = ast_str_alloca(8192); + int res = -1; + + if (!(pvt = get_sip_pvt(conference_data->callid, conference_data->tag, conference_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto conference_cleanup; + } + + sip_pvt_lock(pvt); + + /* Is this a new ad-hoc conference? */ + if (!pvt->conference) { + if (create_conference(pvt)) { + ast_debug(1, "unable to create conference\n"); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + goto conference_cleanup; + } + + /* Increase ref on conference so we don't need to keep a ref on it's parent dialog */ + conference = pvt->conference; + ao2_ref(conference, +1); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + goto conference_cleanup; + } + + ast_channel_ref(chan); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + + if (!(bridged = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + goto conference_cleanup; + } + + if (join_conference(conference, chan, 1)) { + ast_channel_unref(chan); + ast_channel_unref(bridged); + goto conference_cleanup; + } + + ast_indicate(bridged, AST_CONTROL_UNHOLD); + + if (join_conference(conference, bridged, 0)) { + ast_channel_unref(chan); + ast_channel_unref(bridged); + goto conference_cleanup; + } + + ast_channel_unref(chan); + ast_channel_unref(bridged); + } else { + conference = pvt->conference; + ao2_ref(conference, +1); + + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + } + + if (!conference_data->joining) { + if (!(pvt = get_sip_pvt(conference_data->join_callid, conference_data->join_tag, conference_data->join_theirtag))) { + ast_debug(1, "join call leg does not exist\n"); + goto conference_cleanup; + } + + sip_pvt_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + goto conference_cleanup; + } + + ast_channel_ref(chan); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + + if (!(bridged = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + goto conference_cleanup; + } + + ast_indicate(bridged, AST_CONTROL_UNHOLD); + + if (join_conference(conference, bridged, 0)) { + ast_channel_unref(chan); + ast_channel_unref(bridged); + goto conference_cleanup; + } + + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(chan); + ast_channel_unref(bridged); + + res = 0; + + /* We need to signal to the phone to take the first call leg off hold, even though the generator on that + channel has gone due to the masquerade as the phone still thinks that it is on hold */ + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + goto conference_cleanup; + } + copy_pvt_data(pvt, conference_data->pvt); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", conference_data->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } else { + struct sip_selected *selected; + + AST_LIST_TRAVERSE(&conference_data->selected, selected, entry) { + /* Skip the join dialog as that was added to the conference above */ + if (!strcmp(conference_data->callid, selected->callid) && !strcmp(conference_data->tag, selected->tag) && !strcmp(conference_data->theirtag, selected->theirtag)) { + continue; + } + + if (!(pvt = get_sip_pvt(selected->callid, selected->tag, selected->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + continue; + } + + sip_pvt_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + goto conference_cleanup; + } + + ast_channel_ref(chan); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + + if (!(bridged = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridged channel\n"); + ast_channel_unref(chan); + goto conference_cleanup; + } + + ast_indicate(bridged, AST_CONTROL_UNHOLD); + + if (join_conference(conference, bridged, 0)) { + ast_channel_unref(chan); + ast_channel_unref(bridged); + goto conference_cleanup; + } + + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + + ast_channel_unref(chan); + ast_channel_unref(bridged); + } + + res = 0; + } + +conference_cleanup: + if (conference) { + ao2_ref(conference, -1); + } + + ast_str_reset(content); + + if (!conference_data->joining) { + struct sip_request req; + + reqprep(&req, conference_data->pvt, SIP_NOTIFY, 0, 1); + add_header(&req, "Event", "refer"); + add_header(&req, "Subscription-State", "terminated;reason=noresource"); + add_header(&req, "Content-Type", "application/x-cisco-remotecc-response+xml"); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", res ? 500 : 200); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + add_content(&req, ast_str_buffer(content)); + send_request(conference_data->pvt, &req, XMIT_RELIABLE, conference_data->pvt->ocseq); + } else { + if ((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + copy_pvt_data(pvt, conference_data->pvt); + + if (res) { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", conference_data->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\200S\n"); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + } else { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", conference_data->callid); + ast_str_append(&content, 0, "%s\n", conference_data->theirtag); + ast_str_append(&content, 0, "%s\n", conference_data->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Join\n"); + ast_str_append(&content, 0, "Complete\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + } + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } + } + + if (conference_data->joining) { + struct sip_selected *selected; + + while ((selected = AST_LIST_REMOVE_HEAD(&conference_data->selected, entry))) { + destroy_selected(selected); + } + } + + dialog_unref(conference_data->pvt, "drop conference_data->pvt"); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + + return NULL; +} + +/*! \brief Handle conference request */ +static int handle_remotecc_conference(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + pthread_t threadid; + char tmp[64]; + struct sip_request notify_req; + struct conference_data *conference_data; + struct sip_selected *selected; + + if (!(conference_data = ast_calloc_with_stringfields(1, struct conference_data, 128))) { + return -1; + } + + dialog_ref(pvt, "copying dialog pvt into conference_data struct"); + conference_data->pvt = pvt; + conference_data->joining = !strcmp(remotecc_data->softkeyevent, "Join"); + + ast_string_field_set(conference_data, callid, remotecc_data->dialogid.callid); + ast_string_field_set(conference_data, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(conference_data, theirtag, remotecc_data->dialogid.localtag); + + if (!conference_data->joining) { + ast_string_field_set(conference_data, join_callid, remotecc_data->consultdialogid.callid); + ast_string_field_set(conference_data, join_tag, remotecc_data->consultdialogid.remotetag); + ast_string_field_set(conference_data, join_theirtag, remotecc_data->consultdialogid.localtag); + } else { + ao2_lock(peer); + while ((selected = AST_LIST_REMOVE_HEAD(&peer->selected, entry))) { + AST_LIST_INSERT_TAIL(&conference_data->selected, selected, entry); + } + ao2_unlock(peer); + } + + if (ast_pthread_create_detached_background(&threadid, NULL, conference_thread, conference_data)) { + dialog_unref(conference_data->pvt, "thread creation failed"); + ast_string_field_free_memory(conference_data); + ast_free(conference_data); + return -1; + } + + /* If the conference fails we send back a NOTIFY telling the phone */ + transmit_response(pvt, "202 Accepted", req); + + if (!conference_data->joining) { + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + + pvt->subscribed = REMOTECC_XML; + pvt->expiry = min_expiry; + + copy_request(&pvt->initreq, req); + initreqprep(¬ify_req, pvt, SIP_NOTIFY, NULL); + add_header(¬ify_req, "Event", "refer"); + snprintf(tmp, sizeof(tmp), "active;expires=%d", pvt->expiry); + add_header(¬ify_req, "Subscription-State", tmp); + send_request(pvt, ¬ify_req, XMIT_RELIABLE, pvt->ocseq); + } + + return 0; +} + +/*! \brief Handle conflist and confdetails requests */ +static int handle_remotecc_conflist(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *refer_pvt; + struct sip_conference *conference = NULL; + struct sip_participant *participant; + struct ast_str *content; + int needpos = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + struct sip_pvt *targetcall_pvt; + + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + sip_pvt_lock(targetcall_pvt); + + if ((conference = targetcall_pvt->conference)) { + ao2_ref(conference, +1); + } + + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop pvt"); + } else if (remotecc_data->confid) { + AST_LIST_LOCK(&conferences); + AST_LIST_TRAVERSE(&conferences, conference, entry) { + if (conference->confid == remotecc_data->confid) { + ao2_ref(conference, +1); + break; + } + } + AST_LIST_UNLOCK(&conferences); + } + + if (!conference) { + ast_debug(1, "Unable to find conference\n"); + return -1; + } + + if (!ast_strlen_zero(remotecc_data->usercalldata) && strcmp(remotecc_data->usercalldata, "Update")) { + char softkey[16]; + int callid; + + if (!strcmp(remotecc_data->usercalldata, "Remove") || !strcmp(remotecc_data->usercalldata, "Mute")) { + ast_string_field_set(peer, cisco_softkey, remotecc_data->usercalldata); + ao2_ref(conference, -1); + transmit_response(pvt, "202 Accepted", req); + return 0; + } + + /* Default action is to mute/unmute */ + ast_copy_string(softkey, S_OR(peer->cisco_softkey, "Mute"), sizeof(softkey)); + ast_string_field_set(peer, cisco_softkey, ""); + callid = atoi(remotecc_data->usercalldata); + + ao2_lock(conference); + AST_LIST_TRAVERSE(&conference->participants, participant, entry) { + if (participant->callid == callid) { + if (!strcmp(softkey, "Remove")) { + ast_verb(3, "%s is being removed from ad-hoc conference %d\n", + ast_channel_name(participant->chan), conference->confid); + + ast_bridge_remove(conference->bridge, participant->chan); + participant->removed = 1; + } else if (!strcmp(softkey, "Mute")) { + ast_verb(3, "%s is being %s in ad-hoc conference %d\n", + ast_channel_name(participant->chan), participant->muted ? "unmuted" : "muted", conference->confid); + participant->muted = !participant->muted; + + ast_channel_lock(participant->chan); + ast_channel_internal_bridge_channel(participant->chan)->features->mute = participant->muted; + ast_channel_unlock(participant->chan); + } + break; + } + } + ao2_unlock(conference); + } + + transmit_response(pvt, "202 Accepted", req); + + if (!(content = ast_str_create(8192))) { + ao2_ref(conference, -1); + return 0; + } + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + ao2_ref(conference, -1); + ast_free(content); + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", REMOTECC_CONFLIST); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "%d\n", conference->confid); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Conference\n"); + + ao2_lock(conference); + AST_LIST_TRAVERSE(&conference->participants, participant, entry) { + char *status, *callerid = NULL; + + if (participant->removed) { + continue; + } + + if (participant->muted) { + status = "- "; + } else if (participant->talking) { + status = "+ "; + } else { + status = ""; + } + + ast_channel_lock(participant->chan); + if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.name.valid) { + callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.name.str); + } + if (ast_strlen_zero(callerid) && ast_channel_caller(participant->chan)->id.number.valid) { + callerid = ast_strdupa(ast_channel_caller(participant->chan)->id.number.str); + } + ast_channel_unlock(participant->chan); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s%s\n", status, S_OR(callerid, CALLERID_UNKNOWN)); + ast_str_append(&content, 0, "UserCallData:%d:0:%d:0:%d\n", REMOTECC_CONFLIST, conference->confid, participant->callid); + ast_str_append(&content, 0, "\n"); + + ast_channel_unlock(participant->chan); + } + ao2_unlock(conference); + + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Remove\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 1 : 2); + ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Remove\n", REMOTECC_CONFLIST, conference->confid); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Mute\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 2 : 3); + ast_str_append(&content, 0, "UserCallDataSoftKey:Select:%d:0:%d:0:Mute\n", REMOTECC_CONFLIST, conference->confid); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Update\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 4 : 4); + ast_str_append(&content, 0, "UserCallDataSoftKey:Update:%d:0:%d:0:Update\n", REMOTECC_CONFLIST, conference->confid); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + ao2_ref(conference, -1); + ast_free(content); + + return 0; +} + +/*! \brief Handle remove last conference participant requests */ +static int handle_remotecc_rmlastconf(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *targetcall_pvt; + struct sip_conference *conference = NULL; + struct sip_participant *participant; + + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + sip_pvt_lock(targetcall_pvt); + + if (targetcall_pvt->conference) { + conference = targetcall_pvt->conference; + ao2_ref(conference, +1); + } + + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + if (!conference) { + ast_debug(1, "Not in a conference\n"); + return -1; + } + + transmit_response(pvt, "202 Accepted", req); + + ao2_lock(conference); + if ((participant = AST_LIST_FIRST(&conference->participants))) { + ast_bridge_remove(conference->bridge, participant->chan); + } + ao2_unlock(conference); + ao2_ref(conference, -1); + + return 0; +} + +static void remotecc_park_notify(struct park_data *park_data, enum ast_parked_call_event_type event_type, int parkingspace, long unsigned int timeout) +{ + struct ast_str *content = ast_str_alloca(8192); + + if (park_data->monitor) { + if (event_type == PARKED_CALL_FAILED) { + struct sip_pvt *pvt; + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return; + } + copy_pvt_data(pvt, park_data->pvt); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", park_data->callid); + ast_str_append(&content, 0, "%s\n", park_data->theirtag); + ast_str_append(&content, 0, "%s\n", park_data->tag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\200G\n"); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "5\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } else { + struct sip_request req; + const char *fromdomain = S_OR(park_data->pvt->fromdomain, ast_sockaddr_stringify_host_remote(&park_data->pvt->ourip)); + char *parkevent; + + if (event_type == PARKED_CALL) { + parkevent = "parked"; + } else if (event_type == PARKED_CALL_REMINDER) { + parkevent = "reminder"; + } else if (event_type == PARKED_CALL_UNPARKED) { + parkevent = "retrieved"; + } else if (event_type == PARKED_CALL_TIMEOUT) { + parkevent = "forwarded"; + } else if (event_type == PARKED_CALL_GIVEUP) { + parkevent = "abandoned"; + } else { + return; + } + + if (!ast_test_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED)) { + ast_set_flag(&park_data->pvt->flags[0], SIP_OUTGOING); + ast_set_flag(&park_data->pvt->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + + park_data->pvt->subscribed = DIALOG_INFO_XML; + park_data->pvt->expiry = timeout; + + initreqprep(&req, park_data->pvt, SIP_NOTIFY, NULL); + } else { + reqprep(&req, park_data->pvt, SIP_NOTIFY, 0, 1); + } + park_data->pvt->dialogver++; + + add_header(&req, "Event", "refer"); + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + char tmp[64]; + + snprintf(tmp, sizeof(tmp), "active;expires=%d", park_data->pvt->expiry); + add_header(&req, "Subscription-State", tmp); + } else { + add_header(&req, "Subscription-State", "terminated;reason=noresource"); + } + add_header(&req, "Content-Type", "application/dialog-info+xml"); + + /* "parmams" is a typo in the the Cisco API, duh. */ + ast_str_append(&content, 0, "\n", park_data->pvt->dialogver, parkingspace, fromdomain); + ast_str_append(&content, 0, "\n", parkingspace); + if (event_type == PARKED_CALL || event_type == PARKED_CALL_REMINDER) { + ast_str_append(&content, 0, "confirmed\n"); + } else { + ast_str_append(&content, 0, "terminated\n"); + } + ast_str_append(&content, 0, "%s\n", parkevent); + ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); + ast_str_append(&content, 0, "sip:%d@%s\n", parkingspace, fromdomain); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + add_content(&req, ast_str_buffer(content)); + send_request(park_data->pvt, &req, XMIT_RELIABLE, park_data->pvt->ocseq++); + } + } else { + struct sip_pvt *pvt; + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return; + } + copy_pvt_data(pvt, park_data->pvt); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", park_data->callid); + ast_str_append(&content, 0, "%s\n", park_data->theirtag); + ast_str_append(&content, 0, "%s\n", park_data->tag); + ast_str_append(&content, 0, "\n"); + if (event_type == PARKED_CALL) { + ast_str_append(&content, 0, "\200! %d\n", parkingspace); + } else { + ast_str_append(&content, 0, "\200^\n"); + } + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "5\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + } +} + +static void park_stasis_cb(void *data, struct stasis_subscription *sub, struct stasis_message *message) +{ + struct park_data *park_data = data; + struct ast_parked_call_payload *payload; + + if (stasis_message_type(message) != ast_parked_call_type()) { + return; + } + + if (!stasis_subscription_final_message(sub, message)) { + payload = stasis_message_data(message); + + if (strcmp(park_data->uniqueid, payload->parkee->uniqueid)) { + return; + } + + if (payload->event_type == PARKED_CALL) { + ast_softhangup(park_data->chan, AST_SOFTHANGUP_EXPLICIT); + ast_channel_unref(park_data->chan); + park_data->chan = NULL; + } + + remotecc_park_notify(park_data, payload->event_type, payload->parkingspace, payload->timeout); + + if (park_data->monitor && (payload->event_type == PARKED_CALL || payload->event_type == PARKED_CALL_REMINDER)) { + return; + } + } + + stasis_unsubscribe(sub); + ast_channel_cleanup(park_data->chan); + + dialog_unref(park_data->pvt, "drop park_data->pvt"); + ast_string_field_free_memory(park_data); + ast_free(park_data); +} + +/*! \brief park call */ +static void *park_thread(void *obj) +{ + struct park_data *park_data = obj; + struct sip_pvt *pvt; + struct ast_channel *chan, *bridged; + struct stasis_subscription *sub = NULL; + struct ast_exten *exten; + struct pbx_find_info find_info = { .stacklen = 0 }; + int res = -1; + + if (!(pvt = get_sip_pvt(park_data->callid, park_data->tag, park_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto park_cleanup; + } + + sip_pvt_lock(pvt); + + if (!(chan = pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + goto park_cleanup; + } + + ast_channel_ref(chan); + sip_pvt_unlock(pvt); + dialog_unref(pvt, "drop pvt"); + + if (!(bridged = ast_channel_bridge_peer(chan))) { + ast_debug(1, "no bridge channel"); + ast_channel_unref(chan); + goto park_cleanup; + } + + /* needed so that comebacktoorigin will work */ + pbx_builtin_setvar_helper(bridged, "BLINDTRANSFER", ast_channel_name(chan)); + pbx_builtin_setvar_helper(bridged, "PARKINGLOT", ast_channel_parkinglot(chan)); + + park_data->chan = chan; + ast_channel_ref(chan); + ast_string_field_set(park_data, uniqueid, ast_channel_uniqueid(bridged)); + + sub = stasis_subscribe(ast_parking_topic(), park_stasis_cb, park_data); + + exten = pbx_find_extension(NULL, NULL, &find_info, park_data->context, "park", 1, NULL, NULL, E_MATCH); + ast_bridge_channel_write_park(ast_channel_internal_bridge_channel(chan), + ast_channel_uniqueid(bridged), ast_channel_uniqueid(chan), exten ? ast_get_extension_app_data(exten) : NULL); + + ast_channel_unref(chan); + ast_channel_unref(bridged); + + transmit_response(park_data->pvt, "202 Accepted", &park_data->pvt->initreq); + res = 0; + +park_cleanup: + if (res) { + transmit_response(park_data->pvt, "503 Service Unavailable", &park_data->pvt->initreq); + remotecc_park_notify(park_data, PARKED_CALL_FAILED, 0, 0); + + if (sub) { + stasis_unsubscribe(sub); + } + if (park_data->chan) { + ast_channel_unref(park_data->chan); + } + dialog_unref(park_data->pvt, "drop park_data->pvt"); + ast_string_field_free_memory(park_data); + ast_free(park_data); + } + + return NULL; +} + +/*! \brief Handle remotecc park requests */ +static int handle_remotecc_park(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + pthread_t threadid; + struct park_data *park_data; + + if (!(park_data = ast_calloc_with_stringfields(1, struct park_data, 128))) { + return -1; + } + + dialog_ref(pvt, "copying dialog pvt into park_data struct"); + park_data->pvt = pvt; + copy_request(&pvt->initreq, req); + park_data->monitor = !strcmp(remotecc_data->softkeyevent, "ParkMonitor"); + + ast_string_field_set(park_data, context, peer->context); + ast_string_field_set(park_data, callid, remotecc_data->dialogid.callid); + ast_string_field_set(park_data, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(park_data, theirtag, remotecc_data->dialogid.localtag); + + if (ast_pthread_create_detached_background(&threadid, NULL, park_thread, park_data)) { + dialog_unref(park_data->pvt, "thread creation failed"); + ast_string_field_free_memory(park_data); + ast_free(park_data); + return -1; + } + + return 0; +} + +/*! \brief Handle remotecc parkmonitor requests */ +static int handle_remotecc_parkmonitor(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + return handle_remotecc_park(pvt, req, peer, remotecc_data); +} + +static int wait_for_recording(void *obj) +{ + struct sip_pvt *pvt = obj; + + sip_pvt_lock(pvt); + if (ast_channel_state(pvt->owner) != AST_STATE_UP) { + sip_pvt_unlock(pvt); + return -1; + } + + if (!ast_test_flag(&pvt->flags[2], SIP_PAGE3_CISCO_RECORDING)) { + sip_pvt_unlock(pvt); + return -1; + } + + sip_pvt_unlock(pvt); + return 0; +} + +static void *record_thread(void *obj) +{ + struct record_data *record_data = obj; + struct sip_pvt *pvt, *targetcall_pvt; + struct ast_channel *chan; + struct ast_format_cap *cap; + struct ast_party_connected_line connected; + char *peername, *uniqueid, *channame; + int cause; + + if (!(targetcall_pvt = get_sip_pvt(record_data->callid, record_data->tag, record_data->theirtag))) { + ast_debug(1, "call leg does not exist\n"); + goto record_cleanup; + } + + sip_pvt_lock(targetcall_pvt); + + if (!(chan = targetcall_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + goto record_cleanup; + } + + peername = ast_strdupa(targetcall_pvt->peername); + channame = ast_strdupa(ast_channel_name(chan)); + uniqueid = ast_strdupa(ast_channel_uniqueid(chan)); + + cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + ast_format_cap_append(cap, ast_channel_readformat(chan), 0); + + sip_pvt_unlock(targetcall_pvt); + + if (!(chan = ast_request("SIP", cap, NULL, NULL, peername, &cause))) { + ast_debug(1, "unable to request channel\n"); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + goto record_cleanup; + } + + ao2_ref(cap, -1); + pvt = ast_channel_tech_pvt(chan); + + ast_string_field_set(pvt, join_callid, record_data->callid); + ast_string_field_set(pvt, join_tag, record_data->tag); + ast_string_field_set(pvt, join_theirtag, record_data->theirtag); + + ast_set_flag(&pvt->flags[1], SIP_PAGE2_CALL_ONHOLD_INACTIVE); + ast_set_flag(&pvt->flags[2], record_data->outgoing ? SIP_PAGE3_RELAY_NEAREND : SIP_PAGE3_RELAY_FAREND); + + /* We are abusing the onhold flags to set the inactive attribute in the SDP, bump the onhold counter + because when recording starts the reinvite code will decrement onhold when those flags are cleared */ + if (pvt->relatedpeer) { + ast_atomic_fetchadd_int(&pvt->relatedpeer->onhold, +1); + } + + ast_party_connected_line_set_init(&connected, ast_channel_connected(chan)); + connected.id.name.valid = 1; + connected.id.name.str = "Record"; + ast_channel_set_connected_line(chan, &connected, NULL); + + ast_channel_context_set(chan, pvt->context); + ast_channel_exten_set(chan, "record"); + ast_channel_priority_set(chan, 1); + + pbx_builtin_setvar_helper(chan, "RECORD_PEERNAME", peername); + pbx_builtin_setvar_helper(chan, "RECORD_UNIQUEID", uniqueid); + pbx_builtin_setvar_helper(chan, "RECORD_CHANNEL", channame); + pbx_builtin_setvar_helper(chan, "RECORD_DIRECTION", record_data->outgoing ? "out" : "in"); + + if (ast_call(chan, peername, 5000)) { + ast_debug(1, "unable to call\n"); + ast_hangup(chan); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + goto record_cleanup; + } + + if (ast_safe_sleep_conditional(chan, 5000, wait_for_recording, pvt)) { + ast_debug(1, "no answer\n"); + ast_hangup(chan); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + goto record_cleanup; + } + + sip_pvt_lock(targetcall_pvt); + + if (record_data->outgoing) { + targetcall_pvt->recordoutpvt = dialog_ref(pvt, "copying pvt into recordoutpvt"); + } else { + targetcall_pvt->recordinpvt = dialog_ref(pvt, "copying pvt into recordinpvt"); + } + + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + if (!ast_check_hangup(chan)) { + ast_pbx_run(chan); + } + +record_cleanup: + ast_string_field_free_memory(record_data); + ast_free(record_data); + + return NULL; +} + +static void start_record_thread(const char *callid, const char *tag, const char *theirtag, int outgoing) +{ + pthread_t threadid; + struct record_data *record_data; + + if (!(record_data = ast_calloc_with_stringfields(1, struct record_data, 128))) { + return; + } + + ast_string_field_set(record_data, callid, callid); + ast_string_field_set(record_data, tag, tag); + ast_string_field_set(record_data, theirtag, theirtag); + record_data->outgoing = outgoing; + + if (ast_pthread_create_detached_background(&threadid, NULL, record_thread, record_data)) { + ast_string_field_free_memory(record_data); + ast_free(record_data); + } +} + +/*! \brief Handle remotecc start recording requests */ +static int handle_remotecc_startrecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + transmit_response(pvt, "202 Accepted", req); + start_record_thread(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag, 1); + + return 0; +} + +/*! \brief Handle remotecc stop recording requests */ +static int handle_remotecc_stoprecording(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *targetcall_pvt; + struct ast_channel *chan; + + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + transmit_response(pvt, "202 Accepted", req); + + if (targetcall_pvt->recordoutpvt) { + sip_pvt_lock(targetcall_pvt->recordoutpvt); + if ((chan = targetcall_pvt->recordoutpvt->owner)) { + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + } + sip_pvt_unlock(targetcall_pvt->recordoutpvt); + + sip_pvt_lock(targetcall_pvt); + targetcall_pvt->recordoutpvt = dialog_unref(targetcall_pvt->recordoutpvt, "drop recordoutpvt"); + sip_pvt_unlock(targetcall_pvt); + } + + if (targetcall_pvt->recordinpvt) { + sip_pvt_lock(targetcall_pvt->recordinpvt); + if ((chan = targetcall_pvt->recordinpvt->owner)) { + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + } + sip_pvt_unlock(targetcall_pvt->recordinpvt); + + sip_pvt_lock(targetcall_pvt); + targetcall_pvt->recordinpvt = dialog_unref(targetcall_pvt->recordinpvt, "drop recordinpvt"); + sip_pvt_unlock(targetcall_pvt); + } + + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + return 0; +} + +static void parse_rtp_stats(struct sip_pvt *pvt, struct sip_request *req) +{ + char *rxstat, *txstat; + int dur = 0, rxpkt = 0, rxoct = 0, txpkt = 0, txoct = 0, latepkt = 0, lostpkt = 0, avgjit = 0; + struct sip_peer *peer; + + rxstat = ast_strdupa(sip_get_header(req, "RTP-RxStat")); + while (!ast_strlen_zero(rxstat)) { + char *tag, *value; + + tag = strsep(&rxstat, "="); + if (!(value = strsep(&rxstat, ","))) { + break; + } + + if (!strcasecmp(tag, "Dur")) { + dur = atoi(value); + } else if (!strcasecmp(tag, "Pkt")) { + rxpkt = atoi(value); + } else if (!strcasecmp(tag, "Oct")) { + rxoct = atoi(value); + } else if (!strcasecmp(tag, "LatePkt")) { + latepkt = atoi(value); + } else if (!strcasecmp(tag, "LostPkt")) { + lostpkt = atoi(value); + } else if (!strcasecmp(tag, "AvgJit")) { + avgjit = atoi(value); + } + } + + txstat = ast_strdupa(sip_get_header(req, "RTP-TxStat")); + while (!ast_strlen_zero(txstat)) { + char *tag, *value; + + tag = strsep(&txstat, "="); + if (!(value = strsep(&txstat, ","))) { + break; + } + + if (!strcasecmp(tag, "Pkt")) { + txpkt = atoi(value); + } else if (!strcasecmp(tag, "Oct")) { + txoct = atoi(value); + } + } + + ast_verb(3, "Call Quality Report for %s\n" + " Duration : %d\n" + " Sent Packets : %d\n" + " Sent Bytes : %d\n" + " Received Packets: %d\n" + " Received Bytes : %d\n" + " Late Packets : %d\n" + " Lost Packets : %d\n" + " Average Jitter : %d\n", + pvt->peername, dur, txpkt, txoct, rxpkt, rxoct, latepkt, lostpkt, avgjit); + + if ((peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { + send_qrt_url(peer); + sip_unref_peer(peer, "unref after sip_find_peer"); + } +} + +static void send_qrt_url(struct sip_peer *peer) +{ + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + struct ast_str *url = ast_str_alloca(1024); + + if (ast_strlen_zero(peer->cisco_qrt_url)) { + return; + } + + ast_str_set(&url, 0, "%s", peer->cisco_qrt_url); + ast_str_append(&url, 0, "%sname=%s", strchr(ast_str_buffer(url), '?') ? "&" : "?", peer->cisco_devicename); + + if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { + return; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed"); + return; + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", ast_str_buffer(url)); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); +} + +/*! \brief Handle remotecc quality reporting tool requests */ +static int handle_remotecc_qrt(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *targetcall_pvt, *refer_pvt; + struct ast_str *content = ast_str_alloca(8192); + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + ast_set_flag(&targetcall_pvt->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + transmit_response(pvt, "202 Accepted", req); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Quality Reporting Tool is active\n"); + ast_str_append(&content, 0, "5\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + } else { + transmit_response(pvt, "202 Accepted", req); + send_qrt_url(peer); + } + + return 0; +} + +/*! \brief Handle remotecc malicious call requests */ +static int handle_remotecc_mcid(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *targetcall_pvt, *refer_pvt; + struct ast_channel *chan, *bridged; + struct ast_str *content = ast_str_alloca(8192); + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + sip_pvt_lock(targetcall_pvt); + + if (!(chan = targetcall_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + return -1; + } + + ast_channel_ref(chan); + + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + transmit_response(pvt, "202 Accepted", req); + + if ((bridged = ast_channel_bridge_peer(chan))) { + ast_queue_control(chan, AST_CONTROL_MCID); + ast_verb(3, "%s has a malicious call from '%s'\n", targetcall_pvt->peername, ast_channel_name(bridged)); + ast_channel_unref(bridged); + } + + ast_channel_unref(chan); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\200T\n"); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.callid); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.localtag); + ast_str_append(&content, 0, "%s\n", remotecc_data->dialogid.remotetag); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + return 0; +} + +static int remotecc_callback_notify(const char *context, const char *exten, struct ast_state_cb_info *info, void *data) +{ + struct sip_peer *peer = data; + struct sip_pvt *pvt; + struct ast_str *content = ast_str_alloca(8192); + int needpos = strstr(peer->useragent, "CP79") ? 1 : 0; + struct timeval tv; + struct ast_tm tm; + char date[32]; + + if (info->exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || info->presence_state == AST_PRESENCE_DND) { + peer->callback->busy = 1; + return 0; + } else if (info->exten_state != AST_EXTENSION_NOT_INUSE || info->presence_state != AST_PRESENCE_AVAILABLE || !peer->callback->busy) { + return 0; + } + + tv = ast_tvnow(); + ast_strftime(date, sizeof(date), "%X %x", ast_localtime(&tv, &tm, NULL)); + + if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { + return 0; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed"); + return 0; + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + + ast_str_reset(content); + + if (!((pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0)))) { + return 0; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed"); + return 0; + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + ast_str_append(&content, 0, "%s is now available at %s.\n\nPress Dial to call.\nPress Cancel to deactivate.\nPress Exit to quit this screen.\n", peer->callback->exten, date); + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Cancel\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 2 : 2); + ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Dial\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 1 : 3); + ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Dial\n", REMOTECC_CALLBACK); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + + return 0; +} + +/*! \brief Handle remotecc callback requests */ +static int handle_remotecc_callback(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_pvt *refer_pvt; + struct ast_str *content = ast_str_alloca(8192); + int needpos = strstr(sip_get_header(req, "User-Agent"), "CP79") ? 1 : 0; + + if (!ast_strlen_zero(remotecc_data->dialogid.callid)) { + struct sip_pvt *targetcall_pvt; + struct ast_channel *chan = NULL; + char *exten, *subtype = NULL, *message = NULL; + int exten_state, presence_state; + int res = -1; + + /* The remotetag and localtag are swapped here because those are from the viewpoint of the phone */ + if (!(targetcall_pvt = get_sip_pvt(remotecc_data->dialogid.callid, remotecc_data->dialogid.remotetag, remotecc_data->dialogid.localtag))) { + ast_debug(1, "dialogid call leg does not exist\n"); + return -1; + } + + sip_pvt_lock(targetcall_pvt); + + if (!(chan = targetcall_pvt->owner)) { + ast_debug(1, "no owner channel\n"); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + return -1; + } + + ast_channel_ref(chan); + sip_pvt_unlock(targetcall_pvt); + dialog_unref(targetcall_pvt, "drop targetcall_pvt"); + + ast_channel_lock(chan); + exten = ast_strdupa(S_COR(ast_channel_connected(chan)->id.number.valid, ast_channel_connected(chan)->id.number.str, targetcall_pvt->exten)); + ast_channel_unlock(chan); + ast_channel_unref(chan); + + if (peer->callback) { + destroy_callback(peer); + peer->callback = NULL; + } + + if (ast_strlen_zero(exten)) { + goto callback_cleanup; + } + if (!(peer->callback = ast_calloc(1, sizeof(*peer->callback)))) { + goto callback_cleanup; + } + if (!(peer->callback->exten = ast_strdup(exten))) { + ast_free(peer->callback); + peer->callback = NULL; + goto callback_cleanup; + } + sip_ref_peer(peer, "copying peer into callback struct"); + if (!(peer->callback->stateid = ast_extension_state_add(peer->context, peer->callback->exten, remotecc_callback_notify, peer))) { + sip_unref_peer(peer, "copying peer into callback struct failed"); + ast_free(peer->callback->exten); + ast_free(peer->callback); + goto callback_cleanup; + } + + exten_state = ast_extension_state(NULL, peer->context, peer->callback->exten); + presence_state = ast_hint_presence_state(NULL, peer->context, peer->callback->exten, &subtype, &message); + + ast_free(subtype); + ast_free(message); + + if (exten_state & (AST_EXTENSION_INUSE | AST_EXTENSION_BUSY) || presence_state == AST_PRESENCE_DND) { + peer->callback->busy = 1; + } + + ast_channel_hangupcause_set(chan, AST_CAUSE_FAILURE); + ast_softhangup(chan, AST_SOFTHANGUP_EXPLICIT); + res = 0; + + callback_cleanup: + if (res) { + transmit_response(pvt, "202 Accepted", req); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + ast_str_append(&content, 0, "Unable to activate callback on %s\n", S_OR(exten, CALLERID_UNKNOWN)); + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + return 0; + } + } else if (!ast_strlen_zero(remotecc_data->usercalldata) && peer->callback) { + transmit_response(pvt, "202 Accepted", req); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + if (!strcmp(remotecc_data->usercalldata, "Dial")) { + ast_str_reset(content); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->callback->exten); + ast_str_append(&content, 0, "%d\n", peer->cisco_lineindex); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + transmit_refer_with_content(refer_pvt, "application/x-cisco-remotecc-request+xml", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + destroy_callback(peer); + peer->callback = NULL; + } else if (!strcmp(remotecc_data->usercalldata, "Cancel")) { + destroy_callback(peer); + peer->callback = NULL; + } + + return 0; + } + + transmit_response(pvt, "202 Accepted", req); + + if (!(refer_pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return 0; + } + copy_pvt_data(refer_pvt, pvt); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%d\n", REMOTECC_CALLBACK); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "CallBack\n"); + + if (peer->callback) { + ast_str_append(&content, 0, "CallBack is activated on %s.\n\nPress Cancel to deactivate.\nPress Exit to quit this screen.", peer->callback->exten); + } else { + ast_str_append(&content, 0, "CallBack is not activated."); + } + + ast_str_append(&content, 0, "Please select\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Exit\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 3 : 1); + ast_str_append(&content, 0, "SoftKey:Exit\n"); + ast_str_append(&content, 0, "\n"); + + if (peer->callback) { + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "Cancel\n"); + ast_str_append(&content, 0, "%d\n", needpos ? 2 : 2); + ast_str_append(&content, 0, "UserCallData:%d:0:0:0:Cancel\n", REMOTECC_CALLBACK); + ast_str_append(&content, 0, "\n"); + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(refer_pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(refer_pvt, "bump down the count of refer_pvt since we're done with it."); + + return 0; +} + +/*! \brief Handle remotecc select requests */ +static int handle_remotecc_select(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_selected *selected; + int found = 0; + + ao2_lock(peer); + AST_LIST_TRAVERSE(&peer->selected, selected, entry) { + if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { + found = 1; + break; + } + } + ao2_unlock(peer); + + if (!found) { + if (!(selected = ast_calloc_with_stringfields(1, struct sip_selected, 128))) { + return -1; + } + + ast_string_field_set(selected, callid, remotecc_data->dialogid.callid); + ast_string_field_set(selected, tag, remotecc_data->dialogid.remotetag); + ast_string_field_set(selected, theirtag, remotecc_data->dialogid.localtag); + + ao2_lock(peer); + AST_LIST_INSERT_TAIL(&peer->selected, selected, entry); + ao2_unlock(peer); + } + + transmit_response(pvt, "202 Accepted", req); + + return 0; +} + +/*! \brief Handle remotecc unselect requests */ +static int handle_remotecc_unselect(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + struct sip_selected *selected; + + ao2_lock(peer); + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->selected, selected, entry) { + if (!strcmp(remotecc_data->dialogid.callid, selected->callid) && !strcmp(remotecc_data->dialogid.remotetag, selected->tag) && !strcmp(remotecc_data->dialogid.localtag, selected->theirtag)) { + AST_LIST_REMOVE_CURRENT(entry); + destroy_selected(selected); + break; + } + } + AST_LIST_TRAVERSE_SAFE_END; + ao2_unlock(peer); + + transmit_response(pvt, "202 Accepted", req); + + return 0; +} + +/*! \brief Handle remotecc join requests */ +static int handle_remotecc_join(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer, struct remotecc_data *remotecc_data) +{ + return handle_remotecc_conference(pvt, req, peer, remotecc_data); +} + +/*! \brief Handle incoming remotecc request */ +static int handle_refer_remotecc(struct sip_pvt *pvt, struct sip_request *req, struct sip_peer *peer) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_get_header(req, "Content-Type"); + char *remotecc_body; + struct ast_xml_doc *remotecc_doc = NULL; + struct ast_xml_node *remotecc_request_node, *remotecc_request_children; + struct ast_xml_node *softkeyeventmsg_node, *softkeyeventmsg_children; + struct ast_xml_node *datapassthroughreq_node, *datapassthroughreq_children; + struct remotecc_data remotecc_data; + int start, end, done = 0; + char *boundary = NULL; + + if (!strncasecmp(content_type, "multipart/mixed", 15)) { + if ((boundary = strcasestr(content_type, ";boundary="))) { + boundary += 10; + } else if ((boundary = strcasestr(content_type, "; boundary="))) { + boundary += 11; + } else { + return -1; + } + boundary = ast_strdupa(boundary); + boundary = strsep(&boundary, ";"); + + if ((start = find_boundary(req, boundary, 0, &done)) == -1) { + return -1; + } + start += 1; + if ((end = find_boundary(req, boundary, start, &done)) == -1) { + return -1; + } + + content_type = NULL; + while (start < end) { + const char *line = REQ_OFFSET_TO_STR(req, line[start++]); + + if (!strncasecmp(line, "Content-Type:", 13)) { + content_type = ast_skip_blanks(line + 13); + } else if (ast_strlen_zero(line)) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + } else { + start = 0; + end = req->lines; + } + + if (strcasecmp(content_type, "application/x-cisco-remotecc-request+xml")) { + ast_log(LOG_WARNING, "Content type is not x-cisco-remotecc-request+xml\n"); + return -1; + } + + if (!(remotecc_body = get_content(req, start, end - 1))) { + ast_log(LOG_WARNING, "Unable to get remotecc body\n"); + return -1; + } + + if (!(remotecc_doc = ast_xml_read_memory(remotecc_body, strlen(remotecc_body)))) { + ast_log(LOG_WARNING, "Unable to open XML remotecc document. Is it malformed?\n"); + return -1; + } + + if (!(remotecc_request_node = ast_xml_get_root(remotecc_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + ast_xml_close(remotecc_doc); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(remotecc_request_node), "x-cisco-remotecc-request")) { + ast_log(LOG_WARNING, "Missing x-cisco-remotecc-request node\n"); + ast_xml_close(remotecc_doc); + return -1; + } + + if (!(remotecc_request_children = ast_xml_node_get_children(remotecc_request_node))) { + ast_log(LOG_WARNING, "No tuples in x-cisco-remotecc-request node\n"); + ast_xml_close(remotecc_doc); + return -1; + } + + memset(&remotecc_data, 0, sizeof(remotecc_data)); + + if ((softkeyeventmsg_node = ast_xml_find_element(remotecc_request_children, "softkeyeventmsg", NULL, NULL)) && + (softkeyeventmsg_children = ast_xml_node_get_children(softkeyeventmsg_node))) { + struct ast_xml_node *softkeyevent_node; + struct ast_xml_node *dialogid_node, *dialogid_children; + struct ast_xml_node *consultdialogid_node, *consultdialogid_children; + struct ast_xml_node *joindialogid_node, *joindialogid_children; + struct ast_xml_node *callid_node, *localtag_node, *remotetag_node; + const char *softkeyevent_text, *callid_text, *localtag_text, *remotetag_text; + + if ((softkeyevent_node = ast_xml_find_element(softkeyeventmsg_children, "softkeyevent", NULL, NULL))) { + softkeyevent_text = ast_xml_get_text(softkeyevent_node); + remotecc_data.softkeyevent = ast_strdupa(softkeyevent_text); + ast_xml_free_text(softkeyevent_text); + } + + if ((dialogid_node = ast_xml_find_element(softkeyeventmsg_children, "dialogid", NULL, NULL)) && + (dialogid_children = ast_xml_node_get_children(dialogid_node))) { + if ((callid_node = ast_xml_find_element(dialogid_children, "callid", NULL, NULL))) { + callid_text = ast_xml_get_text(callid_node); + remotecc_data.dialogid.callid = ast_strdupa(callid_text); + ast_xml_free_text(callid_text); + } + + if ((localtag_node = ast_xml_find_element(dialogid_children, "localtag", NULL, NULL))) { + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.dialogid.localtag = ast_strdupa(localtag_text); + ast_xml_free_text(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(dialogid_children, "remotetag", NULL, NULL))) { + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.dialogid.remotetag = ast_strdupa(remotetag_text); + ast_xml_free_text(remotetag_text); + } + } + + if ((consultdialogid_node = ast_xml_find_element(softkeyeventmsg_children, "consultdialogid", NULL, NULL)) && + (consultdialogid_children = ast_xml_node_get_children(consultdialogid_node))) { + if ((callid_node = ast_xml_find_element(consultdialogid_children, "callid", NULL, NULL))) { + callid_text = ast_xml_get_text(callid_node); + remotecc_data.consultdialogid.callid = ast_strdupa(callid_text); + ast_xml_free_text(callid_text); + } + + if ((localtag_node = ast_xml_find_element(consultdialogid_children, "localtag", NULL, NULL))) { + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.consultdialogid.localtag = ast_strdupa(localtag_text); + ast_xml_free_text(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(consultdialogid_children, "remotetag", NULL, NULL))) { + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.consultdialogid.remotetag = ast_strdupa(remotetag_text); + ast_xml_free_text(remotetag_text); + } + } + + if ((joindialogid_node = ast_xml_find_element(softkeyeventmsg_children, "joindialogid", NULL, NULL)) && + (joindialogid_children = ast_xml_node_get_children(joindialogid_node))) { + if ((callid_node = ast_xml_find_element(joindialogid_children, "callid", NULL, NULL))) { + callid_text = ast_xml_get_text(callid_node); + remotecc_data.joindialogid.callid = ast_strdupa(callid_text); + ast_xml_free_text(callid_text); + } + + if ((localtag_node = ast_xml_find_element(joindialogid_children, "localtag", NULL, NULL))) { + localtag_text = ast_xml_get_text(localtag_node); + remotecc_data.joindialogid.localtag = ast_strdupa(localtag_text); + ast_xml_free_text(localtag_text); + } + + if ((remotetag_node = ast_xml_find_element(joindialogid_children, "remotetag", NULL, NULL))) { + remotetag_text = ast_xml_get_text(remotetag_node); + remotecc_data.joindialogid.remotetag = ast_strdupa(remotetag_text); + ast_xml_free_text(remotetag_text); + } + } + } else if ((datapassthroughreq_node = ast_xml_find_element(remotecc_request_children, "datapassthroughreq", NULL, NULL)) && + (datapassthroughreq_children = ast_xml_node_get_children(datapassthroughreq_node))) { + struct ast_xml_node *applicationid_node, *confid_node; + const char *applicationid_text, *confid_text; + + if ((applicationid_node = ast_xml_find_element(datapassthroughreq_children, "applicationid", NULL, NULL))) { + applicationid_text = ast_xml_get_text(applicationid_node); + remotecc_data.applicationid = atoi(S_OR(applicationid_text, "")); + ast_xml_free_text(applicationid_text); + } + + if ((confid_node = ast_xml_find_element(datapassthroughreq_children, "confid", NULL, NULL))) { + confid_text = ast_xml_get_text(confid_node); + remotecc_data.confid = atoi(S_OR(confid_text, "")); + ast_xml_free_text(confid_text); + } + } + + ast_xml_close(remotecc_doc); + + if (boundary && !done) { + start = end + 1; + if ((end = find_boundary(req, boundary, start, &done)) == -1) { + ast_log(LOG_WARNING, "Failed to find end boundary\n"); + return -1; + } + + content_type = NULL; + while (start < end) { + const char *line = REQ_OFFSET_TO_STR(req, line[start++]); + + if (!strncasecmp(line, "Content-Type:", 13)) { + content_type = ast_skip_blanks(line + 13); + } else if (ast_strlen_zero(line)) { + break; + } + } + + if (ast_strlen_zero(content_type)) { + return -1; + } + if (!strcasecmp(content_type, "application/x-cisco-remotecc-cm+xml")) { + char *usercalldata; + + if (!(usercalldata = get_content(req, start, end - 1))) { + ast_log(LOG_WARNING, "Unable to get usercalldata body\n"); + return -1; + } + + remotecc_data.usercalldata = ast_trim_blanks(ast_strdupa(usercalldata)); + } + } + + if (!ast_strlen_zero(remotecc_data.softkeyevent)) { + if (!strcmp(remotecc_data.softkeyevent, "IDivert")) { + return handle_remotecc_idivert(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "HLog")) { + return handle_remotecc_hlog(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Conference")) { + return handle_remotecc_conference(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ConfList")) { + return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ConfDetails")) { + return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "RmLastConf")) { + return handle_remotecc_rmlastconf(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Cancel")) { + transmit_response(pvt, "202 Accepted", req); + return 0; + } else if (!strcmp(remotecc_data.softkeyevent, "Park")) { + return handle_remotecc_park(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "ParkMonitor")) { + return handle_remotecc_parkmonitor(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "StartRecording")) { + return handle_remotecc_startrecording(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "StopRecording")) { + return handle_remotecc_stoprecording(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "QRT")) { + return handle_remotecc_qrt(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "MCID")) { + return handle_remotecc_mcid(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "CallBack")) { + return handle_remotecc_callback(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Select")) { + return handle_remotecc_select(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Unselect")) { + return handle_remotecc_unselect(pvt, req, peer, &remotecc_data); + } else if (!strcmp(remotecc_data.softkeyevent, "Join")) { + return handle_remotecc_join(pvt, req, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported softkeyevent: %s\n", remotecc_data.softkeyevent); + } else if (remotecc_data.applicationid) { + if (remotecc_data.applicationid == REMOTECC_CONFLIST) { + return handle_remotecc_conflist(pvt, req, peer, &remotecc_data); + } else if (remotecc_data.applicationid == REMOTECC_CALLBACK) { + return handle_remotecc_callback(pvt, req, peer, &remotecc_data); + } + + ast_log(LOG_WARNING, "Unsupported applicationid: %d\n", remotecc_data.applicationid); + } else { + ast_log(LOG_WARNING, "Unsupported x-cisco-remotecc-request+xml request\n"); + } +#endif + return -1; +} + /*! \brief Get tag from packet * * \return Returns the pointer to the provided tag buffer, @@ -25738,16 +29312,130 @@ return 0; } +/*! \brief Handle dialog notifications */ +static int handle_notify_dialog(struct sip_pvt *pvt, struct sip_request *req) +{ +#ifdef HAVE_LIBXML2 + struct sip_peer *peer; + char to[256]; + char *uri, *name, *domain; + const char *content_type = sip_get_header(req, "Content-Type"); + char *dialog_body; + struct ast_xml_doc *dialog_doc = NULL; + struct ast_xml_node *dialog_info_node, *dialog_info_children; + struct ast_xml_node *dialog_node, *dialog_children; + struct ast_xml_node *state_node; + const char *state_text; + int offhook = 0; + + ast_copy_string(to, sip_get_header(req, "To"), sizeof(to)); + uri = get_in_brackets(to); + uri = remove_uri_parameters(uri); + + if (parse_uri_legacy_check(uri, "sip:,sips", &name, NULL, &domain, NULL)) { + return -1; + } + SIP_PEDANTIC_DECODE(name); + + if (strcasecmp(content_type, "application/dialog-info+xml")) { + ast_log(LOG_WARNING, "Content type is not application/dialog-info+xml\n"); + return -1; + } + + if (!(dialog_body = get_content(req, 0, req->lines - 1))) { + ast_log(LOG_WARNING, "Unable to get dialog body\n"); + return -1; + } + + if (!(dialog_doc = ast_xml_read_memory(dialog_body, strlen(dialog_body)))) { + ast_log(LOG_WARNING, "Unable to open XML dialog document. Is it malformed?\n"); + return -1; + } + + if (!(dialog_info_node = ast_xml_get_root(dialog_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (strcasecmp(ast_xml_node_get_name(dialog_info_node), "dialog-info")) { + ast_log(LOG_WARNING, "Missing dialog-info node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (!(dialog_info_children = ast_xml_node_get_children(dialog_info_node))) { + ast_log(LOG_WARNING, "No tuples in dialog-info node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (!(dialog_node = ast_xml_find_element(dialog_info_children, "dialog", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing dialog node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (!(dialog_children = ast_xml_node_get_children(dialog_node))) { + ast_log(LOG_WARNING, "No tuples in dialog node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + if (!(state_node = ast_xml_find_element(dialog_children, "state", NULL, NULL))) { + ast_log(LOG_WARNING, "Missing state node\n"); + ast_xml_close(dialog_doc); + return -1; + } + + state_text = ast_xml_get_text(state_node); + + if (!strcasecmp(state_text, "trying")) { + offhook = 1; + } else if (!strcasecmp(state_text, "terminated")) { + offhook = -1; + } else { + ast_log(LOG_WARNING, "Invalid content in state node %s\n", state_text); + ast_xml_free_text(state_text); + ast_xml_close(dialog_doc); + return -1; + } + + ast_xml_free_text(state_text); + ast_xml_close(dialog_doc); + + if (!(peer = sip_find_peer(name, NULL, TRUE, FINDPEERS, TRUE, 0))) { + ast_log(LOG_WARNING, "Unknown peer '%s'\n", name); + return -1; + } + + if (peer->socket.type == pvt->socket.type && !ast_sockaddr_cmp(&peer->addr, &pvt->recv)) { + ao2_lock(peer); + if ((peer->offhook += offhook) < 0) { + peer->offhook = 0; + } + ao2_unlock(peer); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + sip_unref_peer(peer, "handle_notify_dialog: unref peer from sip_find_peer lookup"); + transmit_response(pvt, "200 OK", req); + return 0; +#else + return -1; +#endif +} + /*! \brief Handle incoming notifications */ static int handle_request_notify(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) { /* This is mostly a skeleton for future improvements */ /* Mostly created to return proper answers on notifications on outbound REFER's */ int res = 0; - const char *event = sip_get_header(req, "Event"); + char *event = ast_strdupa(sip_get_header(req, "Event")); char *sep; - if( (sep = strchr(event, ';')) ) { /* XXX bug here - overwriting string ? */ + if ((sep = strchr(event, ';'))) { *sep++ = '\0'; } @@ -25762,6 +29450,7 @@ char *buf, *cmd, *code; int respcode; int success = TRUE; + const char *type = find_content_type(req); /* EventID for each transfer... EventID is basically the REFER cseq @@ -25770,7 +29459,13 @@ Check if we have an owner of this event */ /* Check the content type */ - if (strncasecmp(sip_get_header(req, "Content-Type"), "message/sipfrag", strlen("message/sipfrag"))) { + if (strncasecmp(type, "message/sipfrag", strlen("message/sipfrag"))) { + if (!strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { + transmit_response(p, "200 OK", req); + sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); + return 0; + } + /* We need a sipfrag */ transmit_response(p, "400 Bad request", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); @@ -25778,7 +29473,7 @@ } /* Get the text of the attachment */ - if (ast_strlen_zero(buf = get_content(req))) { + if (ast_strlen_zero(buf = get_content(req, 0, req->lines))) { ast_log(LOG_WARNING, "Unable to retrieve attachment from NOTIFY %s\n", p->callid); transmit_response(p, "400 Bad request", req); sip_scheddestroy(p, DEFAULT_TRANS_TIMEOUT); @@ -25892,6 +29587,11 @@ transmit_response(p, "200 OK", req); } else if (!strcmp(event, "call-completion")) { res = handle_cc_notify(p, req); + } else if (!strcmp(event, "dialog")) { + if (handle_notify_dialog(p, req)) { + transmit_response(p, "489 Bad event", req); + res = -1; + } } else { /* We don't understand this event. */ transmit_response(p, "489 Bad event", req); @@ -27331,7 +31031,7 @@ We can't destroy dialogs, since we want the call to continue. */ -static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, uint32_t seqno, int *nounlock) +static int handle_request_refer(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e, int *nounlock) { char *refer_to = NULL; char *refer_to_context = NULL; @@ -27340,6 +31040,46 @@ enum ast_transfer_result transfer_res; RAII_VAR(struct ast_channel *, transferer, NULL, ast_channel_cleanup); RAII_VAR(struct ast_str *, replaces_str, NULL, ast_free_ptr); + const char *type = find_content_type(req); + + /* Cisco USECALLMANAGER remotecc and failover */ + if (!strcasecmp(type, "application/x-cisco-alarm+xml") || !strcasecmp(type, "application/x-cisco-remotecc-response+xml")) { + transmit_response(p, "202 Accepted", req); + if (!p->owner) { + sip_alreadygone(p); + pvt_set_needdestroy(p, "alarm/remotecc device notificaton"); + } + return 0; + } else if (!strcasecmp(type, "application/x-cisco-remotecc-request+xml")) { + struct sip_peer *authpeer = NULL; + + res = check_user_full(p, req, SIP_REFER, e, 0, addr, &authpeer); + + /* if an authentication response was sent, we are done here */ + if (res == AUTH_CHALLENGE_SENT) { + return 0; + } + + if (res != AUTH_SUCCESSFUL) { + ast_log(LOG_NOTICE, "Failed to authenticate device %s for REFER\n", sip_get_header(req, "From")); + transmit_response_reliable(p, "403 Forbidden", req); + } else if (handle_refer_remotecc(p, req, authpeer)) { + transmit_response(p, "603 Declined (Remotecc failed)", req); + if (authpeer) { + sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_refer"); + } + } + if (!p->owner) { + sip_alreadygone(p); + pvt_set_needdestroy(p, "remotecc request"); + } + return 0; + } else if (!strcasecmp(sip_get_header(req, "Refer-To"), "")) { + transmit_response(p, "202 Accepted", req); + sip_alreadygone(p); + pvt_set_needdestroy(p, "token registration"); + return 0; + } if (req->debug) { ast_verbose("Call %s got a SIP call transfer from %s: (REFER)!\n", @@ -27819,6 +31559,10 @@ transmit_response(p, "200 OK", req); } + if (ast_test_flag(&p->flags[2], SIP_PAGE3_RTP_STATS_ON_BYE)) { + parse_rtp_stats(p, req); + } + /* Destroy any pending invites so we won't try to do another * scheduled reINVITE. */ stop_reinvite_retry(p); @@ -28189,7 +31933,7 @@ return FALSE; } - if (!(pidf_body = get_content(req))) { + if (!(pidf_body = get_content(req, 0, req->lines))) { ast_log(LOG_WARNING, "Unable to get PIDF body\n"); return FALSE; } @@ -28309,6 +32053,100 @@ return res; } +static int presence_esc_publish_handler(struct sip_pvt *pvt, struct sip_request *req, struct event_state_compositor *esc, struct sip_esc_entry *esc_entry) +{ + struct ast_xml_doc *pidf_doc = NULL; + struct ast_xml_node *presence_node; + struct ast_xml_node *presence_children; + struct ast_xml_node *person_node; + struct ast_xml_node *person_children; + struct ast_xml_node *activities_node; + struct ast_xml_node *activities_children; + struct ast_xml_node *dnd_node; + struct ast_xml_node *available_node; + struct sip_peer *peer; + struct sip_alias *alias; + int res = 0; + int donotdisturb = 0; + + if (sip_pidf_validate(req, &pidf_doc) == FALSE) { + res = -1; + } else if (ast_test_flag(&pvt->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + presence_node = ast_xml_get_root(pidf_doc); + if (!(presence_children = ast_xml_node_get_children(presence_node))) { + ast_log(LOG_WARNING, "No tuples within presence element.\n"); + res = -1; + goto presence_publish_cleanup; + } + + if (!(person_node = ast_xml_find_element(presence_children, "person", NULL, NULL))) { + ast_log(LOG_NOTICE, "Couldn't find person node?\n"); + res = -1; + goto presence_publish_cleanup; + } + + if (!(person_children = ast_xml_node_get_children(person_node))) { + ast_log(LOG_NOTICE, "No tuples within person node.\n"); + res = -1; + goto presence_publish_cleanup; + } + + if (!(activities_node = ast_xml_find_element(person_children, "activities", NULL, NULL))) { + ast_log(LOG_NOTICE, "Couldn't find activities node?\n"); + res = -1; + goto presence_publish_cleanup; + } + + if (!(activities_children = ast_xml_node_get_children(activities_node))) { + ast_log(LOG_NOTICE, "No tuples within activities node.\n"); + res = -1; + goto presence_publish_cleanup; + } + + if ((dnd_node = ast_xml_find_element(activities_children, "dnd", NULL, NULL))) { + donotdisturb = 1; + } else if ((available_node = ast_xml_find_element(activities_children, "available", NULL, NULL))) { + donotdisturb = 0; + } else { + ast_log(LOG_NOTICE, "Couldn't find dnd or available node?\n"); + res = -1; + goto presence_publish_cleanup; + } + + if (!(peer = sip_find_peer(pvt->peername, NULL, TRUE, FINDALLDEVICES, FALSE, 0))) { + ast_log(LOG_NOTICE, "No such peer '%s'\n", pvt->peername); + res = -1; + goto presence_publish_cleanup; + } + + if (peer->donotdisturb != donotdisturb) { + peer->donotdisturb = donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->donotdisturb = peer->donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", alias->peer->name); + } + } + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } + } + + sip_unref_peer(peer, "presence_esc_publish_handler: from sip_find_peer call, setting DoNotDisturb/Available"); + } else { + res = -1; + } + +presence_publish_cleanup: + if (pidf_doc) { + ast_xml_close(pidf_doc); + } + if (res) { + transmit_response(pvt, "400 Bad Request", req); + } + return res; +} #endif /* HAVE_LIBXML2 */ static int handle_sip_publish_initial(struct sip_pvt *p, struct sip_request *req, struct event_state_compositor *esc, const int expires) @@ -28585,6 +32423,178 @@ return 0; } +enum { + FE_BULKUPDATE, + FE_DONOTDISTURB, + FE_CALLFORWARD +}; + +/*! \brief Handle incoming feature event SUBSCRIBE body */ +static int handle_subscribe_featureevent(struct sip_peer *peer, struct sip_request *req, int *feature) +{ +#ifdef HAVE_LIBXML2 + const char *content_type = sip_get_header(req, "Content-Type"); + char *featureevent_body; + struct ast_xml_doc *featureevent_doc = NULL; + struct ast_xml_node *root_node; + + if (!atoi(sip_get_header(req, "Content-Length"))) { + /* Peer is subscribing to the current DoNotDisturb and CallForward state */ + *feature = FE_BULKUPDATE; + return 0; + } + + if (strcasecmp(content_type, "application/x-as-feature-event+xml")) { + ast_log(LOG_WARNING, "Content type is not x-as-feature-event+xml\n"); + return -1; + } + + if (!(featureevent_body = get_content(req, 0, req->lines - 1))) { + ast_log(LOG_WARNING, "Unable to get feature event body\n"); + return -1; + } + + if (!(featureevent_doc = ast_xml_read_memory(featureevent_body, strlen(featureevent_body)))) { + ast_log(LOG_WARNING, "Unable to open XML as-feature-event document. Is it malformed?\n"); + return -1; + } + + if (!(root_node = ast_xml_get_root(featureevent_doc))) { + ast_log(LOG_WARNING, "Unable to get root node\n"); + ast_xml_close(featureevent_doc); + return -1; + } + + if (!strcmp(ast_xml_node_get_name(root_node), "SetDoNotDisturb")) { + int donotdisturb; + struct ast_xml_node *set_donotdisturb_node, *set_donotdisturb_children; + struct ast_xml_node *donotdisturb_on_node; + const char *donotdisturb_on_text; + + set_donotdisturb_node = root_node; + + if (!(set_donotdisturb_children = ast_xml_node_get_children(set_donotdisturb_node))) { + ast_log(LOG_WARNING, "No tuples within SetDoNotDisturb node"); + ast_xml_close(featureevent_doc); + return -1; + } + + if (!(donotdisturb_on_node = ast_xml_find_element(set_donotdisturb_children, "doNotDisturbOn", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find doNotDisturbOn node"); + ast_xml_close(featureevent_doc); + return -1; + } + + donotdisturb_on_text = ast_xml_get_text(donotdisturb_on_node); + + if (!strcmp(donotdisturb_on_text, "true")) { + donotdisturb = 1; + } else if (!strcmp(donotdisturb_on_text, "false")) { + donotdisturb = 0; + } else { + ast_log(LOG_WARNING, "Invalid content in doNotDisturbOn node %s\n", donotdisturb_on_text); + ast_xml_free_text(donotdisturb_on_text); + ast_xml_close(featureevent_doc); + return -1; + } + + ast_xml_free_text(donotdisturb_on_text); + + if (peer->donotdisturb != donotdisturb) { + peer->donotdisturb = donotdisturb; + ast_presence_state_changed(AST_PRESENCE_NOT_SET, NULL, NULL, "SIP/%s", peer->name); + if (!peer->is_realtime) { + ast_db_put("SIP/DoNotDisturb", peer->name, peer->donotdisturb ? "yes" : "no"); + } + } + + *feature = FE_DONOTDISTURB; + } else if (!strcmp(ast_xml_node_get_name(root_node), "SetForwarding")) { + char callforward[AST_MAX_EXTENSION]; + struct ast_xml_node *set_forwarding_node, *set_forwarding_children; + struct ast_xml_node *forwarding_type_node, *activate_forward_node, *forward_dn_node; + const char *forwarding_type_text, *activate_forward_text, *forward_dn_text; + + set_forwarding_node = root_node; + + if (!(set_forwarding_children = ast_xml_node_get_children(set_forwarding_node))) { + ast_log(LOG_WARNING, "No tuples within SetForwarding node"); + ast_xml_close(featureevent_doc); + return -1; + } + + if (!(forwarding_type_node = ast_xml_find_element(set_forwarding_children, "forwardingType", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find forwardingType node\n"); + ast_xml_close(featureevent_doc); + return -1; + } + + forwarding_type_text = ast_xml_get_text(forwarding_type_node); + + if (strcmp(forwarding_type_text, "forwardImmediate")) { + ast_log(LOG_WARNING, "forwardingType not supported: %s\n", forwarding_type_text); + ast_xml_free_text(forwarding_type_text); + ast_xml_close(featureevent_doc); + return -1; + } + + ast_xml_free_text(forwarding_type_text); + + if (!(activate_forward_node = ast_xml_find_element(set_forwarding_children, "activateForward", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find activateForward node"); + ast_xml_close(featureevent_doc); + return -1; + } + + activate_forward_text = ast_xml_get_text(activate_forward_node); + + if (!strcmp(activate_forward_text, "true")) { + if (!(forward_dn_node = ast_xml_find_element(set_forwarding_children, "forwardDN", NULL, NULL))) { + ast_log(LOG_WARNING, "Couldn't find forwardDN node\n"); + ast_xml_free_text(activate_forward_text); + ast_xml_close(featureevent_doc); + return -1; + } + + forward_dn_text = ast_xml_get_text(forward_dn_node); + ast_copy_string(callforward, S_OR(forward_dn_text, ""), sizeof(callforward)); + ast_xml_free_text(forward_dn_text); + } else if (!strcmp(activate_forward_text, "false")) { + callforward[0] = '\0'; + } else { + ast_log(LOG_WARNING, "Invalid content in activateForward node: %s\n", activate_forward_text); + ast_xml_free_text(activate_forward_text); + ast_xml_close(featureevent_doc); + return -1; + } + + ast_xml_free_text(activate_forward_text); + + if (strcmp(peer->callforward, callforward)) { + ast_string_field_set(peer, callforward, callforward); + if (!peer->is_realtime) { + if (ast_strlen_zero(peer->callforward)) { + ast_db_del("SIP/CallForward", peer->name); + } else { + ast_db_put("SIP/CallForward", peer->name, peer->callforward); + } + } + } + + *feature = FE_CALLFORWARD; + } else { + ast_log(LOG_WARNING, "Couldn't find SetDoNotDisturb or SetForwarding node: %s\n", ast_xml_node_get_name(root_node)); + ast_xml_close(featureevent_doc); + return -1; + } + + ast_xml_close(featureevent_doc); + return 0; +#else + return -1 +#endif +} + /*! \brief Handle incoming SUBSCRIBE request */ static int handle_request_subscribe(struct sip_pvt *p, struct sip_request *req, struct ast_sockaddr *addr, uint32_t seqno, const char *e) { @@ -28593,6 +32603,7 @@ char *event = ast_strdupa(sip_get_header(req, "Event")); /* Get Event package name */ int resubscribe = (p->subscribed != NONE) && !req->ignore; char *options; + int feature = -1; if (p->initreq.headers) { /* We already have a dialog */ @@ -28726,12 +32737,19 @@ subscribed = XPIDF_XML; /* Older versions of Polycom firmware will claim pidf+xml, but really they only support xpidf+xml */ } else { subscribed = PIDF_XML; /* RFC 3863 format */ + if (strstr(p->useragent, "Digium")) { + ast_set_flag(&p->flags[2], SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS); + } } } 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 */ + if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + subscribed = PIDF_XML; /* RFC 3863 format + Cisco USECALLMANAGER */ + } else { + 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 { @@ -28854,6 +32872,33 @@ p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); } /* Do not release authpeer here */ + } else if (!strcmp(event, "as-feature-event")) { + if (!authpeer || handle_subscribe_featureevent(authpeer, req, &feature) == -1) { + transmit_response(p, "489 Bad Event", req); + pvt_set_needdestroy(p, "unknown format"); + if (authpeer) { + sip_unref_peer(authpeer, "sip_unref_peer, from handle_request_subscribe (authpeer 4)"); + } + return 0; + } + + p->subscribed = FEATURE_EVENTS; + if (authpeer->fepvt != p) { /* Destroy old PVT if this is a new one */ + /* We only allow one subscription per peer */ + if (authpeer->fepvt) { + dialog_unlink_all(authpeer->fepvt); + authpeer->fepvt = dialog_unref(authpeer->fepvt, "unref dialog authpeer->fepvt"); + } + authpeer->fepvt = dialog_ref(p, "setting peers' fepvt to p"); + } + + if (p->relatedpeer != authpeer) { + if (p->relatedpeer) { + sip_unref_peer(p->relatedpeer, "Unref previously stored relatedpeer ptr"); + } + p->relatedpeer = sip_ref_peer(authpeer, "setting dialog's relatedpeer pointer"); + } + /* Do not release authpeer here */ } else if (!strcmp(event, "call-completion")) { handle_cc_subscribe(p, req); } else { /* At this point, Asterisk does not understand the specified event */ @@ -28898,6 +32943,9 @@ if (p->subscribed == MWI_NOTIFICATION && p->relatedpeer) { ast_debug(2, "%s subscription for mailbox notification - peer %s\n", action, p->relatedpeer->name); + } else if (p->subscribed == FEATURE_EVENTS) { + ast_debug(2, "%s feature event subscription for peer %s\n", + action, p->username); } else if (p->subscribed == CALL_COMPLETION) { ast_debug(2, "%s CC subscription for peer %s\n", action, p->username); } else { @@ -28920,10 +32968,38 @@ struct sip_peer *peer = p->relatedpeer; sip_ref_peer(peer, "ensure a peer ref is held during MWI sending"); ao2_unlock(p); - sip_send_mwi_to_peer(peer, 0); + sip_send_mwi(peer, 0); ao2_lock(p); sip_unref_peer(peer, "release a peer ref now that MWI is sent"); } + } else if (p->subscribed == FEATURE_EVENTS) { + struct sip_request resp; + + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); + respprep(&resp, p, "200 OK", req); + if (feature != FE_BULKUPDATE) { + add_header(&resp, "Content-Type", "application/x-as-feature-event+xml"); + add_content(&resp, "\n"); + if (feature == FE_DONOTDISTURB) { + add_content(&resp, "\n"); + } else if (feature == FE_CALLFORWARD) { + add_content(&resp, "\n"); + } + } + send_response(p, &resp, XMIT_UNRELIABLE, 0); + + if (p->relatedpeer) { + struct sip_peer *peer = p->relatedpeer; + sip_ref_peer(peer, "ensure a peer ref is held during feature events sending"); + if (feature == FE_BULKUPDATE) { + sip_send_bulkupdate(peer); + } else if (feature == FE_DONOTDISTURB) { + sip_send_donotdisturb(peer); + } else if (feature == FE_CALLFORWARD) { + sip_send_callforward(peer); + } + sip_unref_peer(peer, "release a peer ref now that feature events is sent"); + } } else if (p->subscribed != CALL_COMPLETION) { struct state_notify_data data = { 0, }; char *subtype = NULL; @@ -28944,6 +33020,9 @@ sip_pvt_unlock(p); data.state = ast_extension_state_extended(NULL, p->context, p->exten, &device_state_info); + data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); + data.presence_subtype = subtype; + data.presence_message = message; sip_pvt_lock(p); if (data.state < 0) { @@ -28958,11 +33037,7 @@ } return 0; } - if (allow_notify_user_presence(p)) { - data.presence_state = ast_hint_presence_state(NULL, p->context, p->exten, &subtype, &message); - data.presence_subtype = subtype; - data.presence_message = message; - } + ast_set_flag(&p->flags[1], SIP_PAGE2_DIALOG_ESTABLISHED); transmit_response(p, "200 OK", req); /* RFC 3265: A notification must be sent on every subscribe, so force it */ @@ -29309,7 +33384,7 @@ break; case SIP_REFER: - res = handle_request_refer(p, req, seqno, nounlock); + res = handle_request_refer(p, req, addr, seqno, e, nounlock); break; case SIP_CANCEL: res = handle_request_cancel(p, req); @@ -29768,7 +33843,7 @@ * are processed. The peer will still be valid however. * \returns -1 on failure, 0 on success */ -static int sip_send_mwi_to_peer(struct sip_peer *peer, int cache_only) +static int sip_send_mwi(struct sip_peer *peer, int cache_only) { /* Called with peer lock, but releases it */ struct sip_pvt *p; @@ -29798,7 +33873,7 @@ if (!get_cached_mwi(peer, &newmsgs, &oldmsgs) && !cache_only) { /* Fall back to manually checking the mailbox if not cache_only and get_cached_mwi failed */ struct ast_str *mailbox_str = ast_str_alloca(512); - peer_mailboxes_to_str(&mailbox_str, peer); + get_peer_mailboxes(&mailbox_str, peer); /* if there is no mailbox do nothing */ if (!ast_str_strlen(mailbox_str)) { ao2_unlock(peer); @@ -29816,7 +33891,7 @@ if (peer->mwipvt) { /* Base message on subscription */ - p = dialog_ref(peer->mwipvt, "sip_send_mwi_to_peer: Setting dialog ptr p from peer->mwipvt"); + p = dialog_ref(peer->mwipvt, "sip_send_mwi: Setting dialog ptr p from peer->mwipvt"); ao2_unlock(peer); } else { ao2_unlock(peer); @@ -29866,13 +33941,357 @@ /* the following will decrement the refcount on p as it finishes */ transmit_notify_with_mwi(p, newmsgs, oldmsgs, vmexten); sip_pvt_unlock(p); - dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi_to_peer."); + dialog_unref(p, "unref dialog ptr p just before it goes out of scope at the end of sip_send_mwi."); update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); return 0; } +/*! + * \brief Expire bulk-register aliases + */ +static void expire_peer_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (!alias->peer) { + continue; + } + + ast_verb(3, "Unregistered SIP '%s'\n", alias->name); + alias->peer->lastms = 0; + memset(&alias->peer->addr, 0, sizeof(alias->peer->addr)); + + if (alias->peer->socket.tcptls_session) { + ao2_ref(alias->peer->socket.tcptls_session, -1); + alias->peer->socket.tcptls_session = NULL; + } else if (alias->peer->socket.ws_session) { + ast_websocket_unref(alias->peer->socket.ws_session); + alias->peer->socket.ws_session = NULL; + } + + ast_string_field_set(alias->peer, fullcontact, ""); + ast_string_field_set(alias->peer, username, ""); + ast_string_field_set(alias->peer, useragent, ""); + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_OFFLINE); + blob = ast_json_pack("{s: s, s: s}", + "peer_status", "Unregistered", + "cause", "Expired"); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + register_peer_exten(alias->peer, 0); + + sip_unref_peer(alias->peer, "unref after sip_find_peer"); + alias->peer = NULL; + } +} + +/*! + * \brief Update bulk-register aliases + */ +static void register_peer_aliases(struct sip_peer *peer) +{ + struct sip_alias *alias; + char *scheme, *hostport; + + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return; + } + + scheme = ast_strdupa(peer->fullcontact); + if ((hostport = strchr(scheme, ':')) != NULL) { + *hostport++ = '\0'; + if ((hostport = strchr(hostport, '@')) != NULL) { + *hostport++ = '\0'; + } + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (!alias->peer) { + if (!(alias->peer = sip_find_peer(alias->name, NULL, TRUE, FINDPEERS, FALSE, 0))) { + ast_log(LOG_WARNING, "No such register peer '%s'\n", alias->name); + continue; + } + + /* remove any schedules that may have been created */ + peer_sched_cleanup(alias->peer); + } + + /* These settings could have been overwritten by a reload */ + alias->peer->cisco_lineindex = alias->lineindex; + ast_string_field_set(alias->peer, username, alias->name); + + ast_string_field_set(alias->peer, cisco_authname, peer->name); + ast_string_field_set(alias->peer, secret, peer->secret); + ast_string_field_set(alias->peer, md5secret, peer->md5secret); + + ast_string_field_set(alias->peer, regcallid, peer->regcallid); + ast_string_field_set(alias->peer, cisco_devicename, peer->cisco_devicename); + ast_string_field_set(alias->peer, useragent, peer->useragent); + + alias->peer->donotdisturb = peer->donotdisturb; + alias->peer->huntgroup = peer->huntgroup; + + /* Peer hasn't changed */ + if (!ast_sockaddr_cmp(&peer->addr, &alias->peer->addr)) { + continue; + } + + alias->peer->portinuri = peer->portinuri; + alias->peer->fromdomainport = peer->fromdomainport; + alias->peer->sipoptions = peer->sipoptions; + + ast_string_field_build(alias->peer, fullcontact, "%s:%s@%s", scheme, alias->peer->name, hostport); + + alias->peer->addr = peer->addr; + copy_socket_data(&alias->peer->socket, &peer->socket); + + if (alias->peer->endpoint) { + RAII_VAR(struct ast_json *, blob, NULL, ast_json_unref); + ast_endpoint_set_state(alias->peer->endpoint, AST_ENDPOINT_ONLINE); + blob = ast_json_pack("{s: s, s: s}", + "peer_status", "Registered", + "address", ast_sockaddr_stringify(&peer->addr)); + ast_endpoint_blob_publish(alias->peer->endpoint, ast_endpoint_state_type(), blob); + } + + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + register_peer_exten(alias->peer, 1); + + ast_verb(3, "Registered SIP '%s' at %s\n", alias->peer->name, ast_sockaddr_stringify(&alias->peer->addr)); + alias->peer->offhook = 0; + + update_peer(alias->peer, max_expiry); + update_peer_lastmsgssent(alias->peer, -1, 0); + } +} + +/*! + * \brief Register all bulk-register aliases + */ +static void register_all_aliases(void) +{ + struct ao2_iterator i; + struct sip_peer *peer; + struct sip_alias *alias; + + if (!speerobjs) { + return; + } + + i = ao2_iterator_init(peers, 0); + while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { + ao2_lock(peer); + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (alias->peer) { + alias->peer->addr = peer->addr; + } + } + register_peer_aliases(peer); + ao2_unlock(peer); + sip_unref_peer(peer, "toss iterator peer ptr"); + } + ao2_iterator_destroy(&i); +} + +/*! + * \brief Send donotdisturb, call forward and huntgroup in one bulk update + */ +static int sip_send_bulkupdate(struct sip_peer *peer) +{ + if (ast_sockaddr_isnull(&peer->addr) && ast_sockaddr_isnull(&peer->defaddr)) { + return 0; + } + + if (ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + struct sip_pvt *pvt; + struct ast_str *content; + int newmsgs = 0, oldmsgs = 0; + struct sip_alias *alias; + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + return -1; + } + + if (!(content = ast_str_create(8192))) { + dialog_unref(pvt, "drop pvt"); + return -1; + } + + /* Don't use create_addr_from_peer here as it may fail due to the peer not having responded to an OPTIONS request yet */ + pvt->sa = peer->addr; + pvt->recv = peer->addr; + copy_socket_data(&pvt->socket, &peer->socket); + ast_copy_flags(&pvt->flags[0], &peer->flags[0], SIP_FLAGS_TO_COPY); + ast_copy_flags(&pvt->flags[1], &peer->flags[1], SIP_PAGE2_FLAGS_TO_COPY); + ast_copy_flags(&pvt->flags[2], &peer->flags[2], SIP_PAGE3_FLAGS_TO_COPY); + + /* Recalculate our side, and recalculate Call ID */ + ast_sip_ouraddrfor(&pvt->sa, &pvt->ourip, pvt); + change_callid_pvt(pvt, NULL); + + if (!ast_strlen_zero(peer->tohost)) { + ast_string_field_set(pvt, tohost, peer->tohost); + } else { + ast_string_field_set(pvt, tohost, ast_sockaddr_stringify_host_remote(&peer->addr)); + } + if (!pvt->portinuri) { + pvt->portinuri = peer->portinuri; + } + if (peer->fromdomainport) { + pvt->fromdomainport = peer->fromdomainport; + } + + ast_string_field_set(pvt, fullcontact, peer->fullcontact); + ast_string_field_set(pvt, username, peer->username); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->donotdisturb ? "enable" : "disable"); + ast_str_append(&content, 0, "\n", ast_test_flag(&peer->flags[2], SIP_PAGE3_DND_BUSY) ? "callreject" : "ringeroff"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->huntgroup ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 9, "\n"); + + if (!get_cached_mwi(peer, &newmsgs, &oldmsgs)) { + struct ast_str *mailbox = ast_str_alloca(512); + + get_peer_mailboxes(&mailbox, peer); + if (ast_str_strlen(mailbox)) { + ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); + update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); + } else { + update_peer_lastmsgssent(peer, -1, 0); + } + } + + ast_str_append(&content, 0, "\n", peer->cisco_lineindex); + ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", peer->callforward); + ast_str_append(&content, 0, "%s\n", !strcmp(peer->callforward, peer->vmexten) ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + int newmsgs = 0, oldmsgs = 0; + + if (!alias->peer) { + continue; + } + + if (!get_cached_mwi(alias->peer, &newmsgs, &oldmsgs)) { + struct ast_str *mailbox = ast_str_alloca(512); + + get_peer_mailboxes(&mailbox, alias->peer); + if (ast_str_strlen(mailbox)) { + ast_app_inboxcount(ast_str_buffer(mailbox), &newmsgs, &oldmsgs); + update_peer_lastmsgssent(peer, ((newmsgs > 0x7fff ? 0x7fff0000 : (newmsgs << 16)) | (oldmsgs > 0xffff ? 0xffff : oldmsgs)), 0); + } else { + update_peer_lastmsgssent(peer, -1, 0); + } + } + + ast_str_append(&content, 0, "\n", alias->peer->cisco_lineindex); + ast_str_append(&content, 0, "%s\n", newmsgs ? "yes" : "no"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n", newmsgs, oldmsgs); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "%s\n", alias->peer->callforward); + ast_str_append(&content, 0, "%s\n", !strcmp(alias->peer->callforward, alias->peer->vmexten) ? "on" : "off"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + } + + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "bump down the count of pvt since we're done with it."); + + ast_free(content); + } else if (peer->fepvt) { + struct sip_request req; + struct sip_pvt *pvt = peer->fepvt; + char tmp[512], boundary[32]; + + ast_set_flag(&pvt->flags[0], SIP_OUTGOING); + reqprep(&req, pvt, SIP_NOTIFY, 0, 1); + + snprintf(boundary, sizeof(boundary), "%08lx%08lx%08lx", ast_random(), ast_random(), ast_random()); + + add_header(&req, "Event", "as-feature-event"); + if (pvt->expiry) { + add_header(&req, "Subscription-State", "active"); + } else { + add_header(&req, "Subscription-State", "terminated;reason=timeout"); + } + snprintf(tmp, sizeof(tmp), "multipart/mixed; boundary=%s", boundary); + add_header(&req, "Content-Type", tmp); + + snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); + add_content(&req, tmp); + add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); + add_content(&req, "\r\n"); + add_content(&req, "\n"); + add_content(&req, "\n"); + snprintf(tmp, sizeof(tmp), "\n%s\n", peer->donotdisturb ? "true" : "false"); + add_content(&req, tmp); + add_content(&req, "\n"); + add_content(&req, "\r\n"); + + snprintf(tmp, sizeof(tmp), "--%s\r\n", boundary); + add_content(&req, tmp); + add_content(&req, "Content-Type: application/x-as-feature-event+xml\r\n"); + add_content(&req, "\r\n"); + add_content(&req, "\n"); + add_content(&req, "\n"); + add_content(&req, "\nforwardImmediate\n"); + snprintf(tmp, sizeof(tmp), "%s\n%s\n", !ast_strlen_zero(peer->callforward) ? "true" : "false", peer->callforward); + add_content(&req, tmp); + add_content(&req, "\n"); + add_content(&req, "\r\n"); + + snprintf(tmp, sizeof(tmp), "--%s--\r\n", boundary); + add_content(&req, tmp); + send_request(pvt, &req, XMIT_RELIABLE, pvt->ocseq); + } + + return 0; +} + static struct ast_manager_event_blob *session_timeout_to_ami(struct stasis_message *msg) { RAII_VAR(struct ast_str *, channel_string, NULL, ast_free); @@ -30574,6 +34993,25 @@ ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); } + if (!AST_LIST_EMPTY(&peer->aliases)) { + struct sip_alias *alias; + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (!alias->peer || alias->peer->lastms == -1) { + continue; + } + + ast_log(LOG_NOTICE, "Peer '%s' is now UNREACHABLE! Last qualify: %d\n", alias->peer->name, alias->peer->lastms); + manager_event(EVENT_FLAG_SYSTEM, "PeerStatus", "ChannelType: SIP\r\nPeer: SIP/%s\r\nPeerStatus: Unreachable\r\nTime: %d\r\n", alias->peer->name, -1); + + alias->peer->lastms = -1; + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", alias->peer->name); + if (sip_cfg.regextenonqualify) { + register_peer_exten(alias->peer, FALSE); + } + } + } + /* Try again quickly */ AST_SCHED_REPLACE_UNREF(peer->pokeexpire, sched, DEFAULT_FREQ_NOTOK, sip_poke_peer_s, peer, @@ -30727,13 +35165,13 @@ char *host; char *tmp; struct sip_peer *p; - int res = AST_DEVICE_INVALID; /* make sure data is not null. Maybe unnecessary, but better be safe */ host = ast_strdupa(data ? data : ""); - if ((tmp = strchr(host, '@'))) + if ((tmp = strchr(host, '@'))) { host = tmp + 1; + } ast_debug(3, "Checking device state for peer %s\n", host); @@ -30771,7 +35209,7 @@ else if (p->call_limit && p->busy_level && p->inuse >= p->busy_level) /* We're forcing busy before we've reached the call limit */ res = AST_DEVICE_BUSY; - else if (p->call_limit && p->inuse) + else if (p->call_limit && (p->inuse || p->offhook)) /* Not busy, but we do have a call */ res = AST_DEVICE_INUSE; else if (p->maxms && ((p->lastms > p->maxms) || (p->lastms < 0))) @@ -30789,6 +35227,35 @@ return res; } +static int sip_presencestate(const char *data, char **subtype, char **message) +{ + char *host; + char *tmp; + struct sip_peer *p; + int res = AST_PRESENCE_INVALID; + + /* make sure data is not null. Maybe unnecessary, but better be safe */ + host = ast_strdupa(data ? data : ""); + if ((tmp = strchr(host, '@'))) { + host = tmp + 1; + } + + ast_debug(3, "Checking presence state for peer %s\n", host); + + if ((p = sip_find_peer(host, NULL, FALSE, FINDALLDEVICES, TRUE, 0))) { + if (!(ast_sockaddr_isnull(&p->addr) && ast_sockaddr_isnull(&p->defaddr))) { + if (p->donotdisturb) { + res = AST_PRESENCE_DND; + } else { + res = AST_PRESENCE_AVAILABLE; + } + } + sip_unref_peer(p, "sip_unref_peer, from sip_presencestate, release ref from sip_find_peer"); + } + + return res; +} + /*! \brief PBX interface function -build SIP pvt structure * SIP calls initiated by the PBX arrive here. * @@ -31293,9 +35760,21 @@ } else if (!strcasecmp(v->name, "rfc2833compensate")) { ast_set_flag(&mask[1], SIP_PAGE2_RFC2833_COMPENSATE); ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_RFC2833_COMPENSATE); - } else if (!strcasecmp(v->name, "buggymwi")) { - ast_set_flag(&mask[1], SIP_PAGE2_BUGGY_MWI); - ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_BUGGY_MWI); + } else if (!strcasecmp(v->name, "dndbusy")) { + ast_set_flag(&mask[2], SIP_PAGE3_DND_BUSY); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_DND_BUSY); + } else if (!strcasecmp(v->name, "huntgroup_default")) { + ast_set_flag(&mask[2], SIP_PAGE3_HUNTGROUP_DEFAULT); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_HUNTGROUP_DEFAULT); + } else if (!strcasecmp(v->name, "cisco_usecallmanager")) { + ast_set_flag(&mask[1], SIP_PAGE2_CISCO_USECALLMANAGER); + ast_set2_flag(&flags[1], ast_true(v->value), SIP_PAGE2_CISCO_USECALLMANAGER); + } else if (!strcasecmp(v->name, "cisco_keep_conference")) { + ast_set_flag(&mask[2], SIP_PAGE3_CISCO_KEEP_CONFERENCE); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_KEEP_CONFERENCE); + } else if (!strcasecmp(v->name, "cisco_multiadmin_conference")) { + ast_set_flag(&mask[2], SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); + ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE); } else if (!strcasecmp(v->name, "rtcp_mux")) { ast_set_flag(&mask[2], SIP_PAGE3_RTCP_MUX); ast_set2_flag(&flags[2], ast_true(v->value), SIP_PAGE3_RTCP_MUX); @@ -31561,7 +36040,6 @@ peer->stimer.st_max_se = global_max_se; peer->timer_t1 = global_t1; peer->timer_b = global_timer_b; - clear_peer_mailboxes(peer); peer->disallowed_methods = sip_cfg.disallowed_methods; peer->transports = default_transports; peer->default_outbound_transport = default_primary_transport; @@ -31569,6 +36047,8 @@ ao2_ref(peer->outboundproxy, -1); peer->outboundproxy = NULL; } + peer->cisco_lineindex = 1; + peer->cisco_pickupnotify_timer = 5; } /*! \brief Create temporary peer (used in autocreatepeer mode) */ @@ -31620,7 +36100,6 @@ while ((mbox = strsep(&next, ","))) { struct sip_mailbox *mailbox; - int duplicate = 0; /* remove leading/trailing whitespace from mailbox string */ mbox = ast_strip(mbox); @@ -31631,12 +36110,11 @@ /* Check whether the mailbox is already in the list */ AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { if (!strcmp(mailbox->id, mbox)) { - duplicate = 1; - mailbox->status = SIP_MAILBOX_STATUS_EXISTING; break; } } - if (duplicate) { + if (mailbox) { + mailbox->status = SIP_MAILBOX_STATUS_EXISTING; continue; } @@ -31652,6 +36130,87 @@ } } +static void add_peer_alias(struct sip_peer *peer, const char *value, int *lineindex) +{ + char *next, *name; + struct sip_alias *alias; + + next = ast_strdupa(value); + + while ((name = strsep(&next, ","))) { + name = ast_strip(name); + + if (ast_strlen_zero(name)) { + continue; + } + + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + if (!strcmp(alias->name, name)) { + break; + } + } + + if (alias) { + alias->removed = 0; + } else { + if (!(alias = ast_calloc(1, sizeof(*alias)))) { + return; + } + + if (!(alias->name = ast_strdup(name))) { + ast_free(alias); + return; + } + + AST_LIST_INSERT_TAIL(&peer->aliases, alias, entry); + } + + alias->lineindex = (*lineindex)++; + } +} + +static void add_peer_subscription(struct sip_peer *peer, const char *value) +{ + char *next, *exten, *context; + struct sip_subscription *subscription; + + next = ast_strdupa(value); + + while ((exten = strsep(&next, ","))) { + if ((context = strchr(exten, '@'))) { + *context++ = '\0'; + context = ast_strip(context); + } else { + context = ast_strdupa(S_OR(peer->subscribecontext, peer->context)); + } + + exten = ast_strip(exten); + + if (ast_strlen_zero(exten) || ast_strlen_zero(context)) { + continue; + } + + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + if (!strcmp(subscription->exten, exten) && !strcmp(subscription->context, context)) { + break; + } + } + + if (subscription) { + subscription->removed = 0; + } else { + if (!(subscription = ast_calloc_with_stringfields(1, struct sip_subscription, 32))) { + return; + } + + ast_string_field_set(subscription, exten, exten); + ast_string_field_set(subscription, context, context); + + AST_LIST_INSERT_TAIL(&peer->subscriptions, subscription, entry); + } + } +} + /*! \brief Build peer from configuration (file or realtime static/dynamic) */ static struct sip_peer *build_peer(const char *name, struct ast_variable *v_head, struct ast_variable *alt, int realtime, int devstate_only) { @@ -31675,6 +36234,7 @@ int alt_fullcontact = alt ? 1 : 0, headercount = 0; struct ast_str *fullcontact = ast_str_alloca(512); int acl_change_subscription_needed = 0; + int lineindex = 2; if (!realtime || ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) { /* Note we do NOT use sip_find_peer here, to avoid realtime recursion */ @@ -31772,9 +36332,18 @@ if (!devstate_only) { struct sip_mailbox *mailbox; + struct sip_alias *alias; + struct sip_subscription *subscription; + AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) { mailbox->status = SIP_MAILBOX_STATUS_UNKNOWN; } + AST_LIST_TRAVERSE(&peer->aliases, alias, entry) { + alias->removed = 1; + } + AST_LIST_TRAVERSE(&peer->subscriptions, subscription, entry) { + subscription->removed = 1; + } } /* clear named callgroup and named pickup group container */ @@ -31988,7 +36557,7 @@ } else if (!strcasecmp(v->name, "regexten")) { ast_string_field_set(peer, regexten, v->value); } else if (!strcasecmp(v->name, "callbackextension")) { - ast_string_field_set(peer, callback, v->value); + ast_string_field_set(peer, callbackexten, v->value); } else if (!strcasecmp(v->name, "amaflags")) { format = ast_channel_string2amaflag(v->value); if (format < 0) { @@ -32158,6 +36727,40 @@ ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL); } else if (!strcasecmp(v->name, "force_avp")) { ast_set2_flag(&peer->flags[2], ast_true(v->value), SIP_PAGE3_FORCE_AVP); + } else if (!strcasecmp(v->name, "register")) { + if (!strcasecmp(peer->name, v->value)) { + ast_log(LOG_WARNING, "Invalid register '%s', same name as peer at line %d of %s\n", v->value, v->lineno, config); + } else { + add_peer_alias(peer, v->value, &lineindex); + } + } else if (!strcasecmp(v->name, "subscribe")) { + add_peer_subscription(peer, v->value); + } else if (!strcasecmp(v->name, "cisco_pickupnotify_alert")) { + char *val = ast_strdupa(v->value); + char *alert; + + ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); + + while ((alert = strsep(&val, ","))) { + if (!strcasecmp(alert, "none")) { + ast_clear_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); + } else if (!strcasecmp(alert, "from")) { + ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM); + } else if (!strcasecmp(alert, "to")) { + ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_TO); + } else if (!strcasecmp(alert, "beep")) { + ast_set_flag(&peer->flags[2], SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP); + } else { + ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_alert '%s' at line %d of %s\n", alert, v->lineno, config); + } + } + } else if (!strcasecmp(v->name, "cisco_pickupnotify_timer")) { + if (sscanf(v->value, "%030d", &peer->cisco_pickupnotify_timer) != 1 || peer->cisco_pickupnotify_timer < 0 || peer->cisco_pickupnotify_timer > 60) { + ast_log(LOG_WARNING, "Invalid cisco_pickupnotify_timer '%s' at line %d of %s\n", v->value, v->lineno, config); + peer->cisco_pickupnotify_timer = 5; + } + } else if (!strcasecmp(v->name, "cisco_qrt_url")) { + ast_string_field_set(peer, cisco_qrt_url, v->value); } else { ast_rtp_dtls_cfg_parse(&peer->dtls_cfg, v->name, v->value); } @@ -32240,6 +36843,9 @@ if (!devstate_only) { struct sip_mailbox *mailbox; + struct sip_alias *alias; + struct sip_subscription *subscription; + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->mailboxes, mailbox, entry) { if (mailbox->status == SIP_MAILBOX_STATUS_UNKNOWN) { AST_LIST_REMOVE_CURRENT(entry); @@ -32247,6 +36853,20 @@ } } AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->aliases, alias, entry) { + if (alias->removed) { + AST_LIST_REMOVE_CURRENT(entry); + destroy_alias(alias); + } + } + AST_LIST_TRAVERSE_SAFE_END; + AST_LIST_TRAVERSE_SAFE_BEGIN(&peer->subscriptions, subscription, entry) { + if (subscription->removed) { + AST_LIST_REMOVE_CURRENT(entry); + destroy_subscription(subscription); + } + } + AST_LIST_TRAVERSE_SAFE_END; } if (!can_parse_xml && (ast_get_cc_agent_policy(peer->cc_params) == AST_CC_AGENT_NATIVE)) { @@ -32407,6 +37027,22 @@ reg_source_db(peer); } + if (!peer->is_realtime) { + char data[128]; + + if (!ast_db_get("SIP/DoNotDisturb", peer->name, data, sizeof(data))) { + peer->donotdisturb = ast_true(data); + } + if (!ast_db_get("SIP/CallForward", peer->name, data, sizeof(data))) { + ast_string_field_set(peer, callforward, data); + } + if (!ast_db_get("SIP/HuntGroup", peer->name, data, sizeof(data))) { + peer->huntgroup = ast_true(data); + } else { + peer->huntgroup = ast_test_flag(&peer->flags[2], SIP_PAGE3_HUNTGROUP_DEFAULT) ? 1 : 0; + } + } + /* If they didn't request that MWI is sent *only* on subscribe, go ahead and * subscribe to it now. */ if (!devstate_only && !ast_test_flag(&peer->flags[1], SIP_PAGE2_SUBSCRIBEMWIONLY) && @@ -32415,7 +37051,12 @@ /* Send MWI from the event cache only. This is so we can send initial * MWI if app_voicemail got loaded before chan_sip. If it is the other * way, then we will get events when app_voicemail gets loaded. */ - sip_send_mwi_to_peer(peer, 1); + sip_send_mwi(peer, 1); + } + + if (!devstate_only && ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER) && + !AST_LIST_EMPTY(&peer->subscriptions)) { + extensionstate_subscriptions(peer); } peer->the_mark = 0; @@ -32423,9 +37064,9 @@ oldacl = ast_free_acl_list(oldacl); oldcontactacl = ast_free_acl_list(oldcontactacl); olddirectmediaacl = ast_free_acl_list(olddirectmediaacl); - if (!ast_strlen_zero(peer->callback)) { /* build string from peer info */ + if (!ast_strlen_zero(peer->callbackexten)) { /* build string from peer info */ char *reg_string; - if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callback) >= 0) { + if (ast_asprintf(®_string, "%s?%s:%s:%s@%s/%s", peer->name, S_OR(peer->fromuser, peer->username), S_OR(peer->remotesecret, peer->secret), peer->username, peer->tohost, peer->callbackexten) >= 0) { sip_register(reg_string, 0); /* XXX TODO: count in registry_count */ ast_free(reg_string); } @@ -34043,6 +38684,7 @@ static char *app_dtmfmode = "SIPDtmfMode"; static char *app_sipaddheader = "SIPAddHeader"; static char *app_sipremoveheader = "SIPRemoveHeader"; +static char *app_ciscopage = "SIPCiscoPage"; #ifdef TEST_FRAMEWORK static char *app_sipsendcustominfo = "SIPSendCustomINFO"; #endif @@ -34174,6 +38816,425 @@ return 0; } +/*! \brief options for SIPCiscoPage() */ +enum { + APP_CISCOPAGE_MULTICAST = 1 << 0, + APP_CISCOPAGE_PORT = 1 << 1, + APP_CISCOPAGE_VOLUME = 1 << 2, + APP_CISCOPAGE_DISPLAY = 1 << 3, + APP_CISCOPAGE_INCLUDEBUSY = 1 << 4, + APP_CISCOPAGE_OFFHOOK = 1 << 5, + APP_CISCOPAGE_BEEP = 1 << 6 +}; + +enum { + APP_CISCOPAGE_ARG_MULTICAST, + APP_CISCOPAGE_ARG_PORT, + APP_CISCOPAGE_ARG_VOLUME, + APP_CISCOPAGE_ARG_DISPLAY, + APP_CISCOPAGE_ARG_ARRAY_SIZE +}; + +AST_APP_OPTIONS(app_ciscopage_opts, BEGIN_OPTIONS + AST_APP_OPTION_ARG('m', APP_CISCOPAGE_MULTICAST, APP_CISCOPAGE_ARG_MULTICAST), + AST_APP_OPTION_ARG('p', APP_CISCOPAGE_PORT, APP_CISCOPAGE_ARG_PORT), + AST_APP_OPTION_ARG('v', APP_CISCOPAGE_VOLUME, APP_CISCOPAGE_ARG_VOLUME), + AST_APP_OPTION_ARG('d', APP_CISCOPAGE_DISPLAY, APP_CISCOPAGE_ARG_DISPLAY), + AST_APP_OPTION('b', APP_CISCOPAGE_INCLUDEBUSY), + AST_APP_OPTION('o', APP_CISCOPAGE_OFFHOOK), + AST_APP_OPTION('a', APP_CISCOPAGE_BEEP) +END_OPTIONS); + +struct page_target { + struct sip_peer *peer; + struct ast_rtp_instance *rtp; + AST_LIST_ENTRY(page_target) entry; +}; + +static int sip_ciscopage(struct ast_channel *chan, const char *data) +{ + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(peernames); + AST_APP_ARG(options); + ); + struct ast_flags options = { 0, }; + char *option_args[APP_CISCOPAGE_ARG_ARRAY_SIZE]; + char *parse, *peername, *codec, display[64]; + int volume, port, multicast, includebusy, offhook, beep; + struct ast_format *format; + AST_LIST_HEAD_NOLOCK(, page_target) targets; + struct page_target *target; + struct ast_str *content = NULL; + struct ast_rtp_instance *mrtp = NULL; + struct ast_sockaddr ouraddr; + int res = -1; + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Cannot call %s without arguments\n", app_ciscopage); + return -1; + } + + parse = ast_strdupa(data); + AST_STANDARD_APP_ARGS(args, parse); + + if (ast_strlen_zero(args.peernames)) { + ast_log(LOG_ERROR, "No peer names specified\n"); + return -1; + } + + if (!ast_strlen_zero(args.options)) { + ast_app_parse_options(app_ciscopage_opts, &options, option_args, args.options); + } + + if (ast_test_flag(&options, APP_CISCOPAGE_MULTICAST) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_MULTICAST])) { + if (!ast_sockaddr_parse(&ouraddr, option_args[APP_CISCOPAGE_ARG_MULTICAST], PARSE_PORT_FORBID)) { + ast_log(LOG_ERROR, "Invalid IP address '%s'\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); + return -1; + } + + if (!ast_sockaddr_is_ipv4_multicast(&ouraddr)) { + ast_log(LOG_ERROR, "IP address '%s' is not multicast\n", option_args[APP_CISCOPAGE_ARG_MULTICAST]); + return -1; + } + + multicast = 1; + } else { + multicast = 0; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_PORT) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_PORT])) { + port = strtol(option_args[APP_CISCOPAGE_ARG_PORT], NULL, 10); + if (port < 20480 || port > 32768 || port % 2) { + ast_log(LOG_ERROR, "Invalid port option '%s'\n", option_args[APP_CISCOPAGE_ARG_PORT]); + return -1; + } + } else { + port = 20480; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_VOLUME) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_VOLUME])) { + volume = strtol(option_args[APP_CISCOPAGE_ARG_VOLUME], NULL, 10); + if (volume < 1 || volume > 100) { + ast_log(LOG_ERROR, "Invalid volume option '%s'\n", option_args[APP_CISCOPAGE_ARG_VOLUME]); + return -1; + } + } else { + volume = -1; + } + + if (ast_test_flag(&options, APP_CISCOPAGE_DISPLAY) && !ast_strlen_zero(option_args[APP_CISCOPAGE_ARG_DISPLAY])) { + ast_xml_escape(option_args[APP_CISCOPAGE_ARG_DISPLAY], display, sizeof(display)); + } else { + display[0] = '\0'; + } + + beep = ast_test_flag(&options, APP_CISCOPAGE_BEEP); + includebusy = ast_test_flag(&options, APP_CISCOPAGE_INCLUDEBUSY); + offhook = ast_test_flag(&options, APP_CISCOPAGE_OFFHOOK); + + format = ast_channel_readformat(chan); + if (ast_format_cmp(format, ast_format_ulaw) == AST_FORMAT_CMP_EQUAL || ast_format_cmp(format, ast_format_alaw) == AST_FORMAT_CMP_EQUAL) { + codec = "G.711"; + } else if (ast_format_cmp(format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) { + codec = "G.722"; + } else if (ast_format_cmp(format, ast_format_g729) == AST_FORMAT_CMP_EQUAL) { + codec = "G.729"; + } else { + ast_log(LOG_ERROR, "Unsupported codec format\n"); + return -1; + } + + content = ast_str_create(8192); + AST_LIST_HEAD_INIT_NOLOCK(&targets); + + while ((peername = strsep(&args.peernames, "&"))) { + struct sip_peer *peer; + struct sip_pvt *pvt; + struct ast_rtp_instance *rtp = NULL; + struct ast_sockaddr theiraddr; + + if (!(peer = sip_find_peer(peername, NULL, TRUE, FINDPEERS, FALSE, 0))) { + ast_log(LOG_ERROR, "No such peer '%s'\n", peername); + continue; + } + + if (!ast_test_flag(&peer->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) { + ast_log(LOG_ERROR, "Peer '%s' does not have cisco_usecallmanager enabled\n", peer->name); + sip_unref_peer(peer, "unref peer"); + continue; + } + + if (ast_sockaddr_isnull(&peer->addr)) { + ast_log(LOG_ERROR, "Peer '%s' is not registered\n", peer->name); + sip_unref_peer(peer, "unref peer"); + continue; + } + + if ((peer->offhook || peer->ringing || peer->inuse || peer->donotdisturb) && !includebusy) { + sip_unref_peer(peer, "unref peer"); + continue; + } + + if (!multicast) { + /* We only need the socket type for ast_sip_ouraddrfor */ + struct sip_pvt pvt = { .socket.type = peer->socket.type }; + + ast_sip_ouraddrfor(&peer->addr, &ouraddr, &pvt); + ast_sockaddr_copy(&theiraddr, &peer->addr); + ast_sockaddr_set_port(&theiraddr, port); + + if (!(rtp = ast_rtp_instance_new(peer->engine, sched, &ouraddr, NULL))) { + sip_unref_peer(peer, "unref peer"); + goto ciscopage_cleanup; + } + + ast_rtp_instance_set_write_format(rtp, ast_channel_readformat(chan)); + ast_rtp_instance_set_remote_address(rtp, &theiraddr); + + ast_rtp_instance_set_qos(rtp, global_tos_audio, global_cos_audio, "SIP RTP"); + ast_rtp_instance_activate(rtp); + } + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); + sip_unref_peer(peer, "unref peer"); + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + continue; + } + + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); + sip_unref_peer(peer, "unref peer"); + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + continue; + } + + ast_str_reset(content); + + if (!ast_strlen_zero(display)) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "notify_display\n"); + ast_str_append(&content, 0, "%s\n", display); + ast_str_append(&content, 0, "10\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "1\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + if (beep) { + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "DtZipZip\n"); + ast_str_append(&content, 0, "all\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + } + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + if (volume != -1) { + ast_str_append(&content, 0, "\n", volume); + } else { + ast_str_append(&content, 0, "\n"); + } + ast_str_append(&content, 0, "audio\n"); + ast_str_append(&content, 0, "%s\n", codec); + ast_str_append(&content, 0, "receive\n"); + ast_str_append(&content, 0, "
%s
\n", ast_sockaddr_stringify_fmt(&ouraddr, AST_SOCKADDR_STR_ADDR)); + ast_str_append(&content, 0, "%d\n", port); + ast_str_append(&content, 0, "
\n"); + ast_str_append(&content, 0, "
\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "drop pvt"); + + if (!(target = ast_calloc(1, sizeof(*target)))) { + sip_unref_peer(peer, "unref peer"); + if (rtp) { + ast_rtp_instance_destroy(rtp); + } + goto ciscopage_cleanup; + } + + if (offhook) { + ao2_lock(peer); + peer->offhook += 1; + ao2_unlock(peer); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", peer->name); + } + + target->peer = peer; + target->rtp = rtp; + + AST_LIST_INSERT_TAIL(&targets, target, entry); + } + + if (AST_LIST_EMPTY(&targets)) { + ast_free(content); + return -1; + } + + if (multicast) { + ast_sockaddr_set_port(&ouraddr, port); + + if (!(mrtp = ast_rtp_instance_new("multicast", sched, &bindaddr, "basic"))) { + goto ciscopage_cleanup; + } + + ast_rtp_instance_set_write_format(mrtp, ast_channel_readformat(chan)); + ast_rtp_instance_set_remote_address(mrtp, &ouraddr); + + ast_rtp_instance_set_qos(mrtp, global_tos_audio, global_cos_audio, "SIP RTP"); + ast_rtp_instance_activate(mrtp); + } + + if (ast_channel_state(chan) != AST_STATE_UP) { + if ((res = ast_answer(chan))) { + goto ciscopage_cleanup; + } + } + + /* Wait 500ms for phones to accept the media stream request */ + if ((res = ast_safe_sleep(chan, 500))) { + goto ciscopage_cleanup; + } + + for (;;) { + struct ast_frame *frame; + + if (ast_waitfor(chan, 10000) < 1) { + break; + } + frame = ast_read(chan); + + if (!frame || (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_HANGUP)) { + if (frame) { + ast_frfree(frame); + } + break; + } + + if (frame->frametype == AST_FRAME_VOICE) { + if (mrtp) { + ast_rtp_instance_write(mrtp, frame); + } else { + AST_LIST_TRAVERSE(&targets, target, entry) { + ast_rtp_instance_write(target->rtp, frame); + } + } + } + + ast_frfree(frame); + } + + res = 0; + +ciscopage_cleanup: + while ((target = AST_LIST_REMOVE_HEAD(&targets, entry))) { + struct sip_pvt *pvt; + + if (!(pvt = sip_alloc(NULL, NULL, 0, SIP_REFER, NULL, 0))) { + ast_log(LOG_ERROR, "Unable to build sip pvt data for refer (memory/socket error)\n"); + } else { + set_socket_transport(&pvt->socket, 0); + if (create_addr_from_peer(pvt, target->peer)) { + dialog_unlink_all(pvt); + dialog_unref(pvt, "create_addr_from_peer failed. unref dialog"); + pvt = NULL; + } + } + + if (pvt) { + ast_str_reset(content); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-request+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "StationSequenceLast\n"); + ast_str_append(&content, 0, "2\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "0\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + + ast_str_append(&content, 0, "--uniqueBoundary\r\n"); + ast_str_append(&content, 0, "Content-Type: application/x-cisco-remotecc-cm+xml\r\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\n"); + ast_str_append(&content, 0, "\r\n"); + ast_str_append(&content, 0, "--uniqueBoundary--\r\n"); + + transmit_refer_with_content(pvt, "multipart/mixed; boundary=uniqueBoundary", ast_str_buffer(content)); + dialog_unref(pvt, "drop pvt"); + } + + if (offhook) { + ao2_lock(target->peer); + target->peer->offhook -= 1; + ao2_unlock(target->peer); + ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "SIP/%s", target->peer->name); + } + + sip_unref_peer(target->peer, "unref peer"); + if (target->rtp) { + ast_rtp_instance_destroy(target->rtp); + } + ast_free(target); + } + + if (mrtp) { + ast_rtp_instance_destroy(mrtp); + } + ast_free(content); + + return res; +} + #ifdef TEST_FRAMEWORK /*! \brief Send a custom INFO message via AST_CONTROL_CUSTOM indication */ static int sip_sendcustominfo(struct ast_channel *chan, const char *data) @@ -34279,6 +39340,11 @@ i = ao2_iterator_init(peers, 0); while ((peer = ao2_t_iterator_next(&i, "iterate thru peers table"))) { ao2_lock(peer); + /* Only poke the primary line */ + if (peer->cisco_lineindex > 1) { + ao2_unlock(peer); + continue; + } /* Don't schedule poking on a peer without qualify */ if (peer->maxms) { if (num == global_qualify_peers) { @@ -34424,6 +39490,9 @@ /* Send qualify (OPTIONS) to all peers */ sip_poke_all_peers(); + /* Register aliases now that all peers have been added */ + register_all_aliases(); + /* Send keepalive to all peers */ sip_keepalive_all_peers(); @@ -34570,9 +39639,9 @@ static int peer_ipcmp_cb_full(void *obj, void *arg, void *data, int flags) { struct sip_peer *peer = obj, *peer2 = arg; - char *callback = data; + char *callbackexten = data; - if (!ast_strlen_zero(callback) && strcasecmp(peer->callback, callback)) { + if (!ast_strlen_zero(callbackexten) && strcasecmp(peer->callbackexten, callbackexten)) { /* We require a callback extension match, but don't have one */ return 0; } @@ -34734,6 +39803,9 @@ AST_CLI_DEFINE(sip_show_settings, "Show SIP global settings"), AST_CLI_DEFINE(sip_show_mwi, "Show MWI subscriptions"), AST_CLI_DEFINE(sip_cli_notify, "Send a notify packet to a SIP peer"), + AST_CLI_DEFINE(sip_cli_donotdisturb, "Enables/Disables do not disturb on a SIP peer"), + AST_CLI_DEFINE(sip_cli_callforward, "Sets/Removes the call forwarding extension for a SIP peer"), + AST_CLI_DEFINE(sip_cli_huntgroup, "Login to/Logout from Hunt Group for a SIP peer"), AST_CLI_DEFINE(sip_show_channel, "Show detailed SIP channel info"), AST_CLI_DEFINE(sip_show_history, "Show SIP dialog history"), AST_CLI_DEFINE(sip_show_peer, "Show details on specific SIP peer"), @@ -35612,6 +40684,7 @@ ast_register_application_xml(app_dtmfmode, sip_dtmfmode); ast_register_application_xml(app_sipaddheader, sip_addheader); ast_register_application_xml(app_sipremoveheader, sip_removeheader); + ast_register_application_xml(app_ciscopage, sip_ciscopage); #ifdef TEST_FRAMEWORK ast_register_application_xml(app_sipsendcustominfo, sip_sendcustominfo); #endif @@ -35686,6 +40759,7 @@ sip_register_tests(); network_change_stasis_subscribe(); + pickup_notify_stasis_subscribe(); if (sip_cfg.websocket_enabled) { ast_websocket_add_protocol("sip", sip_websocket_callback); @@ -35712,6 +40786,7 @@ network_change_stasis_unsubscribe(); acl_change_event_stasis_unsubscribe(); + pickup_notify_stasis_unsubscribe(); /* First, take us out of the channel type list */ ast_channel_unregister(&sip_tech); diff -durN asterisk-16.12.0.orig/channels/sip/config_parser.c asterisk-16.12.0/channels/sip/config_parser.c --- asterisk-16.12.0.orig/channels/sip/config_parser.c 2020-07-28 15:08:00.360838824 +1200 +++ asterisk-16.12.0/channels/sip/config_parser.c 2020-07-28 15:22:32.872256402 +1200 @@ -254,7 +254,7 @@ } /* copy into sip_registry object */ - ast_string_field_set(reg, callback, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); + ast_string_field_set(reg, exten, ast_strip_quoted(S_OR(host2.extension, "s"), "\"", "\"")); ast_string_field_set(reg, username, ast_strip_quoted(S_OR(user2.user, ""), "\"", "\"")); ast_string_field_set(reg, hostname, ast_strip_quoted(S_OR(host3.host, ""), "\"", "\"")); ast_string_field_set(reg, authuser, ast_strip_quoted(S_OR(user3.authuser, ""), "\"", "\"")); @@ -309,7 +309,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg1, 1) || - strcmp(reg->callback, "s") || + strcmp(reg->exten, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "") || strcmp(reg->hostname, "domain") || @@ -338,7 +338,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg2, 1) || - strcmp(reg->callback, "s") || + strcmp(reg->exten, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "") || strcmp(reg->hostname, "domain") || @@ -367,7 +367,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg3, 1) || - strcmp(reg->callback, "s") || + strcmp(reg->exten, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -396,7 +396,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg4, 1) || - strcmp(reg->callback, "extension") || + strcmp(reg->exten, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -425,7 +425,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg5, 1) || - strcmp(reg->callback, "extension") || + strcmp(reg->exten, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -454,7 +454,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg6, 1) || - strcmp(reg->callback, "extension") || + strcmp(reg->exten, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -483,7 +483,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg7, 1) || - strcmp(reg->callback, "extension") || + strcmp(reg->exten, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -512,7 +512,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg8, 1) || - strcmp(reg->callback, "extension") || + strcmp(reg->exten, "extension") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -582,7 +582,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg12, 1) || - strcmp(reg->callback, "s") || + strcmp(reg->exten, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || @@ -611,7 +611,7 @@ goto alloc_fail; } else if ( sip_parse_register_line(reg, default_expiry, reg13, 1) || - strcmp(reg->callback, "s") || + strcmp(reg->exten, "s") || strcmp(reg->username, "name") || strcmp(reg->regdomain, "namedomain") || strcmp(reg->hostname, "domain") || diff -durN asterisk-16.12.0.orig/channels/sip/include/sip.h asterisk-16.12.0/channels/sip/include/sip.h --- asterisk-16.12.0.orig/channels/sip/include/sip.h 2020-07-28 15:08:00.360838824 +1200 +++ asterisk-16.12.0/channels/sip/include/sip.h 2020-07-28 15:22:32.872256402 +1200 @@ -37,6 +37,7 @@ #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" #include "asterisk/features_config.h" +#include "asterisk/stasis.h" #include "route.h" @@ -107,7 +108,7 @@ #define SIP_MIN_PACKET 4096 /*!< Initialize size of memory to allocate for packets */ #define MAX_HISTORY_ENTRIES 50 /*!< Max entires in the history list for a sip_pvt */ -#define INITIAL_CSEQ 101 /*!< Our initial sip sequence number */ +#define INITIAL_CSEQ 100 /*!< Our initial sip sequence number */ #define DEFAULT_MAX_SE 1800 /*!< Session-Timer Default Session-Expires period (RFC 4028) */ #define DEFAULT_MIN_SE 90 /*!< Session-Timer Default Min-SE period (RFC 4028) */ @@ -347,7 +348,7 @@ #define SIP_PAGE2_CALL_ONHOLD_INACTIVE (3 << 19) /*!< D: Inactive hold */ #define SIP_PAGE2_RFC2833_COMPENSATE (1 << 21) /*!< DP: Compensate for buggy RFC2833 implementations */ -#define SIP_PAGE2_BUGGY_MWI (1 << 22) /*!< DP: Buggy CISCO MWI fix */ +#define SIP_PAGE2_CISCO_USECALLMANAGER (1 << 22) /*!< DP: Enable Cisco USECALLMANAGER phone support */ #define SIP_PAGE2_DIALOG_ESTABLISHED (1 << 23) /*!< 29: Has a dialog been established? */ #define SIP_PAGE2_FAX_DETECT (3 << 24) /*!< DP: Fax Detection support */ @@ -368,7 +369,7 @@ #define SIP_PAGE2_FLAGS_TO_COPY \ (SIP_PAGE2_ALLOWSUBSCRIBE | SIP_PAGE2_ALLOWOVERLAP | SIP_PAGE2_IGNORESDPVERSION | \ SIP_PAGE2_VIDEOSUPPORT | SIP_PAGE2_T38SUPPORT | SIP_PAGE2_RFC2833_COMPENSATE | \ - SIP_PAGE2_BUGGY_MWI | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ + SIP_PAGE2_CISCO_USECALLMANAGER | SIP_PAGE2_TEXTSUPPORT | SIP_PAGE2_FAX_DETECT | \ SIP_PAGE2_UDPTL_DESTINATION | SIP_PAGE2_VIDEOSUPPORT_ALWAYS | SIP_PAGE2_PREFERRED_CODEC | \ SIP_PAGE2_RPID_IMMEDIATE | SIP_PAGE2_RPID_UPDATE | SIP_PAGE2_SYMMETRICRTP |\ SIP_PAGE2_Q850_REASON | SIP_PAGE2_HAVEPEERCONTEXT | SIP_PAGE2_USE_SRTP | SIP_PAGE2_TRUST_ID_OUTBOUND) @@ -385,11 +386,28 @@ #define SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL (1 << 8) /*!< DGP: Stop telling the peer to start music on hold */ #define SIP_PAGE3_FORCE_AVP (1 << 9) /*!< DGP: Force 'RTP/AVP' for all streams, even DTLS */ #define SIP_PAGE3_RTCP_MUX (1 << 10) /*!< DGP: Attempt to negotiate RFC 5761 RTCP multiplexing */ +#define SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS (1 << 11) /*!< Duh */ +#define SIP_PAGE3_DND_BUSY (1 << 12) /*!< DPG: Treat endpoint as busy when DND is enabled */ +#define SIP_PAGE3_SUBSCRIPTIONSTATE_ACTIVE (1 << 13) /*!< D: Force Subscription-State to be active for NOTIFYs */ +#define SIP_PAGE3_CISCO_KEEP_CONFERENCE (1 << 14) /*!< DGP: Keep ad-hoc conference after initiator hangs up */ +#define SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE (1 << 15) /*!<< DGP: Allow participants to administrate conference */ +#define SIP_PAGE3_RELAY_NEAREND (1 << 16) /*!< D: Add x-relay-nearend attribute to SDP */ +#define SIP_PAGE3_RELAY_FAREND (1 << 17) /*!< D: Add x-relay-farend attribute to SDP */ +#define SIP_PAGE3_SDP_ACK (1 << 18) /*!< D: Add SDP to ACK */ +#define SIP_PAGE3_CISCO_RECORDING (1 << 19) /*!< D: Call is now being recorded */ +#define SIP_PAGE3_RTP_STATS_ON_BYE (1 << 20) /*!< D: Display RTP stats on BYE */ +#define SIP_PAGE3_HUNTGROUP_DEFAULT (1 << 21) /*!< GP: If peer is logged into huntgroup by default */ +#define SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM (1 << 22) /*!> P: Include the from number in the statusline for pickup notify */ +#define SIP_PAGE3_CISCO_PICKUPNOTIFY_TO (1 << 23) /*!> P: Include the to number in the statusline for pickup notify */ +#define SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP (1 << 24) /*!> P: Play a beep for pickup notify */ #define SIP_PAGE3_FLAGS_TO_COPY \ (SIP_PAGE3_SNOM_AOC | SIP_PAGE3_SRTP_TAG_32 | SIP_PAGE3_NAT_AUTO_RPORT | SIP_PAGE3_NAT_AUTO_COMEDIA | \ SIP_PAGE3_DIRECT_MEDIA_OUTGOING | SIP_PAGE3_USE_AVPF | SIP_PAGE3_ICE_SUPPORT | SIP_PAGE3_IGNORE_PREFCAPS | \ - SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX) + SIP_PAGE3_DISCARD_REMOTE_HOLD_RETRIEVAL | SIP_PAGE3_FORCE_AVP | SIP_PAGE3_RTCP_MUX | \ + SIP_PAGE3_WHY_DIDNT_DIGIUM_THINK_OF_THIS | SIP_PAGE3_DND_BUSY | SIP_PAGE3_CISCO_KEEP_CONFERENCE | \ + SIP_PAGE3_CISCO_MULTIADMIN_CONFERENCE | SIP_PAGE3_HUNTGROUP_DEFAULT | SIP_PAGE3_CISCO_PICKUPNOTIFY_FROM | \ + SIP_PAGE3_CISCO_PICKUPNOTIFY_TO | SIP_PAGE3_CISCO_PICKUPNOTIFY_BEEP) #define CHECK_AUTH_BUF_INITLEN 256 @@ -471,6 +489,9 @@ PIDF_XML, MWI_NOTIFICATION, CALL_COMPLETION, + PRESENCE, + FEATURE_EVENTS, + REMOTECC_XML, }; /*! \brief The number of media types in enum \ref media_type below. */ @@ -934,10 +955,14 @@ AST_STRING_FIELD(replaces_callid); /*!< Replace info: callid */ AST_STRING_FIELD(replaces_callid_totag); /*!< Replace info: to-tag */ AST_STRING_FIELD(replaces_callid_fromtag); /*!< Replace info: from-tag */ + AST_STRING_FIELD(require); /*!< Outgoing Require header */ + AST_STRING_FIELD(content_id); /*!< Outgoing Content-ID header */ + AST_STRING_FIELD(content_type); /*!< Outgoing Content-Type header */ ); int attendedtransfer; /*!< Attended or blind transfer? */ int localtransfer; /*!< Transfer to local domain? */ enum referstatus status; /*!< REFER status */ + struct ast_str *content; /*!< Outgoing content body */ }; /*! \brief Struct to handle custom SIP notify requests. Dynamically allocated when needed */ @@ -1053,6 +1078,10 @@ AST_STRING_FIELD(msg_body); /*!< Text for a MESSAGE body */ AST_STRING_FIELD(tel_phone_context); /*!< The phone-context portion of a TEL URI */ AST_STRING_FIELD(sessionunique_remote); /*!< Remote UA's SDP Session unique parts */ + AST_STRING_FIELD(callforward); /*!< Call Forward target */ + AST_STRING_FIELD(join_callid); /*!< Join callid */ + AST_STRING_FIELD(join_tag); /*!< Join tag */ + AST_STRING_FIELD(join_theirtag); /*!< Join theirtag */ ); char via[128]; /*!< Via: header */ int maxforwards; /*!< SIP Loop prevention */ @@ -1178,6 +1207,9 @@ struct ast_sdp_srtp *srtp; /*!< Structure to hold Secure RTP session data for audio */ struct ast_sdp_srtp *vsrtp; /*!< Structure to hold Secure RTP session data for video */ struct ast_sdp_srtp *tsrtp; /*!< Structure to hold Secure RTP session data for text */ + int donotdisturb:1; /*!< Peer has set DoNotDisturb */ + struct sip_pvt *recordoutpvt; /*!< Pvt for the outbound recording leg */ + struct sip_pvt *recordinpvt; /*!< Pvt for the inbound recording leg */ int red; /*!< T.140 RTP Redundancy */ int hangupcause; /*!< Storage of hangupcause copied from our owner before we disconnect from the AST channel (only used at hangup) */ @@ -1211,6 +1243,7 @@ struct ast_cc_config_params *cc_params; struct sip_epa_entry *epa_entry; int fromdomainport; /*!< Domain port to show in from field */ + struct sip_conference *conference; /*!< Ad-hoc n-way conference support */ struct ast_rtp_dtls_cfg dtls_cfg; }; @@ -1260,6 +1293,30 @@ char id[1]; }; +/*! + * \brief A peer's bulk register aliases + */ +struct sip_alias { + char *name; + AST_LIST_ENTRY(sip_alias) entry; + struct sip_peer *peer; + int lineindex; + unsigned int removed:1; +}; + +/*! + * \brief A peer's subscription + */ +struct sip_subscription { + struct sip_pvt *pvt; + AST_LIST_ENTRY(sip_subscription) entry; + unsigned int removed:1; + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(exten); + AST_STRING_FIELD(context); + ); +}; + /*! \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 */ @@ -1295,7 +1352,13 @@ AST_STRING_FIELD(zone); /*!< Tonezone for this device */ AST_STRING_FIELD(record_on_feature); /*!< Feature to use when receiving INFO with record: on during a call */ AST_STRING_FIELD(record_off_feature); /*!< Feature to use when receiving INFO with record: off during a call */ - AST_STRING_FIELD(callback); /*!< Callback extension */ + AST_STRING_FIELD(callbackexten); /*!< Callback extension */ + AST_STRING_FIELD(callforward); /*!< Call forwarding extension */ + AST_STRING_FIELD(regcallid); /*!< Call-ID of the REGISTER dialog */ + AST_STRING_FIELD(cisco_authname); /*!< Name of the primary line */ + AST_STRING_FIELD(cisco_softkey); /*!< Name of the last soft-key sent via remotecc */ + AST_STRING_FIELD(cisco_devicename); /*!< Name of the device */ + AST_STRING_FIELD(cisco_qrt_url); /*< QRT URL */ ); struct sip_socket socket; /*!< Socket used for this peer */ enum ast_transport default_outbound_transport; /*!< Peer Registration may change the default outbound transport. @@ -1318,6 +1381,9 @@ int inuse; /*!< Number of calls in use */ int ringing; /*!< Number of calls ringing */ int onhold; /*!< Peer has someone on hold */ + int donotdisturb:1; /*!< Peer has set DoNotDisturb */ + int huntgroup:1; /*!< Peer is logged into the HuntGroup */ + int offhook; /*!< Peer has signalled that they are off-hook */ int call_limit; /*!< Limit of concurrent calls */ unsigned int t38_maxdatagram; /*!< T.38 FaxMaxDatagram override */ int busy_level; /*!< Level of active channels where we signal busy */ @@ -1330,6 +1396,15 @@ /*! Mailboxes that this peer cares about */ AST_LIST_HEAD_NOLOCK(, sip_mailbox) mailboxes; + /*! Bulk register aliases that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_alias) aliases; + + /*! Subscriptions that this peer cares about */ + AST_LIST_HEAD_NOLOCK(, sip_subscription) subscriptions; + + /*! Dialogs selected for joining */ + AST_LIST_HEAD_NOLOCK(, sip_selected) selected; + int maxcallbitrate; /*!< Maximum Bitrate for a video call */ int expire; /*!< When to expire this peer registration */ struct ast_format_cap *caps; /*!< Codec capability */ @@ -1358,11 +1433,16 @@ struct ast_acl_list *directmediaacl; /*!< Restrict what IPs are allowed to interchange direct media with */ struct ast_variable *chanvars; /*!< Variables to set for channel created by user */ struct sip_pvt *mwipvt; /*!< Subscription for MWI */ + struct sip_pvt *fepvt; /*!< Subscription for Feature Events */ + struct sip_callback *callback; /*!< Extension State watcher for Call Back requests */ struct sip_st_cfg stimer; /*!< SIP Session-Timers */ int timer_t1; /*!< The maximum T1 value for the peer */ int timer_b; /*!< The maximum timer B (transaction timeouts) */ int fromdomainport; /*!< The From: domain port */ struct sip_route path; /*!< List of out-of-dialog outgoing routing steps (fm Path headers) */ + int cisco_lineindex; /*!< Line index number */ + int cisco_pickupnotify_timer; /*!< Toast timer for pickup notify */ + time_t cisco_pickupnotify_sent; /*!< Last time a pickup notify was sent */ /*XXX Seems like we suddenly have two flags with the same content. Why? To be continued... */ enum sip_peer_type type; /*!< Distinguish between "user" and "peer" types. This is used solely for CLI and manager commands */ @@ -1401,7 +1481,7 @@ AST_STRING_FIELD(hostname); /*!< Domain or host we register to */ AST_STRING_FIELD(secret); /*!< Password in clear text */ AST_STRING_FIELD(md5secret); /*!< Password in md5 */ - AST_STRING_FIELD(callback); /*!< Contact extension */ + AST_STRING_FIELD(exten); /*!< Contact extension */ AST_STRING_FIELD(peername); /*!< Peer registering to */ AST_STRING_FIELD(localtag); /*!< Local tag generated same time as callid */ ); @@ -1442,6 +1522,50 @@ }; /*! + * \brief Container for server-side n-way conference bridging + */ +struct sip_conference { + int confid; + int next_callid; + struct ast_bridge *bridge; + int keep:1; + int multiadmin:1; + int administrators; + int users; + AST_LIST_HEAD_NOLOCK(, sip_participant) participants; + AST_LIST_ENTRY(sip_conference) entry; +}; + +struct sip_participant { + int callid; + struct sip_conference *conference; + struct ast_channel *chan; + int administrator:1; + int removed:1; + int muted:1; + int talking:1; + AST_LIST_ENTRY(sip_participant) entry; +}; + +struct sip_selected { + AST_DECLARE_STRING_FIELDS( + AST_STRING_FIELD(callid); + AST_STRING_FIELD(tag); + AST_STRING_FIELD(theirtag); + ); + AST_LIST_ENTRY(sip_selected) entry; +}; + +/*! + * \brief Container for peer callback (camp-on) watcher + */ +struct sip_callback { + int stateid; + char *exten; + int busy:1; +}; + +/*! * \brief Definition of an MWI subscription to another server */ struct sip_subscription_mwi { diff -durN asterisk-16.12.0.orig/configs/samples/res_parking.conf.sample asterisk-16.12.0/configs/samples/res_parking.conf.sample --- asterisk-16.12.0.orig/configs/samples/res_parking.conf.sample 2020-07-28 15:08:00.276842058 +1200 +++ asterisk-16.12.0/configs/samples/res_parking.conf.sample 2020-07-28 15:22:32.876256277 +1200 @@ -68,6 +68,7 @@ ; extensions if parkext is set ;parkingtime => 45 ; Number of seconds a call can be parked before returning +;remindertime => 0 ; Number of seconds before sending a reminder, 0 for no reminder. ;comebacktoorigin = yes ; Setting this option configures the behavior of call parking when the ; parked call times out (See the parkingtime option). The default value is 'yes'. diff -durN asterisk-16.12.0.orig/configs/samples/sip.conf.sample asterisk-16.12.0/configs/samples/sip.conf.sample --- asterisk-16.12.0.orig/configs/samples/sip.conf.sample 2020-07-28 15:08:00.276842058 +1200 +++ asterisk-16.12.0/configs/samples/sip.conf.sample 2020-07-28 15:22:32.884256027 +1200 @@ -1602,6 +1602,22 @@ ;defaultip=192.168.0.4 ; IP address to use until registration ;defaultuser=goran ; Username to use when calling this device before registration ; Normally you do NOT need to set this parameter +;dndbusy=yes ; Automatically send back a busy signal when trying to call a peer that is DND +;huntgroup_default=yes ; If peer is logged into huntgroup by default +;cisco_usecallmanager=yes ; Enable Cisco phone features, required to make hints, presence and call-forwarding work, + ; DO NOT enable this on any other type of device +;subscribe=123 ; cisco_usecallmanager phones don't SUBSCRIBE to hints so they need to be configured + ; both in SEPMAC.cnf.xml and here as well +;register=cisco2 ; Cisco phones on register their first line, so any additional lines must be + ; registered by specifying the name of the peer. +;cisco_keep_conference=no ; When there are no more administrators in an ad-hoc conference hang up the other + ; participants +;cisco_multiadmin_conference=yes ; If the participant added to an ad-hoc conference has cisco_usecallmanager=yes and + ; cisco_multiadmin_conference=yes then make them an administrator as well +;cisco_pickupnotify_alert=from,to,beep ; Send an alert to the phone if another phone in the pickup group is ringing. + ; 'from' - show the caller number, 'to' - show the callee number, + ; 'beep' - play a soft beep tone, 'none' - disabled. +;cisco_pickupnotify_timer=5 ; Display timeout for pickup notify when either 'from' or 'to' are used. ;setvar=CUSTID=5678 ; Channel variable to be set for all calls from or to this device ;setvar=ATTENDED_TRANSFER_COMPLETE_SOUND=beep ; This channel variable will ; cause the given audio file to diff -durN asterisk-16.12.0.orig/configs/samples/sip_notify.conf.sample asterisk-16.12.0/configs/samples/sip_notify.conf.sample --- asterisk-16.12.0.orig/configs/samples/sip_notify.conf.sample 2020-07-28 15:08:00.276842058 +1200 +++ asterisk-16.12.0/configs/samples/sip_notify.conf.sample 2020-07-28 15:22:32.884256027 +1200 @@ -55,3 +55,26 @@ [cisco-check-cfg] Event=>check-sync + +[cisco-restart] +Event=>service-control +Subscription-State=>active +Content-Type=>text/plain +Content=>action=restart +Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} + +[cisco-reset] +Event=>service-control +Subscription-State=>active +Content-Type=>text/plain +Content=>action=reset +Content=>RegisterCallId={${SIPPEER(${PEERNAME},regcallid)}} +Content=>ConfigVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>DialplanVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>SoftkeyVersionStamp={00000000-0000-0000-0000-000000000000} +Content=>FeatureControlVersionStamp={00000000-0000-0000-0000-000000000000} + diff -durN asterisk-16.12.0.orig/include/asterisk/parking.h asterisk-16.12.0/include/asterisk/parking.h --- asterisk-16.12.0.orig/include/asterisk/parking.h 2020-07-28 15:08:00.376838208 +1200 +++ asterisk-16.12.0/include/asterisk/parking.h 2020-07-28 15:22:32.884256027 +1200 @@ -50,6 +50,7 @@ PARKED_CALL_UNPARKED, PARKED_CALL_FAILED, PARKED_CALL_SWAP, + PARKED_CALL_REMINDER, }; /*! diff -durN asterisk-16.12.0.orig/main/cel.c asterisk-16.12.0/main/cel.c --- asterisk-16.12.0.orig/main/cel.c 2020-07-28 15:08:00.400837284 +1200 +++ asterisk-16.12.0/main/cel.c 2020-07-28 15:22:32.884256027 +1200 @@ -1148,6 +1148,8 @@ case PARKED_CALL_SWAP: reason = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + return; } if (parked_payload->retriever) { diff -durN asterisk-16.12.0.orig/main/pbx.c asterisk-16.12.0/main/pbx.c --- asterisk-16.12.0.orig/main/pbx.c 2020-07-28 15:08:00.416836668 +1200 +++ asterisk-16.12.0/main/pbx.c 2020-07-28 15:22:32.888255902 +1200 @@ -5223,7 +5223,7 @@ return CLI_SUCCESS; } /* ... we have hints ... */ - ast_cli(a->fd, "\n -= Registered Asterisk Dial Plan Hints =-\n"); + ast_cli(a->fd, "Location Hints DeviceState PresenceState Watchers\n"); i = ao2_iterator_init(hints, 0); for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) { @@ -5250,8 +5250,7 @@ } ao2_iterator_destroy(&i); - ast_cli(a->fd, "----------------\n"); - ast_cli(a->fd, "- %d hints registered\n", num); + ast_cli(a->fd, "%d hints registered\n", num); return CLI_SUCCESS; } diff -durN asterisk-16.12.0.orig/main/presencestate.c asterisk-16.12.0/main/presencestate.c --- asterisk-16.12.0.orig/main/presencestate.c 2020-07-28 15:08:00.416836668 +1200 +++ asterisk-16.12.0/main/presencestate.c 2020-07-28 15:22:32.888255902 +1200 @@ -72,13 +72,13 @@ enum ast_presence_state state; } state2string[] = { - { "not_set", AST_PRESENCE_NOT_SET}, - { "unavailable", AST_PRESENCE_UNAVAILABLE }, - { "available", AST_PRESENCE_AVAILABLE}, - { "away", AST_PRESENCE_AWAY}, - { "xa", AST_PRESENCE_XA}, - { "chat", AST_PRESENCE_CHAT}, - { "dnd", AST_PRESENCE_DND}, + { "Not_Set", AST_PRESENCE_NOT_SET}, + { "Unavailable", AST_PRESENCE_UNAVAILABLE }, + { "Available", AST_PRESENCE_AVAILABLE}, + { "Away", AST_PRESENCE_AWAY}, + { "XA", AST_PRESENCE_XA}, + { "Chat", AST_PRESENCE_CHAT}, + { "DND", AST_PRESENCE_DND}, }; static struct ast_manager_event_blob *presence_state_to_ami(struct stasis_message *msg); diff -durN asterisk-16.12.0.orig/res/parking/parking_applications.c asterisk-16.12.0/res/parking/parking_applications.c --- asterisk-16.12.0.orig/res/parking/parking_applications.c 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/parking_applications.c 2020-07-28 15:22:32.888255902 +1200 @@ -77,6 +77,11 @@ Use a timeout of duration seconds instead of the timeout specified by the parking lot. + @@ -241,6 +246,7 @@ enum park_args { OPT_ARG_COMEBACK, OPT_ARG_TIMEOUT, + OPT_ARG_REMINDER, OPT_ARG_ARRAY_SIZE /* Always the last element of the enum */ }; @@ -250,6 +256,7 @@ MUXFLAG_NOANNOUNCE = (1 << 2), MUXFLAG_COMEBACK_OVERRIDE = (1 << 3), MUXFLAG_TIMEOUT_OVERRIDE = (1 << 4), + MUXFLAG_REMINDER_OVERRIDE = (1 << 5), }; AST_APP_OPTIONS(park_opts, { @@ -258,6 +265,7 @@ AST_APP_OPTION('s', MUXFLAG_NOANNOUNCE), AST_APP_OPTION_ARG('c', MUXFLAG_COMEBACK_OVERRIDE, OPT_ARG_COMEBACK), AST_APP_OPTION_ARG('t', MUXFLAG_TIMEOUT_OVERRIDE, OPT_ARG_TIMEOUT), + AST_APP_OPTION_ARG('T', MUXFLAG_REMINDER_OVERRIDE, OPT_ARG_REMINDER), }); static int apply_option_timeout (int *var, char *timeout_arg) @@ -275,7 +283,22 @@ return 0; } -static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, char **comeback_override, char **lot_name) +static int apply_option_reminder (int *var, char *reminder_arg) +{ + if (ast_strlen_zero(reminder_arg)) { + ast_log(LOG_ERROR, "No duration value provided for the reminder ('T') option.\n"); + return -1; + } + + if (sscanf(reminder_arg, "%d", var) != 1 || *var < 0) { + ast_log(LOG_ERROR, "Duration value provided for timeout ('T') option must be 0 or greater.\n"); + return -1; + } + + return 0; +} + +static int park_app_parse_data(const char *data, int *disable_announce, int *use_ringing, int *randomize, int *time_limit, int *reminder_delay, char **comeback_override, char **lot_name) { char *parse; struct ast_flags flags = { 0 }; @@ -298,6 +321,12 @@ } } + if (ast_test_flag(&flags, MUXFLAG_REMINDER_OVERRIDE)) { + if (apply_option_reminder(reminder_delay, opts[OPT_ARG_REMINDER])) { + return -1; + } + } + if (ast_test_flag(&flags, MUXFLAG_COMEBACK_OVERRIDE)) { *comeback_override = ast_strdup(opts[OPT_ARG_COMEBACK]); } @@ -360,7 +389,7 @@ ast_channel_unlock(chan); } -static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int silence_announce) +static int setup_park_common_datastore(struct ast_channel *parkee, const char *parker_uuid, const char *comeback_override, int randomize, int time_limit, int reminder_delay, int silence_announce) { struct ast_datastore *datastore = NULL; struct park_common_datastore *park_datastore; @@ -412,6 +441,7 @@ park_datastore->randomize = randomize; park_datastore->time_limit = time_limit; + park_datastore->reminder_delay = reminder_delay; park_datastore->silence_announce = silence_announce; if (comeback_override) { @@ -460,6 +490,7 @@ data_copy->randomize = data->randomize; data_copy->time_limit = data->time_limit; + data_copy->reminder_delay = data->reminder_delay; data_copy->silence_announce = data->silence_announce; if (data->comeback_override) { @@ -483,7 +514,7 @@ struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, - int use_ringing, int randomize, int time_limit, int silence_announcements) + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements) { struct ast_bridge *parking_bridge; RAII_VAR(struct parking_lot *, lot, NULL, ao2_cleanup); @@ -519,7 +550,7 @@ /* Apply relevant bridge roles and such to the parking channel */ parking_channel_set_roles(parkee, lot, use_ringing); setup_park_common_datastore(parkee, ast_channel_uniqueid(parker), comeback_override, randomize, time_limit, - silence_announcements); + reminder_delay, silence_announcements); return parking_bridge; } @@ -529,16 +560,17 @@ int use_ringing = 0; int randomize = 0; int time_limit = -1; + int reminder_delay = -1; RAII_VAR(char *, comeback_override, NULL, ast_free); RAII_VAR(char *, lot_name_app_arg, NULL, ast_free); if (app_data) { - park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &comeback_override, &lot_name_app_arg); + park_app_parse_data(app_data, silence_announcements, &use_ringing, &randomize, &time_limit, &reminder_delay, &comeback_override, &lot_name_app_arg); } return park_common_setup(parkee, parker, lot_name_app_arg, comeback_override, use_ringing, - randomize, time_limit, silence_announcements ? *silence_announcements : 0); + randomize, time_limit, reminder_delay, silence_announcements ? *silence_announcements : 0); } diff -durN asterisk-16.12.0.orig/res/parking/parking_bridge.c asterisk-16.12.0/res/parking/parking_bridge.c --- asterisk-16.12.0.orig/res/parking/parking_bridge.c 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/parking_bridge.c 2020-07-28 15:22:32.892255777 +1200 @@ -106,7 +106,7 @@ * * \note ao2_cleanup this reference when you are done using it or you'll cause leaks. */ -static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit) +static struct parked_user *generate_parked_user(struct parking_lot *lot, struct ast_channel *chan, const char *parker_channel_name, const char *parker_dial_string, int use_random_space, int time_limit, int reminder_delay) { struct parked_user *new_parked_user; int preferred_space = -1; /* Initialize to use parking lot defaults */ @@ -163,6 +163,7 @@ new_parked_user->start = ast_tvnow(); new_parked_user->time_limit = (time_limit >= 0) ? time_limit : lot->cfg->parkingtime; + new_parked_user->reminder_delay = (reminder_delay >= 0) ? reminder_delay : lot->cfg->remindertime; if (parker_dial_string) { new_parked_user->parker_dial_string = ast_strdup(parker_dial_string); @@ -249,6 +250,7 @@ ast_bridge_channel_unlock(swap); parking_set_duration(bridge_channel->features, pu); + parking_set_reminder(bridge_channel->features, pu); if (parking_channel_set_roles(bridge_channel->chan, self->lot, use_ringing)) { ast_log(LOG_WARNING, "Failed to apply holding bridge roles to %s while joining the parking lot.\n", @@ -288,7 +290,7 @@ } pu = generate_parked_user(self->lot, bridge_channel->chan, parker_channel_name, - park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit); + park_datastore->parker_dial_string, park_datastore->randomize, park_datastore->time_limit, park_datastore->reminder_delay); ao2_cleanup(parker); if (!pu) { publish_parked_call_failure(bridge_channel->chan); @@ -313,6 +315,7 @@ /* Apply parking duration limits */ parking_set_duration(bridge_channel->features, pu); + parking_set_reminder(bridge_channel->features, pu); /* Set this to the bridge pvt so that we don't have to refind the parked user associated with this bridge channel again. */ bridge_channel->bridge_pvt = pu; diff -durN asterisk-16.12.0.orig/res/parking/parking_bridge_features.c asterisk-16.12.0/res/parking/parking_bridge_features.c --- asterisk-16.12.0.orig/res/parking/parking_bridge_features.c 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/parking_bridge_features.c 2020-07-28 15:22:32.892255777 +1200 @@ -681,6 +681,15 @@ return -1; } +static int parking_reminder_callback(struct ast_bridge_channel *bridge_channel, void *hook_pvt) +{ + struct parked_user *user = hook_pvt; + + publish_parked_call(user, PARKED_CALL_REMINDER); + + return -1; +} + void say_parking_space(struct ast_bridge_channel *bridge_channel, const char *payload) { unsigned int numeric_value; @@ -729,6 +738,33 @@ ao2_ref(user, -1); } } + +void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user) +{ + unsigned int reminder_delay; + + reminder_delay = user->reminder_delay * 1000; + + if (!reminder_delay) { + /* The is no reminder delay that we need to apply */ + return; + } + + /* If the reminder delay has already been passed skip it */ + reminder_delay = ast_remaining_ms(user->start, reminder_delay); + if (reminder_delay <= 0) { + return; + } + + /* The interval hook is going to need a reference to the parked_user */ + ao2_ref(user, +1); + + if (ast_bridge_interval_hook(features, 0, reminder_delay, + parking_reminder_callback, user, __ao2_cleanup, AST_BRIDGE_HOOK_REMOVE_ON_PULL)) { + ast_log(LOG_ERROR, "Failed to apply reminder delay to the parked call.\n"); + ao2_ref(user, -1); + } +} /*! \brief Dial plan function to get the parking lot channel of an occupied parking lot */ static int func_get_parkingslot_channel(struct ast_channel *chan, const char *function, char *data, char *buf, size_t len) diff -durN asterisk-16.12.0.orig/res/parking/parking_manager.c asterisk-16.12.0/res/parking/parking_manager.c --- asterisk-16.12.0.orig/res/parking/parking_manager.c 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/parking_manager.c 2020-07-28 15:22:32.892255777 +1200 @@ -453,7 +453,7 @@ struct ast_channel *chan, const char *parkinglot, int timeout_override) { struct ast_bridge *parking_bridge = park_common_setup(chan, - chan, parkinglot, NULL, 0, 0, timeout_override, 1); + chan, parkinglot, NULL, 0, 0, timeout_override, -1, 1); if (!parking_bridge) { astman_send_error(s, m, "Park action failed\n"); @@ -657,6 +657,9 @@ case PARKED_CALL_SWAP: event_type = "ParkedCallSwap"; break; + case PARKED_CALL_REMINDER: + /* PARKED_CALL_REMINDER doesn't currently get a message is is used exclusivelly for subscriptions */ + return; case PARKED_CALL_FAILED: /* PARKED_CALL_FAILED doesn't currently get a message and is used exclusively for bridging */ return; diff -durN asterisk-16.12.0.orig/res/parking/parking_ui.c asterisk-16.12.0/res/parking/parking_ui.c --- asterisk-16.12.0.orig/res/parking/parking_ui.c 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/parking_ui.c 2020-07-28 15:22:32.896255652 +1200 @@ -58,6 +58,7 @@ ast_cli(fd, "Parking Context : %s\n", lot->cfg->parking_con); ast_cli(fd, "Parking Spaces : %d-%d\n", lot->cfg->parking_start, lot->cfg->parking_stop); ast_cli(fd, "Parking Time : %u sec\n", lot->cfg->parkingtime); + ast_cli(fd, "Reminder Time : %u sec\n", lot->cfg->remindertime); ast_cli(fd, "Comeback to Origin : %s\n", lot->cfg->comebacktoorigin ? "yes" : "no"); ast_cli(fd, "Comeback Context : %s%s\n", lot->cfg->comebackcontext, lot->cfg->comebacktoorigin ? " (comebacktoorigin=yes, not used)" : ""); ast_cli(fd, "Comeback Dial Time : %u sec\n", lot->cfg->comebackdialtime); diff -durN asterisk-16.12.0.orig/res/parking/res_parking.h asterisk-16.12.0/res/parking/res_parking.h --- asterisk-16.12.0.orig/res/parking/res_parking.h 2020-07-28 15:08:00.484834050 +1200 +++ asterisk-16.12.0/res/parking/res_parking.h 2020-07-28 15:22:32.896255652 +1200 @@ -67,6 +67,7 @@ int parking_stop; /*!< Last space in the parking lot */ unsigned int parkingtime; /*!< Analogous to parkingtime config option */ + unsigned int remindertime; /*!< Analogous to remindertime config option */ unsigned int comebackdialtime; /*!< Analogous to comebackdialtime config option */ unsigned int parkfindnext; /*!< Analogous to parkfindnext config option */ unsigned int parkext_exclusive; /*!< Analogous to parkext_exclusive config option */ @@ -110,6 +111,7 @@ char comeback[AST_MAX_CONTEXT]; /*!< Where to go on parking timeout */ char *parker_dial_string; /*!< dialstring to call back with comebacktoorigin. Used timeout extension generation and call control */ unsigned int time_limit; /*!< How long this specific channel may remain in the parking lot before timing out */ + unsigned int reminder_delay; /*!< How long to wait before sending a reminder */ struct parking_lot *lot; /*!< Which parking lot the user is parked to */ enum park_call_resolution resolution; /*!< How did the parking session end? If the call is in a bridge, lock parked_user before checking/setting */ }; @@ -268,6 +270,15 @@ void parking_set_duration(struct ast_bridge_features *features, struct parked_user *user); /*! + * \since 13.7.2 + * \brief Setup a reminder delay feature an ast_bridge_features for parking + * + * \param features The ast_bridge_features we are establishing the interval hook on + * \param user The parked_user receiving the timeout duration limits + */ +void parking_set_reminder(struct ast_bridge_features *features, struct parked_user *user); + +/*! * \since 12.0.0 * \brief Get a pointer to the parking lot container for purposes such as iteration * @@ -422,7 +433,7 @@ */ struct ast_bridge *park_common_setup(struct ast_channel *parkee, struct ast_channel *parker, const char *lot_name, const char *comeback_override, - int use_ringing, int randomize, int time_limit, int silence_announcements); + int use_ringing, int randomize, int time_limit, int reminder_delay, int silence_announcements); /*! * \since 12.0.0 @@ -451,6 +462,7 @@ char *comeback_override; /*!< Optional goto string for where to send the call after we are done */ int randomize; /*!< Pick a parking space to enter on at random */ int time_limit; /*!< time limit override. -1 values don't override, 0 for unlimited time, >0 for custom time limit in seconds */ + int reminder_delay; /*!< reminder delay override. -1 values don't override, 0 for none, >0 custom reminder delay in seconds */ int silence_announce; /*!< Used when a call parks itself to keep it from hearing the parked call announcement */ }; diff -durN asterisk-16.12.0.orig/res/res_format_attr_h264.c asterisk-16.12.0/res/res_format_attr_h264.c --- asterisk-16.12.0.orig/res/res_format_attr_h264.c 2020-07-28 15:08:00.488833895 +1200 +++ asterisk-16.12.0/res/res_format_attr_h264.c 2020-07-28 15:22:32.896255652 +1200 @@ -47,6 +47,7 @@ * length. It must ALWAYS be a string literal representation of one less than * H264_MAX_SPS_PPS_SIZE */ #define H264_MAX_SPS_PPS_SIZE_SCAN_LIMIT "15" +#define H264_MAX_IMAGEATTR_SIZE 256 struct h264_attr { unsigned int PROFILE_IDC; @@ -71,6 +72,7 @@ unsigned int LEVEL_ASYMMETRY_ALLOWED; char SPS[H264_MAX_SPS_PPS_SIZE]; char PPS[H264_MAX_SPS_PPS_SIZE]; + char IMAGEATTR[H264_MAX_IMAGEATTR_SIZE]; }; static void h264_destroy(struct ast_format *format) @@ -160,6 +162,12 @@ ast_copy_string(attr->PPS, attr2->PPS, sizeof(attr->PPS)); } + if (attr1 && !ast_strlen_zero(attr1->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr1->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } else if (attr2 && !ast_strlen_zero(attr2->IMAGEATTR)) { + ast_copy_string(attr->IMAGEATTR, attr2->IMAGEATTR, sizeof(attr->IMAGEATTR)); + } + return cloned; } @@ -307,6 +315,42 @@ return; } +static struct ast_format *h264_attribute_set(const struct ast_format *format, const char *name, const char *value) +{ + struct ast_format *cloned = ast_format_clone(format); + struct h264_attr *attr; + + if (!cloned) { + return NULL; + } + attr = ast_format_get_attribute_data(cloned); + + if (!strcmp(name, "imageattr")) { + ast_copy_string(attr->IMAGEATTR, value, sizeof(attr->IMAGEATTR)); + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return cloned; +} + +static const void *h264_attribute_get(const struct ast_format *format, const char *name) +{ + struct h264_attr *attr = ast_format_get_attribute_data(format); + + if (!attr) { + return NULL; + } + + if (!strcmp(name, "imageattr")) { + return attr->IMAGEATTR; + } else { + ast_log(LOG_WARNING, "unknown attribute type %s\n", name); + } + + return NULL; +} + static struct ast_format_interface h264_interface = { .format_destroy = h264_destroy, .format_clone = h264_clone, @@ -314,6 +358,8 @@ .format_get_joint = h264_getjoint, .format_parse_sdp_fmtp = h264_parse_sdp_fmtp, .format_generate_sdp_fmtp = h264_generate_sdp_fmtp, + .format_attribute_set = h264_attribute_set, + .format_attribute_get = h264_attribute_get, }; static int unload_module(void) diff -durN asterisk-16.12.0.orig/res/res_parking.c asterisk-16.12.0/res/res_parking.c --- asterisk-16.12.0.orig/res/res_parking.c 2020-07-28 15:08:00.488833895 +1200 +++ asterisk-16.12.0/res/res_parking.c 2020-07-28 15:22:32.896255652 +1200 @@ -113,6 +113,9 @@ Amount of time a call will remain parked before giving up (in seconds). + + Amount of time before sending a reminder warning (in seconds). + Which music class to use for parked calls. They will use the default if unspecified. @@ -957,6 +960,7 @@ cfg->parking_start = source->parking_start; cfg->parking_stop = source->parking_stop; cfg->parkingtime = source->parkingtime; + cfg->remindertime = source->remindertime; cfg->comebackdialtime = source->comebackdialtime; cfg->parkfindnext = source->parkfindnext; cfg->parkext_exclusive = source->parkext_exclusive; @@ -1229,6 +1233,7 @@ aco_option_register(&cfg_info, "parkext", ACO_EXACT, parking_lot_types, "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parkext)); aco_option_register(&cfg_info, "context", ACO_EXACT, parking_lot_types, "parkedcalls", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, parking_con)); aco_option_register(&cfg_info, "parkingtime", ACO_EXACT, parking_lot_types, "45", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, parkingtime)); + aco_option_register(&cfg_info, "remindertime", ACO_EXACT, parking_lot_types, "0", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, remindertime)); aco_option_register(&cfg_info, "comebacktoorigin", ACO_EXACT, parking_lot_types, "yes", OPT_BOOL_T, 1, FLDSET(struct parking_lot_cfg, comebacktoorigin)); aco_option_register(&cfg_info, "comebackcontext", ACO_EXACT, parking_lot_types, "parkedcallstimeout", OPT_STRINGFIELD_T, 0, STRFLDSET(struct parking_lot_cfg, comebackcontext)); aco_option_register(&cfg_info, "comebackdialtime", ACO_EXACT, parking_lot_types, "30", OPT_UINT_T, 0, FLDSET(struct parking_lot_cfg, comebackdialtime));