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(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
121 SIPE_UNUSED_PARAMETER
struct sipmsg
*msg
)
124 if (is_media_session_msg(sipe_private
->media_call
, msg
)) {
125 process_incoming_cancel_call(sipe_private
, msg
);
130 void process_incoming_info(struct sipe_core_private
*sipe_private
,
133 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
134 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
136 struct sip_session
*session
;
138 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info");
140 /* Call Control protocol */
141 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
143 process_incoming_info_csta(sipe_private
, msg
);
146 else if (g_str_has_prefix(contenttype
, "application/xml+conversationinfo"))
148 process_incoming_info_conversation(sipe_private
, msg
);
152 from
= parse_from(sipmsg_find_header(msg
, "From"));
153 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, from
);
159 /* Group Chat uses text/plain */
160 if (session
->is_groupchat
) {
161 process_incoming_info_groupchat(sipe_private
, msg
, session
);
166 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
168 sipe_xml
*xn_action
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
169 const sipe_xml
*xn_request_rm
= sipe_xml_child(xn_action
, "RequestRM");
170 const sipe_xml
*xn_set_rm
= sipe_xml_child(xn_action
, "SetRM");
172 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
175 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
176 int bid
= sipe_xml_int_attribute(xn_request_rm
, "bid", 0);
177 gchar
*body
= g_strdup_printf(
178 "<?xml version=\"1.0\"?>\r\n"
179 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
180 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
181 sipe_private
->username
,
182 session
->bid
< bid
? "true" : "false");
183 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
185 } else if (xn_set_rm
) {
188 sipe_chat_set_roster_manager(session
,
189 sipe_xml_attribute(xn_set_rm
, "uri"));
191 body
= g_strdup_printf(
192 "<?xml version=\"1.0\"?>\r\n"
193 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
194 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
195 sipe_private
->username
);
196 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
199 sipe_xml_free(xn_action
);
204 /* looks like purple lacks typing notification for chat */
205 if (!session
->chat_session
) {
206 sipe_xml
*xn_keyboard_activity
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
207 const char *status
= sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity
, "status"),
209 if (sipe_strequal(status
, "type")) {
210 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
212 } else if (sipe_strequal(status
, "idle")) {
213 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
216 sipe_xml_free(xn_keyboard_activity
);
219 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
224 static gboolean
sipe_process_incoming_x_msmsgsinvite(struct sipe_core_private
*sipe_private
,
225 struct sip_dialog
*dialog
,
228 gboolean found
= FALSE
;
231 const gchar
*invitation_command
= sipe_utils_nameval_find(parsed_body
, "Invitation-Command");
233 if (sipe_strequal(invitation_command
, "INVITE")) {
234 sipe_ft_incoming_transfer(sipe_private
, dialog
, parsed_body
);
236 } else if (sipe_strequal(invitation_command
, "CANCEL")) {
237 sipe_ft_incoming_cancel(dialog
, parsed_body
);
239 } else if (sipe_strequal(invitation_command
, "ACCEPT")) {
240 sipe_ft_incoming_accept(dialog
, parsed_body
);
248 static void sipe_invite_mime_cb(gpointer user_data
, const GSList
*fields
,
249 const gchar
*body
, gsize length
)
251 const gchar
*type
= sipe_utils_nameval_find(fields
, "Content-Type");
252 const gchar
*cd
= sipe_utils_nameval_find(fields
, "Content-Disposition");
254 if (!g_str_has_prefix(type
, "application/sdp"))
257 if (cd
&& !strstr(cd
, "ms-proxy-2007fallback")) {
258 struct sipmsg
*msg
= user_data
;
259 const gchar
* msg_ct
= sipmsg_find_header(msg
, "Content-Type");
261 if (g_str_has_prefix(msg_ct
, "application/sdp")) {
262 /* We have already found suitable alternative and set message's body
263 * and Content-Type accordingly */
267 sipmsg_remove_header_now(msg
, "Content-Type");
268 sipmsg_add_header_now(msg
, "Content-Type", type
);
270 /* Replace message body with chosen alternative, so we can continue to
271 * process it as a normal single part message. */
273 msg
->body
= g_strndup(body
, length
);
274 msg
->bodylen
= length
;
279 void process_incoming_invite(struct sipe_core_private
*sipe_private
,
284 const gchar
*oldHeader
;
286 gboolean is_multiparty
= FALSE
;
287 gboolean was_multiparty
= TRUE
;
288 gboolean just_joined
= FALSE
;
290 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
291 const gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
292 const gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
293 const gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
294 const gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
295 const gchar
*subject
= sipmsg_find_header(msg
, "Subject");
296 GSList
*end_points
= NULL
;
297 struct sip_session
*session
;
298 struct sip_dialog
*dialog
;
299 const gchar
*ms_text_format
;
302 if (g_str_has_prefix(content_type
, "multipart/alternative")) {
303 sipe_mime_parts_foreach(content_type
, msg
->body
, sipe_invite_mime_cb
, msg
);
304 /* Reload Content-Type to get type of the selected message part */
305 content_type
= sipmsg_find_header(msg
, "Content-Type");
309 /* Invitation to join conference */
310 if (g_str_has_prefix(content_type
, "application/ms-conf-invite+xml")) {
311 process_incoming_invite_conf(sipe_private
, msg
);
316 /* Invitation to audio call */
317 if (msg
->body
&& strstr(msg
->body
, "m=audio")) {
318 process_incoming_invite_call(sipe_private
, msg
);
323 /* Only accept text invitations */
324 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
325 sip_transport_response(sipe_private
, msg
, 501, "Not implemented", NULL
);
329 // TODO There *must* be a better way to clean up the To header to add a tag...
330 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
331 oldHeader
= sipmsg_find_header(msg
, "To");
333 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
335 sipmsg_remove_header_now(msg
, "To");
336 sipmsg_add_header_now(msg
, "To", newHeader
);
339 if (end_points_hdr
) {
340 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
342 if (g_slist_length(end_points
) > 2) {
343 is_multiparty
= TRUE
;
346 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
347 is_multiparty
= TRUE
;
350 /* Multiparty session */
351 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
355 if (session
->chat_session
) {
356 /* Update roster manager for existing multiparty session */
358 sipe_chat_set_roster_manager(session
, roster_manager
);
361 gchar
*chat_title
= sipe_chat_get_name();
363 /* Convert IM session to multiparty session */
364 g_free(session
->with
);
365 session
->with
= NULL
;
366 was_multiparty
= FALSE
;
367 session
->chat_session
= sipe_chat_create_session(SIPE_CHAT_TYPE_MULTIPARTY
,
374 /* New multiparty session */
375 session
= sipe_session_add_chat(sipe_private
,
382 if (!session
->chat_session
->backend
) {
383 gchar
*self
= sip_uri_self(sipe_private
);
384 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
385 session
->chat_session
,
386 session
->chat_session
->title
,
393 from
= parse_from(sipmsg_find_header(msg
, "From"));
395 session
= sipe_session_find_or_add_im(sipe_private
, from
);
397 /* session is now initialized */
398 g_free(session
->callid
);
399 session
->callid
= g_strdup(callid
);
401 if (is_multiparty
&& end_points
) {
402 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
403 GSList
*entry
= end_points
;
405 struct sipendpoint
*end_point
= entry
->data
;
408 if (!g_strcasecmp(from
, end_point
->contact
) ||
409 !g_strcasecmp(to
, end_point
->contact
))
412 dialog
= sipe_dialog_find(session
, end_point
->contact
);
414 g_free(dialog
->theirepid
);
415 dialog
->theirepid
= end_point
->epid
;
416 end_point
->epid
= NULL
;
418 dialog
= sipe_dialog_add(session
);
420 dialog
->callid
= g_strdup(session
->callid
);
421 dialog
->with
= end_point
->contact
;
422 end_point
->contact
= NULL
;
423 dialog
->theirepid
= end_point
->epid
;
424 end_point
->epid
= NULL
;
428 /* send triggered INVITE */
429 sipe_im_invite(sipe_private
, session
, dialog
->with
, NULL
, NULL
, NULL
, TRUE
);
436 GSList
*entry
= end_points
;
438 struct sipendpoint
*end_point
= entry
->data
;
440 g_free(end_point
->contact
);
441 g_free(end_point
->epid
);
444 g_slist_free(end_points
);
447 dialog
= sipe_dialog_find(session
, from
);
449 sipe_im_cancel_dangling(sipe_private
, session
, dialog
, from
,
450 sipe_im_reenqueue_unconfirmed
);
451 /* dialog is no longer valid */
456 dialog
= sipe_dialog_add(session
);
457 dialog
->with
= g_strdup(from
);
458 dialog
->callid
= g_strdup(session
->callid
);
459 sipe_dialog_parse(dialog
, msg
, FALSE
);
461 if (is_multiparty
&& !was_multiparty
) {
462 /* add current IM counterparty to chat */
463 sipe_backend_chat_add(session
->chat_session
->backend
,
464 sipe_dialog_first(session
)->with
,
468 /* add inviting party to chat */
469 if (just_joined
&& session
->chat_session
) {
470 sipe_backend_chat_add(session
->chat_session
->backend
,
475 if (!is_multiparty
&& subject
)
476 sipe_im_topic(sipe_private
, session
, subject
);
478 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
480 /* This used only in 2005 official client, not 2007 or Reuters.
481 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
482 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
484 /* also enabled for 2005 file transfer. Didn't work otherwise. */
485 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
487 (ms_text_format
&& g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite")) )
489 if (ms_text_format
) {
490 if (g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite"))
492 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
495 gchar
*body
= (gchar
*) g_base64_decode(tmp
, &len
);
496 GSList
*parsed_body
= sipe_ft_parse_msg_body(body
);
498 dialog
= sipe_dialog_find(session
, from
);
499 sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, parsed_body
);
500 sipe_utils_nameval_free(parsed_body
);
501 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
505 else if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html"))
507 /* please do not optimize logic inside as this code may be re-enabled for other cases */
508 gchar
*html
= get_html_message(ms_text_format
, NULL
);
511 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
512 session
->chat_session
->backend
,
516 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
521 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
529 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
530 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
532 body
= g_strdup_printf(
534 "o=- 0 0 IN IP4 %s\r\n"
538 "m=%s %d sip sip:%s\r\n"
539 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
540 sipe_backend_network_ip_address(),
541 sipe_backend_network_ip_address(),
542 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
543 sip_transport_port(sipe_private
),
544 sipe_private
->username
);
545 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
549 void process_incoming_message(struct sipe_core_private
*sipe_private
,
553 const gchar
*contenttype
;
554 gboolean found
= FALSE
;
556 from
= parse_from(sipmsg_find_header(msg
, "From"));
560 SIPE_DEBUG_INFO("got message from %s: %s", from
, msg
->body
);
562 contenttype
= sipmsg_find_header(msg
, "Content-Type");
563 if (g_str_has_prefix(contenttype
, "text/plain")
564 || g_str_has_prefix(contenttype
, "text/html")
565 || g_str_has_prefix(contenttype
, "multipart/related")
566 || g_str_has_prefix(contenttype
, "multipart/alternative"))
568 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
569 gchar
*html
= get_html_message(contenttype
, msg
->body
);
571 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
574 if (session
&& session
->chat_session
) {
575 if (session
->chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
) { /* a conference */
576 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
577 gchar
*sender
= parse_from(tmp
);
579 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
580 session
->chat_session
->backend
,
584 } else { /* a multiparty chat */
585 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
586 session
->chat_session
->backend
,
591 sipe_backend_im_message(SIPE_CORE_PUBLIC
,
596 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
599 } else if (g_str_has_prefix(contenttype
, "application/im-iscomposing+xml")) {
600 sipe_xml
*isc
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
601 const sipe_xml
*state
;
605 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
610 state
= sipe_xml_child(isc
, "state");
613 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
619 statedata
= sipe_xml_data(state
);
621 if (strstr(statedata
, "active")) {
622 sipe_backend_user_feedback_typing(SIPE_CORE_PUBLIC
,
625 sipe_backend_user_feedback_typing_stop(SIPE_CORE_PUBLIC
,
631 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
633 } else if (g_str_has_prefix(contenttype
, "text/x-msmsgsinvite")) {
634 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
635 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
638 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
639 GSList
*body
= sipe_ft_parse_msg_body(msg
->body
);
640 found
= sipe_process_incoming_x_msmsgsinvite(sipe_private
, dialog
, body
);
641 sipe_utils_nameval_free(body
);
643 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
647 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
648 struct sip_session
*session
= sipe_session_find_chat_or_im(sipe_private
,
652 gchar
*errmsg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
654 sipe_user_present_error(sipe_private
, session
, errmsg
);
658 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype
);
659 sip_transport_response(sipe_private
, msg
, 415, "Unsupported media type", NULL
);
664 void process_incoming_options(struct sipe_core_private
*sipe_private
,
669 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
670 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
672 body
= g_strdup_printf(
674 "o=- 0 0 IN IP4 0.0.0.0\r\n"
676 "c=IN IP4 0.0.0.0\r\n"
678 "m=%s %d sip sip:%s\r\n"
679 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
680 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
681 sip_transport_port(sipe_private
),
682 sipe_private
->username
);
683 sip_transport_response(sipe_private
, msg
, 200, "OK", body
);
687 void process_incoming_refer(struct sipe_core_private
*sipe_private
,
690 gchar
*self
= sip_uri_self(sipe_private
);
691 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
692 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
693 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
694 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
695 struct sip_session
*session
;
696 struct sip_dialog
*dialog
;
698 session
= sipe_session_find_chat_by_callid(sipe_private
, callid
);
699 dialog
= sipe_dialog_find(session
, from
);
701 if (!session
|| !dialog
|| !session
->chat_session
||
702 (session
->chat_session
->type
!= SIPE_CHAT_TYPE_MULTIPARTY
) ||
703 !session
->chat_session
->id
||
704 !sipe_strcase_equal(session
->chat_session
->id
, self
)) {
705 sip_transport_response(sipe_private
, msg
, 500, "Server Internal Error", NULL
);
707 sip_transport_response(sipe_private
, msg
, 202, "Accepted", NULL
);
709 sipe_im_invite(sipe_private
, session
, refer_to
, NULL
, NULL
, referred_by
, FALSE
);