2 * @file sipe-incoming.c
6 * Copyright (C) 2010-2011 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"
45 #include "sipe-incoming.h"
46 #include "sipe-media.h"
47 #include "sipe-mime.h"
49 #include "sipe-session.h"
50 #include "sipe-user.h"
51 #include "sipe-utils.h"
54 void process_incoming_bye(struct sipe_core_private
*sipe_private
,
57 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
58 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
59 struct sip_session
*session
;
60 struct sip_dialog
*dialog
;
63 if (is_media_session_msg(sipe_private
->media_call
, msg
)) {
64 // BYE ends a media call
65 sipe_media_hangup(sipe_private
->media_call
);
69 /* collect dialog identification
70 * we need callid, ourtag and theirtag to unambiguously identify dialog
72 /* take data before 'msg' will be modified by sip_transport_response */
73 dialog
= g_new0(struct sip_dialog
, 1);
74 dialog
->callid
= g_strdup(callid
);
75 dialog
->cseq
= sipmsg_parse_cseq(msg
);
76 dialog
->with
= g_strdup(from
);
77 sipe_dialog_parse(dialog
, msg
, FALSE
);
79 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
81 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, from
);
83 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_bye: couldn't find session. Ignoring");
84 sipe_dialog_free(dialog
);
89 SIPE_DEBUG_INFO("process_incoming_bye: session found (chat ID %s)",
90 (session
->chat_session
&& session
->chat_session
->id
) ?
91 session
->chat_session
->id
: "<NO CHAT>");
93 if (session
->chat_session
&&
94 (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
) &&
95 session
->chat_session
->id
&&
96 !g_strcasecmp(from
, session
->chat_session
->id
))
97 sipe_chat_set_roster_manager(session
, NULL
);
99 sipe_im_cancel_unconfirmed(sipe_private
, session
, callid
, from
);
101 /* This what BYE is essentially for - terminating dialog */
102 sipe_dialog_remove_3(session
, dialog
);
103 sipe_dialog_free(dialog
);
104 if (session
->chat_session
) {
105 if ((session
->chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
) &&
106 !g_strcasecmp(from
, session
->im_mcu_uri
)) {
107 SIPE_DEBUG_INFO("process_incoming_bye: disconnected from conference %s",
108 session
->im_mcu_uri
);
109 sipe_conf_immcu_closed(sipe_private
, session
);
110 } else if (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
) {
111 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_bye: disconnected from multiparty chat");
112 sipe_backend_chat_remove(session
->chat_session
->backend
,
120 void process_incoming_cancel(struct sipe_core_private
*sipe_private
,
126 if (is_media_session_msg(sipe_private
->media_call
, msg
)) {
127 process_incoming_cancel_call(sipe_private
, msg
);
131 callid
= sipmsg_find_header(msg
, "Call-ID");
133 if (!sipe_session_find_chat_by_callid(sipe_private
, callid
))
134 sipe_conf_cancel_unaccepted(sipe_private
, msg
);
137 void process_incoming_info(struct sipe_core_private
*sipe_private
,
140 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
141 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
143 struct sip_session
*session
;
145 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info");
147 /* Call Control protocol */
148 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
150 process_incoming_info_csta(sipe_private
, msg
);
153 else if (g_str_has_prefix(contenttype
, "application/xml+conversationinfo"))
155 process_incoming_info_conversation(sipe_private
, msg
);
159 from
= parse_from(sipmsg_find_header(msg
, "From"));
160 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, from
);
166 /* Group Chat uses text/plain */
167 if (session
->is_groupchat
) {
168 process_incoming_info_groupchat(sipe_private
, msg
, session
);
173 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
175 sipe_xml
*xn_action
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
176 const sipe_xml
*xn_request_rm
= sipe_xml_child(xn_action
, "RequestRM");
177 const sipe_xml
*xn_set_rm
= sipe_xml_child(xn_action
, "SetRM");
179 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
182 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
183 int bid
= sipe_xml_int_attribute(xn_request_rm
, "bid", 0);
184 gchar
*body
= g_strdup_printf(
185 "<?xml version=\"1.0\"?>\r\n"
186 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
187 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
188 sipe_private
->username
,
189 session
->bid
< bid
? "true" : "false");
190 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
192 } else if (xn_set_rm
) {
195 sipe_chat_set_roster_manager(session
,
196 sipe_xml_attribute(xn_set_rm
, "uri"));
198 body
= g_strdup_printf(
199 "<?xml version=\"1.0\"?>\r\n"
200 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
201 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
202 sipe_private
->username
);
203 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
206 sipe_xml_free(xn_action
);
211 /* looks like purple lacks typing notification for chat */
212 if (!session
->chat_session
) {
213 sipe_xml
*xn_keyboard_activity
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
214 const char *status
= sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity
, "status"),
216 if (sipe_strequal(status
, "type")) {
217 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
219 } else if (sipe_strequal(status
, "idle")) {
220 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
223 sipe_xml_free(xn_keyboard_activity
);
226 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
231 static gboolean
sipe_process_incoming_x_msmsgsinvite(struct sipe_core_private
*sipe_private
,
232 struct sip_dialog
*dialog
,
235 gboolean found
= FALSE
;
238 const gchar
*invitation_command
= sipe_utils_nameval_find(parsed_body
, "Invitation-Command");
240 if (sipe_strequal(invitation_command
, "INVITE")) {
241 sipe_ft_incoming_transfer(sipe_private
, dialog
, parsed_body
);
243 } else if (sipe_strequal(invitation_command
, "CANCEL")) {
244 sipe_ft_incoming_cancel(dialog
, parsed_body
);
246 } else if (sipe_strequal(invitation_command
, "ACCEPT")) {
247 sipe_ft_incoming_accept(dialog
, parsed_body
);
255 static void sipe_invite_mime_cb(gpointer user_data
, const GSList
*fields
,
256 const gchar
*body
, gsize length
)
258 const gchar
*type
= sipe_utils_nameval_find(fields
, "Content-Type");
259 const gchar
*cd
= sipe_utils_nameval_find(fields
, "Content-Disposition");
261 if (!g_str_has_prefix(type
, "application/sdp"))
264 if (cd
&& !strstr(cd
, "ms-proxy-2007fallback")) {
265 struct sipmsg
*msg
= user_data
;
266 const gchar
* msg_ct
= sipmsg_find_header(msg
, "Content-Type");
268 if (g_str_has_prefix(msg_ct
, "application/sdp")) {
269 /* We have already found suitable alternative and set message's body
270 * and Content-Type accordingly */
274 sipmsg_remove_header_now(msg
, "Content-Type");
275 sipmsg_add_header_now(msg
, "Content-Type", type
);
277 /* Replace message body with chosen alternative, so we can continue to
278 * process it as a normal single part message. */
280 msg
->body
= g_strndup(body
, length
);
281 msg
->bodylen
= length
;
286 void process_incoming_invite(struct sipe_core_private
*sipe_private
,
291 const gchar
*oldHeader
;
293 gboolean is_multiparty
= FALSE
;
294 gboolean was_multiparty
= TRUE
;
295 gboolean just_joined
= FALSE
;
297 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
298 const gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
299 const gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
300 const gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
301 const gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
302 const gchar
*subject
= sipmsg_find_header(msg
, "Subject");
303 GSList
*end_points
= NULL
;
304 struct sip_session
*session
;
305 struct sip_dialog
*dialog
;
306 const gchar
*ms_text_format
;
309 if (g_str_has_prefix(content_type
, "multipart/alternative")) {
310 sipe_mime_parts_foreach(content_type
, msg
->body
, sipe_invite_mime_cb
, msg
);
311 /* Reload Content-Type to get type of the selected message part */
312 content_type
= sipmsg_find_header(msg
, "Content-Type");
316 /* Invitation to join conference */
317 if (g_str_has_prefix(content_type
, "application/ms-conf-invite+xml")) {
318 process_incoming_invite_conf(sipe_private
, msg
);
323 /* Invitation to audio call */
324 if (msg
->body
&& strstr(msg
->body
, "m=audio")) {
325 process_incoming_invite_call(sipe_private
, msg
);
330 /* Only accept text invitations */
331 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
332 sip_transport_response(sipe_private
, msg
, 501, "Not implemented", NULL
);
336 // TODO There *must* be a better way to clean up the To header to add a tag...
337 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
338 oldHeader
= sipmsg_find_header(msg
, "To");
340 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
342 sipmsg_remove_header_now(msg
, "To");
343 sipmsg_add_header_now(msg
, "To", newHeader
);
346 if (end_points_hdr
) {
347 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
349 if (g_slist_length(end_points
) > 2) {
350 is_multiparty
= TRUE
;
353 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
354 is_multiparty
= TRUE
;
357 /* Multiparty session */
358 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
362 if (session
->chat_session
) {
363 /* Update roster manager for existing multiparty session */
365 sipe_chat_set_roster_manager(session
, roster_manager
);
368 gchar
*chat_title
= sipe_chat_get_name();
370 /* Convert IM session to multiparty session */
371 g_free(session
->with
);
372 session
->with
= NULL
;
373 was_multiparty
= FALSE
;
374 session
->chat_session
= sipe_chat_create_session(SIPE_CHAT_TYPE_MULTIPARTY
,
381 /* New multiparty session */
382 session
= sipe_session_add_chat(sipe_private
,
389 if (!session
->chat_session
->backend
) {
390 gchar
*self
= sip_uri_self(sipe_private
);
391 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
392 session
->chat_session
,
393 session
->chat_session
->title
,
400 from
= parse_from(sipmsg_find_header(msg
, "From"));
402 session
= sipe_session_find_or_add_im(sipe_private
, from
);
404 /* session is now initialized */
405 g_free(session
->callid
);
406 session
->callid
= g_strdup(callid
);
408 if (is_multiparty
&& end_points
) {
409 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
410 GSList
*entry
= end_points
;
412 struct sipendpoint
*end_point
= entry
->data
;
415 if (!g_strcasecmp(from
, end_point
->contact
) ||
416 !g_strcasecmp(to
, end_point
->contact
))
419 dialog
= sipe_dialog_find(session
, end_point
->contact
);
421 g_free(dialog
->theirepid
);
422 dialog
->theirepid
= end_point
->epid
;
423 end_point
->epid
= NULL
;
425 dialog
= sipe_dialog_add(session
);
427 dialog
->callid
= g_strdup(session
->callid
);
428 dialog
->with
= end_point
->contact
;
429 end_point
->contact
= NULL
;
430 dialog
->theirepid
= end_point
->epid
;
431 end_point
->epid
= NULL
;
435 /* send triggered INVITE */
436 sipe_im_invite(sipe_private
, session
, dialog
->with
, NULL
, NULL
, NULL
, TRUE
);
443 GSList
*entry
= end_points
;
445 struct sipendpoint
*end_point
= entry
->data
;
447 g_free(end_point
->contact
);
448 g_free(end_point
->epid
);
451 g_slist_free(end_points
);
454 dialog
= sipe_dialog_find(session
, from
);
456 sipe_im_cancel_dangling(sipe_private
, session
, dialog
, from
,
457 sipe_im_reenqueue_unconfirmed
);
458 /* dialog is no longer valid */
463 dialog
= sipe_dialog_add(session
);
464 dialog
->with
= g_strdup(from
);
465 dialog
->callid
= g_strdup(session
->callid
);
466 sipe_dialog_parse(dialog
, msg
, FALSE
);
468 if (is_multiparty
&& !was_multiparty
) {
469 /* add current IM counterparty to chat */
470 sipe_backend_chat_add(session
->chat_session
->backend
,
471 sipe_dialog_first(session
)->with
,
475 /* add inviting party to chat */
476 if (just_joined
&& session
->chat_session
) {
477 sipe_backend_chat_add(session
->chat_session
->backend
,
482 if (!is_multiparty
&& subject
)
483 sipe_im_topic(sipe_private
, session
, subject
);
485 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
487 /* This used only in 2005 official client, not 2007 or Reuters.
488 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
489 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
491 /* also enabled for 2005 file transfer. Didn't work otherwise. */
492 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
494 (ms_text_format
&& g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite")) )
496 if (ms_text_format
) {
497 if (g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite"))
499 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
502 gchar
*body
= (gchar
*) g_base64_decode(tmp
, &len
);
503 GSList
*parsed_body
= sipe_ft_parse_msg_body(body
);
505 dialog
= sipe_dialog_find(session
, from
);
506 sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, parsed_body
);
507 sipe_utils_nameval_free(parsed_body
);
508 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
512 else if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html"))
514 /* please do not optimize logic inside as this code may be re-enabled for other cases */
515 gchar
*html
= get_html_message(ms_text_format
, NULL
);
518 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
519 session
->chat_session
->backend
,
523 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
528 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
536 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
537 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
539 body
= g_strdup_printf(
541 "o=- 0 0 IN IP4 %s\r\n"
545 "m=%s %d sip sip:%s\r\n"
546 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
547 sipe_backend_network_ip_address(),
548 sipe_backend_network_ip_address(),
549 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
550 sip_transport_port(sipe_private
),
551 sipe_private
->username
);
552 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
556 void process_incoming_message(struct sipe_core_private
*sipe_private
,
560 const gchar
*contenttype
;
561 gboolean found
= FALSE
;
563 from
= parse_from(sipmsg_find_header(msg
, "From"));
567 SIPE_DEBUG_INFO("got message from %s: %s", from
, msg
->body
);
569 contenttype
= sipmsg_find_header(msg
, "Content-Type");
570 if (g_str_has_prefix(contenttype
, "text/plain")
571 || g_str_has_prefix(contenttype
, "text/html")
572 || g_str_has_prefix(contenttype
, "multipart/related")
573 || g_str_has_prefix(contenttype
, "multipart/alternative"))
575 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
576 gchar
*html
= get_html_message(contenttype
, msg
->body
);
578 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
581 if (session
&& session
->chat_session
) {
582 if (session
->chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
) { /* a conference */
583 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
584 gchar
*sender
= parse_from(tmp
);
586 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
587 session
->chat_session
->backend
,
591 } else { /* a multiparty chat */
592 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
593 session
->chat_session
->backend
,
598 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
603 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
606 } else if (g_str_has_prefix(contenttype
, "application/im-iscomposing+xml")) {
607 sipe_xml
*isc
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
608 const sipe_xml
*state
;
612 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
617 state
= sipe_xml_child(isc
, "state");
620 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
626 statedata
= sipe_xml_data(state
);
628 if (strstr(statedata
, "active")) {
629 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
632 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
638 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
640 } else if (g_str_has_prefix(contenttype
, "text/x-msmsgsinvite")) {
641 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
642 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
645 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
646 GSList
*body
= sipe_ft_parse_msg_body(msg
->body
);
647 found
= sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, body
);
648 sipe_utils_nameval_free(body
);
650 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
654 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
655 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
659 gchar
*errmsg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
661 sipe_user_present_error(sipe_private
, session
, errmsg
);
665 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype
);
666 sip_transport_response(sipe_private
, msg
, 415, "Unsupported media type", NULL
);
671 void process_incoming_options(struct sipe_core_private
*sipe_private
,
676 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
677 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
679 body
= g_strdup_printf(
681 "o=- 0 0 IN IP4 0.0.0.0\r\n"
683 "c=IN IP4 0.0.0.0\r\n"
685 "m=%s %d sip sip:%s\r\n"
686 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
687 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
688 sip_transport_port(sipe_private
),
689 sipe_private
->username
);
690 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
694 void process_incoming_refer(struct sipe_core_private
*sipe_private
,
697 gchar
*self
= sip_uri_self(sipe_private
);
698 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
699 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
700 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
701 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
702 struct sip_session
*session
;
703 struct sip_dialog
*dialog
;
705 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
706 dialog
= sipe_dialog_find(session
, from
);
708 if (!session
|| !dialog
|| !session
->chat_session
||
709 (session
->chat_session
->type
!= SIPE_CHAT_TYPE_MULTIPARTY
) ||
710 !session
->chat_session
->id
||
711 !sipe_strcase_equal(session
->chat_session
->id
, self
)) {
712 sip_transport_response(sipe_private
, msg
, 500, "Server Internal Error", NULL
);
714 sip_transport_response(sipe_private
, msg
, 202, "Accepted", NULL
);
716 sipe_im_invite(sipe_private
, session
, refer_to
, NULL
, NULL
, referred_by
, FALSE
);