diff -durN asterisk-18.16.0.orig/channels/chan_sip.c asterisk-18.16.0/channels/chan_sip.c
--- asterisk-18.16.0.orig/channels/chan_sip.c 2023-03-05 22:09:51.259632621 +1300
+++ asterisk-18.16.0/channels/chan_sip.c 2023-03-05 22:10:04.603289846 +1300
@@ -367,6 +367,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.
@@ -509,6 +570,27 @@
Preferred codec index number x (beginning with zero).
+
+ The vmexten for this peer.
+
+
+ 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
+
@@ -727,7 +809,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
@@ -892,6 +976,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 call ringing events */
static char used_context[AST_MAX_CONTEXT]; /*!< name of automatically created context for unloading */
@@ -926,6 +1011,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" },
@@ -979,11 +1126,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
/*!
@@ -1006,6 +1159,7 @@
} event_state_compositors [] = {
#ifdef HAVE_LIBXML2
{CALL_COMPLETION, "call-completion", &cc_esc_publish_callbacks},
+ {PRESENCE, "presence", &presence_esc_publish_callbacks},
#endif
};
@@ -1071,6 +1225,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);
@@ -1177,6 +1335,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);
@@ -1202,7 +1361,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_remotecc_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);
@@ -1212,6 +1372,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);
@@ -1225,6 +1386,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);
@@ -1234,7 +1396,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);
@@ -1249,6 +1415,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);
@@ -1259,6 +1426,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);
@@ -1272,9 +1440,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);
@@ -1316,7 +1484,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);
@@ -1338,6 +1506,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)
@@ -1402,6 +1571,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);
@@ -1412,7 +1583,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);
@@ -1467,9 +1638,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);
@@ -1516,7 +1692,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);
@@ -1581,6 +1757,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 */
@@ -3407,9 +3584,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) {
@@ -5309,6 +5500,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);
@@ -5325,6 +5595,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);
@@ -5342,6 +5615,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;
@@ -6009,6 +6292,31 @@
}
}
+static void copy_pvt_data(struct sip_pvt *to_pvt, struct sip_pvt *from_pvt)
+{
+ sip_pvt_lock(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);
+ sip_pvt_unlock(from_pvt);
+}
+
/*! \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)
{
@@ -6312,6 +6620,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;
}
@@ -6480,6 +6792,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) {
@@ -6715,6 +7036,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");
@@ -7253,6 +7577,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 */
@@ -7319,7 +7648,9 @@
const char *res;
stop_provisional_keepalive(p);
- if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause)))
+ if (ast_test_flag(&p->flags[2], SIP_PAGE3_TRANSFER_RESPONSE))
+ transmit_response_reliable(p, "500 Internal Server Error", &p->initreq);
+ else if (p->hangupcause && (res = hangup_cause2sip(p->hangupcause)))
transmit_response_reliable(p, res, &p->initreq);
else
transmit_response_reliable(p, "603 Declined", &p->initreq);
@@ -8142,6 +8473,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 */
@@ -8614,14 +8946,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;
@@ -8629,8 +8959,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;
}
}
@@ -8638,6 +8968,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)
{
@@ -10320,7 +10712,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 */
@@ -10388,11 +10782,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;
@@ -10849,7 +11243,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;
@@ -10872,7 +11266,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;
@@ -10882,7 +11276,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;
@@ -11564,11 +11958,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);
@@ -11591,24 +11985,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;
@@ -11644,36 +12038,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;
@@ -11693,41 +12088,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;
@@ -11868,15 +12283,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-10.0.0");
+ }
- return res;
+ return add_header(req, "Supported", ast_str_buffer(supported));
}
/*! \brief Add header to SIP message */
@@ -12537,8 +12957,11 @@
ast_clear_flag(&p->flags[1], SIP_PAGE2_CONNECTLINEUPDATE_PEND);
add_rpid(&resp, p);
}
+ if (p->method == SIP_INVITE) {
+ add_remotecc_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 */
@@ -12785,6 +13208,36 @@
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, "\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
\retval -1 on error.
@@ -13007,15 +13460,15 @@
{
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;
+ int lid_source;
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;
@@ -13024,18 +13477,16 @@
if (!p->owner) {
return 0;
}
+
+ lid_source = ast_channel_connected(p->owner)->source;
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 */
@@ -13051,66 +13502,63 @@
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)) {
+ if (ast_test_flag(&p->flags[1], SIP_PAGE2_CISCO_USECALLMANAGER)) {
+ if (!strcmp(lid_name, "Conference") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_CONFERENCE) {
+ /* Phone will translate \2004 into the localised version of Conference and enable ConfList/ConfDetails support */
+ lid_name = "\2004";
+ } else if (!strcmp(lid_name, "Park") && lid_source == AST_CONNECTED_LINE_UPDATE_SOURCE_PARKED_CALL) {
+ lid_name = "\2005";
+ }
+ }
+
+ 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));
}
return 0;
@@ -13331,6 +13779,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) {
@@ -13338,6 +13792,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 */
@@ -13711,7 +14175,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));
@@ -13751,6 +14215,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 */
@@ -14097,7 +14566,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);
@@ -14129,6 +14598,52 @@
ao2_ref(agent, -1);
}
+static void add_remotecc_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, "; orientation=%s; security=",
+ p->outgoing_call ? "from" : "to");
+ 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");
+ }
+
+ if (ast_test_flag(&p->flags[0], SIP_OUTGOING) && p->owner) {
+ const char *huntpilot = pbx_builtin_getvar_helper(p->owner, "CISCO_HUNTPILOT");
+
+ if (!ast_strlen_zero(huntpilot)) {
+ char *huntpilot_copy = ast_strdupa(huntpilot);
+ char *name, *location, tmp[256];
+
+ if (!ast_callerid_parse(huntpilot_copy, &name, &location)) {
+ ast_str_append(&header, 0, "; huntpiloturi=\"");
+
+ if (!ast_strlen_zero(name)) {
+ ast_uri_encode(name, tmp, sizeof(tmp), ast_uri_sip_user);
+ ast_str_append(&header, 0, "%%%02X%s%%%02X ",
+ (unsigned char) '"', tmp, (unsigned char) '"');
+ }
+
+ ast_uri_encode(location, tmp, sizeof(tmp), ast_uri_sip_user);
+ ast_str_append(&header, 0, "\"",
+ tmp, ast_sockaddr_stringify_host_remote(&p->ourip));
+ }
+ }
+ }
+
+ add_header(req, "Call-Info", ast_str_buffer(header));
+}
+
/*! \brief Used for 200 OK and 183 early media
\retval XMIT_ERROR for network errors.
*/
@@ -14143,9 +14658,10 @@
respprep(&resp, p, msg, req);
if (rpid == TRUE) {
add_rpid(&resp, p);
+ add_remotecc_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);
@@ -14430,8 +14946,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;
@@ -14481,7 +14995,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.
*/
@@ -14490,13 +15004,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))
@@ -14539,7 +15048,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) {
@@ -14715,9 +15224,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;
@@ -14784,12 +15329,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");
@@ -14881,6 +15439,8 @@
add_rpid(&req, p);
if (sipmethod == SIP_INVITE) {
add_diversion(&req, p);
+ add_join(&req, p);
+ add_remotecc_call_info(&req, p);
}
if (sdp) {
offered_media_list_destroy(p);
@@ -15175,6 +15735,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)) {
@@ -15193,93 +15756,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:
@@ -15290,46 +15769,92 @@
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");
+ 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");
@@ -15439,7 +15964,13 @@
} else {
ast_str_append(tmp, 0, "