6 * Copyright (C) 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"
34 #include "sip-transport.h"
35 #include "sipe-backend.h"
36 #include "sipe-chat.h"
37 #include "sipe-core.h"
38 #include "sipe-core-private.h"
39 #include "sipe-dialog.h"
41 #include "sipe-groupchat.h"
44 #include "sipe-session.h"
45 #include "sipe-user.h"
46 #include "sipe-utils.h"
48 /* key must be g_free()'d */
49 static gchar
*get_unconfirmed_message_key(const gchar
*callid
,
53 return(g_strdup_printf("<%s><%d><%s><%s>", callid
, cseq
,
54 with
? "MESSAGE" : "INVITE",
58 static void insert_unconfirmed_message(struct sip_session
*session
,
59 struct sip_dialog
*dialog
,
61 struct queued_message
*message
)
63 gchar
*key
= get_unconfirmed_message_key(dialog
->callid
, dialog
->cseq
+ 1, with
);
64 g_hash_table_insert(session
->unconfirmed_messages
, key
, message
);
65 SIPE_DEBUG_INFO("insert_confirmed_message: added %s to list (count=%d)",
66 key
, g_hash_table_size(session
->unconfirmed_messages
));
69 static void remove_unconfirmed_message(struct sip_session
*session
,
72 g_hash_table_remove(session
->unconfirmed_messages
, key
);
73 SIPE_DEBUG_INFO("remove_unconfirmed_message: removed %s from list (count=%d)",
74 key
, g_hash_table_size(session
->unconfirmed_messages
));
77 static void sipe_refer_notify(struct sipe_core_private
*sipe_private
,
78 struct sip_session
*session
,
85 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
87 hdr
= g_strdup_printf(
89 "Subscription-State: %s\r\n"
90 "Content-Type: message/sipfrag\r\n",
91 status
>= 200 ? "terminated" : "active");
93 body
= g_strdup_printf(
97 sip_transport_request(sipe_private
,
110 static gboolean
process_invite_response(struct sipe_core_private
*sipe_private
,
112 struct transaction
*trans
)
114 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
115 struct sip_session
*session
;
116 struct sip_dialog
*dialog
;
118 struct queued_message
*message
;
119 struct sipmsg
*request_msg
= trans
->msg
;
121 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
124 session
= sipe_session_find_chat_or_im(sipe_private
, callid
, with
);
126 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
131 dialog
= sipe_dialog_find(session
, with
);
133 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
138 sipe_dialog_parse(dialog
, msg
, TRUE
);
140 key
= get_unconfirmed_message_key(dialog
->callid
, sipmsg_parse_cseq(msg
), NULL
);
141 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
143 if (msg
->response
!= 200) {
144 sipe_backend_buddy pbuddy
;
146 const char *warn_hdr
= sipmsg_find_header(msg
, "Warning");
149 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
152 gchar
**parts
= g_strsplit(warn_hdr
, " ", 2);
154 warning
= atoi(parts
[0]);
159 /* cancel file transfer as rejected by server */
160 if (msg
->response
== 606 && /* Not acceptable all. */
161 warning
== 309 && /* Message contents not allowed by policy */
162 message
&& g_str_has_prefix(message
->content_type
, "text/x-msmsgsinvite"))
164 GSList
*parsed_body
= sipe_ft_parse_msg_body(message
->body
);
165 sipe_ft_incoming_cancel(dialog
, parsed_body
);
166 sipe_utils_nameval_free(parsed_body
);
169 if ((pbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, with
, NULL
))) {
170 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
, pbuddy
);
174 sipe_user_present_message_undelivered(sipe_private
, session
, msg
->response
, warning
, alias
? alias
: with
, message
->body
);
176 gchar
*tmp_msg
= g_strdup_printf(_("Failed to invite %s"), alias
);
177 sipe_user_present_error(sipe_private
, session
, tmp_msg
);
182 remove_unconfirmed_message(session
, key
);
183 /* message is no longer valid */
186 sipe_dialog_remove(session
, with
);
189 if (session
->is_groupchat
) {
190 sipe_groupchat_invite_failed(sipe_private
, session
);
191 /* session is no longer valid */
198 sip_transport_ack(sipe_private
, dialog
);
199 dialog
->outgoing_invite
= NULL
;
200 dialog
->is_established
= TRUE
;
202 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
204 sipe_refer_notify(sipe_private
, session
, referred_by
, 200, "OK");
208 /* add user to chat if it is a multiparty session */
209 if (session
->chat_session
&&
210 (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
)) {
211 sipe_backend_chat_add(session
->chat_session
->backend
,
216 if (session
->is_groupchat
) {
217 sipe_groupchat_invite_response(sipe_private
, dialog
);
220 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
221 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
222 sipe_session_dequeue_message(session
);
225 sipe_im_process_queue(sipe_private
, session
);
227 remove_unconfirmed_message(session
, key
);
234 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
235 static gchar
*get_end_points(struct sipe_core_private
*sipe_private
,
236 struct sip_session
*session
)
240 if (session
== NULL
) {
244 res
= g_strdup_printf("<sip:%s>", sipe_private
->username
);
246 SIPE_DIALOG_FOREACH
{
248 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
251 if (dialog
->theirepid
) {
253 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
256 } SIPE_DIALOG_FOREACH_END
;
262 sipe_im_invite(struct sipe_core_private
*sipe_private
,
263 struct sip_session
*session
,
265 const gchar
*msg_body
,
266 const gchar
*content_type
,
267 const gchar
*referred_by
,
268 const gboolean is_triggered
)
275 char *ms_text_format
= NULL
;
276 char *ms_conversation_id
= NULL
;
277 gchar
*roster_manager
;
279 gchar
*referred_by_str
;
280 gboolean is_multiparty
=
281 session
->chat_session
&&
282 (session
->chat_session
->type
== SIPE_CHAT_TYPE_MULTIPARTY
);
283 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
285 if (dialog
&& dialog
->is_established
) {
286 SIPE_DEBUG_INFO("session with %s already has a dialog open", who
);
291 dialog
= sipe_dialog_add(session
);
292 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
293 dialog
->with
= g_strdup(who
);
296 if (!(dialog
->ourtag
)) {
297 dialog
->ourtag
= gentag();
303 char *msgtext
= NULL
;
305 const gchar
*msgr
= "";
306 struct queued_message
*message
;
309 if (!g_str_has_prefix(content_type
, "text/x-msmsgsinvite")) {
313 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
314 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat
);
316 msgr_value
= sipmsg_get_msgr_string(msgformat
);
319 msgr
= tmp
= g_strdup_printf(";msgr=%s", msgr_value
);
323 /* When Sipe reconnects after a crash, we are not able
324 * to send messages to contacts with which we had open
325 * conversations when the crash occured. Server sends
326 * error response with reason="This client has an IM
327 * session with the same conversation ID"
329 * Setting random Ms-Conversation-ID prevents this problem
330 * so we can continue the conversation. */
331 ms_conversation_id
= g_strdup_printf("Ms-Conversation-ID: %u\r\n",
332 rand() % 1000000000);
334 msgtext
= g_strdup(msg_body
);
337 base64_msg
= g_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
338 ms_text_format
= g_strdup_printf("ms-text-format: %s; charset=UTF-8%s;ms-body=%s\r\n",
339 content_type
? content_type
: "text/plain",
346 message
= g_new0(struct queued_message
,1);
347 message
->body
= g_strdup(msg_body
);
348 if (content_type
!= NULL
)
349 message
->content_type
= g_strdup(content_type
);
351 insert_unconfirmed_message(session
, dialog
, NULL
, message
);
354 contact
= get_contact(sipe_private
);
355 end_points
= get_end_points(sipe_private
, session
);
356 self
= sip_uri_self(sipe_private
);
357 roster_manager
= g_strdup_printf(
358 "Roster-Manager: %s\r\n"
362 referred_by_str
= referred_by
?
364 "Referred-By: %s\r\n",
367 hdr
= g_strdup_printf(
368 "Supported: ms-sender\r\n"
375 "Content-Type: application/sdp\r\n",
376 is_multiparty
&& sipe_strcase_equal(session
->chat_session
->id
, self
) ? roster_manager
: "",
378 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
379 is_triggered
|| is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
381 ms_text_format
? ms_text_format
: "",
382 ms_conversation_id
? ms_conversation_id
: "");
383 g_free(ms_text_format
);
384 g_free(ms_conversation_id
);
387 body
= g_strdup_printf(
389 "o=- 0 0 IN IP4 %s\r\n"
393 "m=%s %d sip null\r\n"
394 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
395 sipe_backend_network_ip_address(),
396 sipe_backend_network_ip_address(),
397 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
) ? "message" : "x-ms-message",
398 sip_transport_port(sipe_private
));
400 dialog
->outgoing_invite
= sip_transport_request(sipe_private
,
407 process_invite_response
);
410 g_free(roster_manager
);
412 g_free(referred_by_str
);
419 process_message_response(struct sipe_core_private
*sipe_private
,
421 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
424 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
425 struct sip_session
*session
= sipe_session_find_im(sipe_private
, with
);
426 struct sip_dialog
*dialog
;
428 struct queued_message
*message
;
431 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
436 dialog
= sipe_dialog_find(session
, with
);
438 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
443 key
= get_unconfirmed_message_key(sipmsg_find_header(msg
, "Call-ID"), sipmsg_parse_cseq(msg
), with
);
444 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
446 if (msg
->response
>= 400) {
447 sipe_backend_buddy pbuddy
;
449 const char *warn_hdr
= sipmsg_find_header(msg
, "Warning");
452 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
455 gchar
**parts
= g_strsplit(warn_hdr
, " ", 2);
457 warning
= atoi(parts
[0]);
462 /* cancel file transfer as rejected by server */
463 if (msg
->response
== 606 && /* Not acceptable all. */
464 warning
== 309 && /* Message contents not allowed by policy */
465 message
&& g_str_has_prefix(message
->content_type
, "text/x-msmsgsinvite"))
467 GSList
*parsed_body
= sipe_ft_parse_msg_body(msg
->body
);
468 sipe_ft_incoming_cancel(dialog
, parsed_body
);
469 sipe_utils_nameval_free(parsed_body
);
472 if ((pbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, with
, NULL
))) {
473 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
,pbuddy
);
476 sipe_user_present_message_undelivered(sipe_private
, session
,
477 msg
->response
, warning
,
478 alias
? alias
: with
,
479 message
? message
->body
: NULL
);
481 /* drop dangling IM sessions: assume that BYE from remote never reached us */
482 if (msg
->response
== 408 || /* Request timeout */
483 msg
->response
== 480 || /* Temporarily Unavailable */
484 msg
->response
== 481) { /* Call/Transaction Does Not Exist */
485 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
486 sip_transport_bye(sipe_private
, dialog
);
488 /* We might not get a valid reply to our BYE,
489 so make sure the dialog is removed for sure. */
490 sipe_dialog_remove(session
, with
);
497 const gchar
*message_id
= sipmsg_find_header(msg
, "Message-Id");
499 g_hash_table_insert(session
->conf_unconfirmed_messages
, g_strdup(message_id
), g_strdup(message
->body
));
500 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
501 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
506 remove_unconfirmed_message(session
, key
);
510 if (ret
) sipe_im_process_queue(sipe_private
, session
);
515 process_message_timeout(struct sipe_core_private
*sipe_private
,
517 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
519 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
520 struct sip_session
*session
= sipe_session_find_im(sipe_private
, with
);
522 sipe_backend_buddy pbuddy
;
526 SIPE_DEBUG_INFO_NOFORMAT("process_message_timeout: unable to find IM session");
531 /* Remove timed-out message from unconfirmed list */
532 key
= get_unconfirmed_message_key(sipmsg_find_header(msg
, "Call-ID"), sipmsg_parse_cseq(msg
), with
);
533 remove_unconfirmed_message(session
, key
);
536 if ((pbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, with
, NULL
))) {
537 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
,pbuddy
);
540 sipe_user_present_message_undelivered(sipe_private
, session
, -1, -1,
541 alias
? alias
: with
,
549 static void sipe_im_send_message(struct sipe_core_private
*sipe_private
,
550 struct sip_dialog
*dialog
,
551 const gchar
*msg_body
,
552 const gchar
*content_type
)
556 char *msgtext
= NULL
;
557 const gchar
*msgr
= "";
560 if (content_type
== NULL
)
561 content_type
= "text/plain";
563 if (!g_str_has_prefix(content_type
, "text/x-msmsgsinvite")) {
567 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
568 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat
);
570 msgr_value
= sipmsg_get_msgr_string(msgformat
);
573 msgr
= tmp2
= g_strdup_printf(";msgr=%s", msgr_value
);
577 msgtext
= g_strdup(msg_body
);
580 tmp
= get_contact(sipe_private
);
581 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
582 //hdr = g_strdup("Content-Type: text/rtf\r\n");
583 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
585 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp
, content_type
, msgr
);
589 sip_transport_request_timeout(sipe_private
,
596 process_message_response
,
598 process_message_timeout
);
604 sipe_im_process_queue (struct sipe_core_private
*sipe_private
,
605 struct sip_session
* session
)
607 GSList
*entry2
= session
->outgoing_message_queue
;
609 struct queued_message
*msg
= entry2
->data
;
611 /* for multiparty chat or conference */
612 if (session
->chat_session
) {
613 gchar
*who
= sip_uri_self(sipe_private
);
614 sipe_backend_chat_message(SIPE_CORE_PUBLIC
,
615 session
->chat_session
->backend
,
621 SIPE_DIALOG_FOREACH
{
622 struct queued_message
*message
;
624 if (dialog
->outgoing_invite
) continue; /* do not send messages as INVITE is not responded. */
626 message
= g_new0(struct queued_message
,1);
627 message
->body
= g_strdup(msg
->body
);
628 if (msg
->content_type
!= NULL
)
629 message
->content_type
= g_strdup(msg
->content_type
);
631 insert_unconfirmed_message(session
, dialog
, dialog
->with
, message
);
633 sipe_im_send_message(sipe_private
, dialog
, msg
->body
, msg
->content_type
);
634 } SIPE_DIALOG_FOREACH_END
;
636 entry2
= sipe_session_dequeue_message(session
);
640 void sipe_core_im_send(struct sipe_core_public
*sipe_public
,
644 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
645 struct sip_session
*session
;
646 struct sip_dialog
*dialog
;
647 gchar
*uri
= sip_uri(who
);
649 SIPE_DEBUG_INFO("sipe_core_im_send: '%s'", what
);
651 session
= sipe_session_find_or_add_im(sipe_private
, uri
);
652 dialog
= sipe_dialog_find(session
, uri
);
654 /* Queue the message */
655 sipe_session_enqueue_message(session
, what
, NULL
);
657 if (dialog
&& !dialog
->outgoing_invite
) {
658 sipe_im_process_queue(sipe_private
, session
);
659 } else if (!dialog
|| !dialog
->outgoing_invite
) {
660 /* Need to send the INVITE to get the outgoing dialog setup */
661 sipe_im_invite(sipe_private
, session
, uri
, what
, NULL
, NULL
, FALSE
);