2 * @file sipe-incoming.c
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
32 #include "sipe-common.h"
35 #include "sip-transport.h"
36 #include "sipe-backend.h"
37 #include "sipe-chat.h"
38 #include "sipe-conf.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-dialog.h"
43 #include "sipe-groupchat.h"
44 #include "sipe-incoming.h"
45 #include "sipe-media.h"
47 #include "sipe-session.h"
48 #include "sipe-utils.h"
50 #include "sipe-mime.h"
53 void process_incoming_bye(struct sipe_core_private
*sipe_private
,
56 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
57 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
58 struct sip_session
*session
;
59 struct sip_dialog
*dialog
;
62 if (is_media_session_msg(sipe_private
->media_call
, msg
)) {
63 // BYE ends a media call
64 sipe_media_hangup(sipe_private
->media_call
);
68 /* collect dialog identification
69 * we need callid, ourtag and theirtag to unambiguously identify dialog
71 /* take data before 'msg' will be modified by sip_transport_response */
72 dialog
= g_new0(struct sip_dialog
, 1);
73 dialog
->callid
= g_strdup(callid
);
74 dialog
->cseq
= parse_cseq(sipmsg_find_header(msg
, "CSeq"));
75 dialog
->with
= g_strdup(from
);
76 sipe_dialog_parse(dialog
, msg
, FALSE
);
78 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
80 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, from
);
82 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_bye: couldn't find session. Ignoring");
83 sipe_dialog_free(dialog
);
88 SIPE_DEBUG_INFO("process_incoming_bye: session found (chat ID %s)",
89 (session
->chat_session
&& session
->chat_session
->id
) ?
90 session
->chat_session
->id
: "<NO CHAT>");
92 if (session
->chat_session
&&
93 (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
) &&
94 session
->chat_session
->id
&&
95 !g_strcasecmp(from
, session
->chat_session
->id
))
96 sipe_chat_set_roster_manager(session
, NULL
);
98 /* This what BYE is essentially for - terminating dialog */
99 sipe_dialog_remove_3(session
, dialog
);
100 sipe_dialog_free(dialog
);
101 if (session
->chat_session
) {
102 if ((session
->chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
) &&
103 !g_strcasecmp(from
, session
->im_mcu_uri
)) {
104 SIPE_DEBUG_INFO("process_incoming_bye: disconnected from conference %s",
105 session
->im_mcu_uri
);
106 sipe_conf_immcu_closed(sipe_private
, session
);
107 } else if (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
) {
108 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_bye: disconnected from multiparty chat");
109 sipe_backend_chat_remove(session
->chat_session
->backend
,
117 void process_incoming_cancel(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
118 SIPE_UNUSED_PARAMETER
struct sipmsg
*msg
)
121 if (is_media_session_msg(sipe_private
->media_call
, msg
)) {
122 process_incoming_cancel_call(sipe_private
, msg
);
127 void process_incoming_info(struct sipe_core_private
*sipe_private
,
130 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
131 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
133 struct sip_session
*session
;
135 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info");
137 /* Call Control protocol */
138 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
140 process_incoming_info_csta(sipe_private
, msg
);
144 from
= parse_from(sipmsg_find_header(msg
, "From"));
145 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, from
);
151 /* Group Chat uses text/plain */
152 if (session
->is_groupchat
) {
153 process_incoming_info_groupchat(sipe_private
, msg
, session
);
158 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
160 sipe_xml
*xn_action
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
161 const sipe_xml
*xn_request_rm
= sipe_xml_child(xn_action
, "RequestRM");
162 const sipe_xml
*xn_set_rm
= sipe_xml_child(xn_action
, "SetRM");
164 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
167 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
168 int bid
= sipe_xml_int_attribute(xn_request_rm
, "bid", 0);
169 gchar
*body
= g_strdup_printf(
170 "<?xml version=\"1.0\"?>\r\n"
171 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
172 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
173 sipe_private
->username
,
174 session
->bid
< bid
? "true" : "false");
175 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
177 } else if (xn_set_rm
) {
180 sipe_chat_set_roster_manager(session
,
181 sipe_xml_attribute(xn_set_rm
, "uri"));
183 body
= g_strdup_printf(
184 "<?xml version=\"1.0\"?>\r\n"
185 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
186 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
187 sipe_private
->username
);
188 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
191 sipe_xml_free(xn_action
);
196 /* looks like purple lacks typing notification for chat */
197 if (!session
->chat_session
) {
198 sipe_xml
*xn_keyboard_activity
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
199 const char *status
= sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity
, "status"),
201 if (sipe_strequal(status
, "type")) {
202 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
204 } else if (sipe_strequal(status
, "idle")) {
205 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
208 sipe_xml_free(xn_keyboard_activity
);
211 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
216 static gboolean
sipe_process_incoming_x_msmsgsinvite(struct sipe_core_private
*sipe_private
,
217 struct sip_dialog
*dialog
,
220 gboolean found
= FALSE
;
223 const gchar
*invitation_command
= sipe_utils_nameval_find(parsed_body
, "Invitation-Command");
225 if (sipe_strequal(invitation_command
, "INVITE")) {
226 sipe_ft_incoming_transfer(sipe_private
, dialog
, parsed_body
);
228 } else if (sipe_strequal(invitation_command
, "CANCEL")) {
229 sipe_ft_incoming_cancel(dialog
, parsed_body
);
231 } else if (sipe_strequal(invitation_command
, "ACCEPT")) {
232 sipe_ft_incoming_accept(dialog
, parsed_body
);
240 static void sipe_invite_mime_cb(gpointer user_data
, const GSList
*fields
,
241 const gchar
*body
, gsize length
)
243 const gchar
*type
= sipe_utils_nameval_find(fields
, "Content-Type");
244 const gchar
*cd
= sipe_utils_nameval_find(fields
, "Content-Disposition");
246 if (!g_str_has_prefix(type
, "application/sdp"))
249 if (cd
&& !strstr(cd
, "ms-proxy-2007fallback")) {
250 struct sipmsg
*msg
= user_data
;
251 const gchar
* msg_ct
= sipmsg_find_header(msg
, "Content-Type");
253 if (g_str_has_prefix(msg_ct
, "application/sdp")) {
254 /* We have already found suitable alternative and set message's body
255 * and Content-Type accordingly */
259 sipmsg_remove_header_now(msg
, "Content-Type");
260 sipmsg_add_header_now(msg
, "Content-Type", type
);
262 /* Replace message body with chosen alternative, so we can continue to
263 * process it as a normal single part message. */
265 msg
->body
= g_strndup(body
, length
);
266 msg
->bodylen
= length
;
271 void process_incoming_invite(struct sipe_core_private
*sipe_private
,
276 const gchar
*oldHeader
;
278 gboolean is_multiparty
= FALSE
;
279 gboolean is_triggered
= FALSE
;
280 gboolean was_multiparty
= TRUE
;
281 gboolean just_joined
= FALSE
;
283 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
284 const gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
285 const gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
286 const gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
287 const gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
288 GSList
*end_points
= NULL
;
289 struct sip_session
*session
;
290 struct sip_dialog
*dialog
;
291 const gchar
*ms_text_format
;
294 if (g_str_has_prefix(content_type
, "multipart/alternative")) {
295 sipe_mime_parts_foreach(content_type
, msg
->body
, sipe_invite_mime_cb
, msg
);
296 /* Reload Content-Type to get type of the selected message part */
297 content_type
= sipmsg_find_header(msg
, "Content-Type");
301 /* Invitation to join conference */
302 if (g_str_has_prefix(content_type
, "application/ms-conf-invite+xml")) {
303 process_incoming_invite_conf(sipe_private
, msg
);
308 /* Invitation to audio call */
309 if (msg
->body
&& strstr(msg
->body
, "m=audio")) {
310 process_incoming_invite_call(sipe_private
, msg
);
315 /* Only accept text invitations */
316 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
317 sip_transport_response(sipe_private
, msg
, 501, "Not implemented", NULL
);
321 // TODO There *must* be a better way to clean up the To header to add a tag...
322 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
323 oldHeader
= sipmsg_find_header(msg
, "To");
325 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
326 sipmsg_remove_header_now(msg
, "To");
327 sipmsg_add_header_now(msg
, "To", newHeader
);
330 if (end_points_hdr
) {
331 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
333 if (g_slist_length(end_points
) > 2) {
334 is_multiparty
= TRUE
;
337 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
339 is_multiparty
= TRUE
;
342 /* Multiparty session */
343 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
347 if (session
->chat_session
) {
348 /* Update roster manager for existing multiparty session */
350 sipe_chat_set_roster_manager(session
, roster_manager
);
353 gchar
*chat_title
= sipe_chat_get_name();
355 /* Convert IM session to multiparty session */
356 g_free(session
->with
);
357 session
->with
= NULL
;
358 was_multiparty
= FALSE
;
359 session
->chat_session
= sipe_chat_create_session(SIPE_CHAT_TYPE_MULTIPARTY
,
366 /* New multiparty session */
367 session
= sipe_session_add_chat(sipe_private
,
374 if (!session
->chat_session
->backend
) {
375 gchar
*self
= sip_uri_self(sipe_private
);
376 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
377 session
->chat_session
,
378 session
->chat_session
->title
,
385 from
= parse_from(sipmsg_find_header(msg
, "From"));
387 session
= sipe_session_find_or_add_im(sipe_private
, from
);
389 /* session is now initialized */
390 g_free(session
->callid
);
391 session
->callid
= g_strdup(callid
);
393 if (is_multiparty
&& end_points
) {
394 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
395 GSList
*entry
= end_points
;
397 struct sipendpoint
*end_point
= entry
->data
;
400 if (!g_strcasecmp(from
, end_point
->contact
) ||
401 !g_strcasecmp(to
, end_point
->contact
))
404 dialog
= sipe_dialog_find(session
, end_point
->contact
);
406 g_free(dialog
->theirepid
);
407 dialog
->theirepid
= end_point
->epid
;
408 end_point
->epid
= NULL
;
410 dialog
= sipe_dialog_add(session
);
412 dialog
->callid
= g_strdup(session
->callid
);
413 dialog
->with
= end_point
->contact
;
414 end_point
->contact
= NULL
;
415 dialog
->theirepid
= end_point
->epid
;
416 end_point
->epid
= NULL
;
420 /* send triggered INVITE */
421 sipe_invite(sipe_private
, session
, dialog
->with
, NULL
, NULL
, NULL
, TRUE
);
428 GSList
*entry
= end_points
;
430 struct sipendpoint
*end_point
= entry
->data
;
432 g_free(end_point
->contact
);
433 g_free(end_point
->epid
);
436 g_slist_free(end_points
);
439 dialog
= sipe_dialog_find(session
, from
);
441 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
442 sipe_dialog_parse_routes(dialog
, msg
, FALSE
);
444 dialog
= sipe_dialog_add(session
);
446 dialog
->callid
= g_strdup(session
->callid
);
447 dialog
->with
= g_strdup(from
);
448 sipe_dialog_parse(dialog
, msg
, FALSE
);
450 if (!dialog
->ourtag
) {
451 dialog
->ourtag
= newTag
;
459 if (is_multiparty
&& !was_multiparty
) {
460 /* add current IM counterparty to chat */
461 sipe_backend_chat_add(session
->chat_session
->backend
,
462 sipe_dialog_first(session
)->with
,
466 /* add inviting party to chat */
467 if (just_joined
&& session
->chat_session
) {
468 sipe_backend_chat_add(session
->chat_session
->backend
,
473 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
475 /* This used only in 2005 official client, not 2007 or Reuters.
476 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
477 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
479 /* also enabled for 2005 file transfer. Didn't work otherwise. */
480 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
482 (ms_text_format
&& g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite")) )
484 if (ms_text_format
) {
485 if (g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite"))
487 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
490 gchar
*body
= (gchar
*) g_base64_decode(tmp
, &len
);
491 GSList
*parsed_body
= sipe_ft_parse_msg_body(body
);
493 dialog
= sipe_dialog_find(session
, from
);
494 sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, parsed_body
);
495 sipe_utils_nameval_free(parsed_body
);
496 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
500 else if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html"))
502 /* please do not optimize logic inside as this code may be re-enabled for other cases */
503 gchar
*html
= get_html_message(ms_text_format
, NULL
);
506 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
507 session
->chat_session
->backend
,
511 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
516 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
524 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
525 sipmsg_add_header(msg
, "User-Agent", sip_transport_user_agent(sipe_private
));
526 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
528 body
= g_strdup_printf(
530 "o=- 0 0 IN IP4 %s\r\n"
534 "m=%s %d sip sip:%s\r\n"
535 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
536 sipe_backend_network_ip_address(),
537 sipe_backend_network_ip_address(),
538 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
539 sip_transport_port(sipe_private
),
540 sipe_private
->username
);
541 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
545 void process_incoming_message(struct sipe_core_private
*sipe_private
,
549 const gchar
*contenttype
;
550 gboolean found
= FALSE
;
552 from
= parse_from(sipmsg_find_header(msg
, "From"));
556 SIPE_DEBUG_INFO("got message from %s: %s", from
, msg
->body
);
558 contenttype
= sipmsg_find_header(msg
, "Content-Type");
559 if (g_str_has_prefix(contenttype
, "text/plain")
560 || g_str_has_prefix(contenttype
, "text/html")
561 || g_str_has_prefix(contenttype
, "multipart/related")
562 || g_str_has_prefix(contenttype
, "multipart/alternative"))
564 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
565 gchar
*html
= get_html_message(contenttype
, msg
->body
);
567 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
570 if (session
&& session
->chat_session
) {
571 if (session
->chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
) { /* a conference */
572 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
573 gchar
*sender
= parse_from(tmp
);
575 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
576 session
->chat_session
->backend
,
580 } else { /* a multiparty chat */
581 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
582 session
->chat_session
->backend
,
587 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
592 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
595 } else if (g_str_has_prefix(contenttype
, "application/im-iscomposing+xml")) {
596 sipe_xml
*isc
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
597 const sipe_xml
*state
;
601 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
606 state
= sipe_xml_child(isc
, "state");
609 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
615 statedata
= sipe_xml_data(state
);
617 if (strstr(statedata
, "active")) {
618 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
621 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
627 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
629 } else if (g_str_has_prefix(contenttype
, "text/x-msmsgsinvite")) {
630 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
631 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
634 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
635 GSList
*body
= sipe_ft_parse_msg_body(msg
->body
);
636 found
= sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, body
);
637 sipe_utils_nameval_free(body
);
639 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
643 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
644 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
648 gchar
*errmsg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
650 sipe_present_err(sipe_private
, session
, errmsg
);
654 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype
);
655 sip_transport_response(sipe_private
, msg
, 415, "Unsupported media type", NULL
);
660 void process_incoming_options(struct sipe_core_private
*sipe_private
,
665 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
666 sipmsg_add_header(msg
, "User-Agent", sip_transport_user_agent(sipe_private
));
667 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
669 body
= g_strdup_printf(
671 "o=- 0 0 IN IP4 0.0.0.0\r\n"
673 "c=IN IP4 0.0.0.0\r\n"
675 "m=%s %d sip sip:%s\r\n"
676 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
677 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
678 sip_transport_port(sipe_private
),
679 sipe_private
->username
);
680 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
684 void process_incoming_refer(struct sipe_core_private
*sipe_private
,
687 gchar
*self
= sip_uri_self(sipe_private
);
688 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
689 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
690 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
691 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
692 struct sip_session
*session
;
693 struct sip_dialog
*dialog
;
695 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
696 dialog
= sipe_dialog_find(session
, from
);
698 if (!session
|| !dialog
|| !session
->chat_session
||
699 (session
->chat_session
->type
!= SIPE_CHAT_TYPE_MULTIPARTY
) ||
700 !session
->chat_session
->id
||
701 !sipe_strcase_equal(session
->chat_session
->id
, self
)) {
702 sip_transport_response(sipe_private
, msg
, 500, "Server Internal Error", NULL
);
704 sip_transport_response(sipe_private
, msg
, 202, "Accepted", NULL
);
706 sipe_invite(sipe_private
, session
, refer_to
, NULL
, NULL
, referred_by
, FALSE
);