core: move IM related code out of sipe.c
[siplcs.git] / src / core / sipe-im.c
blobc2714cbdca6af09c7dfd6e4685f5df6371d3e50f
1 /**
2 * @file sipe-im.c
4 * pidgin-sipe
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
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <stdlib.h>
28 #include <string.h>
30 #include <glib.h>
32 #include "sipe-common.h"
33 #include "sipmsg.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"
40 #include "sipe-ft.h"
41 #include "sipe-groupchat.h"
42 #include "sipe-im.h"
43 #include "sipe-nls.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,
50 unsigned int cseq,
51 const gchar *with)
53 return(g_strdup_printf("<%s><%d><%s><%s>", callid, cseq,
54 with ? "MESSAGE" : "INVITE",
55 with ? with : ""));
58 static void insert_unconfirmed_message(struct sip_session *session,
59 struct sip_dialog *dialog,
60 const gchar *with,
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,
70 const gchar *key)
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,
79 const gchar *who,
80 int status,
81 const gchar *desc)
83 gchar *hdr;
84 gchar *body;
85 struct sip_dialog *dialog = sipe_dialog_find(session, who);
87 hdr = g_strdup_printf(
88 "Event: refer\r\n"
89 "Subscription-State: %s\r\n"
90 "Content-Type: message/sipfrag\r\n",
91 status >= 200 ? "terminated" : "active");
93 body = g_strdup_printf(
94 "SIP/2.0 %d %s\r\n",
95 status, desc);
97 sip_transport_request(sipe_private,
98 "NOTIFY",
99 who,
100 who,
101 hdr,
102 body,
103 dialog,
104 NULL);
106 g_free(hdr);
107 g_free(body);
110 static gboolean process_invite_response(struct sipe_core_private *sipe_private,
111 struct sipmsg *msg,
112 struct transaction *trans)
114 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
115 struct sip_session *session;
116 struct sip_dialog *dialog;
117 gchar *key;
118 struct queued_message *message;
119 struct sipmsg *request_msg = trans->msg;
121 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
122 gchar *referred_by;
124 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
125 if (!session) {
126 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
127 g_free(with);
128 return FALSE;
131 dialog = sipe_dialog_find(session, with);
132 if (!dialog) {
133 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
134 g_free(with);
135 return FALSE;
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;
145 gchar *alias = NULL;
146 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
147 int warning = -1;
149 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
151 if (warn_hdr) {
152 gchar **parts = g_strsplit(warn_hdr, " ", 2);
153 if (parts[0]) {
154 warning = atoi(parts[0]);
156 g_strfreev(parts);
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);
173 if (message) {
174 sipe_user_present_message_undelivered(sipe_private, session, msg->response, warning, alias ? alias : with, message->body);
175 } else {
176 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
177 sipe_user_present_error(sipe_private, session, tmp_msg);
178 g_free(tmp_msg);
180 g_free(alias);
182 remove_unconfirmed_message(session, key);
183 /* message is no longer valid */
184 g_free(key);
186 sipe_dialog_remove(session, with);
187 g_free(with);
189 if (session->is_groupchat) {
190 sipe_groupchat_invite_failed(sipe_private, session);
191 /* session is no longer valid */
194 return FALSE;
197 dialog->cseq = 0;
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"));
203 if (referred_by) {
204 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
205 g_free(referred_by);
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,
212 with,
213 TRUE);
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);
229 g_free(key);
230 g_free(with);
231 return TRUE;
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)
238 gchar *res;
240 if (session == NULL) {
241 return NULL;
244 res = g_strdup_printf("<sip:%s>", sipe_private->username);
246 SIPE_DIALOG_FOREACH {
247 gchar *tmp = res;
248 res = g_strdup_printf("%s, <%s>", res, dialog->with);
249 g_free(tmp);
251 if (dialog->theirepid) {
252 tmp = res;
253 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
254 g_free(tmp);
256 } SIPE_DIALOG_FOREACH_END;
258 return res;
261 void
262 sipe_im_invite(struct sipe_core_private *sipe_private,
263 struct sip_session *session,
264 const gchar *who,
265 const gchar *msg_body,
266 const gchar *content_type,
267 const gchar *referred_by,
268 const gboolean is_triggered)
270 gchar *hdr;
271 gchar *to;
272 gchar *contact;
273 gchar *body;
274 gchar *self;
275 char *ms_text_format = NULL;
276 char *ms_conversation_id = NULL;
277 gchar *roster_manager;
278 gchar *end_points;
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);
287 return;
290 if (!dialog) {
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();
300 to = sip_uri(who);
302 if (msg_body) {
303 char *msgtext = NULL;
304 char *base64_msg;
305 const gchar *msgr = "";
306 struct queued_message *message;
307 gchar *tmp = NULL;
309 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
310 char *msgformat;
311 gchar *msgr_value;
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);
317 g_free(msgformat);
318 if (msgr_value) {
319 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
320 g_free(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);
333 } else {
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",
340 msgr,
341 base64_msg);
342 g_free(msgtext);
343 g_free(tmp);
344 g_free(base64_msg);
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"
359 "EndPoints: %s\r\n",
360 self,
361 end_points);
362 referred_by_str = referred_by ?
363 g_strdup_printf(
364 "Referred-By: %s\r\n",
365 referred_by)
366 : g_strdup("");
367 hdr = g_strdup_printf(
368 "Supported: ms-sender\r\n"
369 "%s"
370 "%s"
371 "%s"
372 "%s"
373 "Contact: %s\r\n%s"
374 "%s"
375 "Content-Type: application/sdp\r\n",
376 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
377 referred_by_str,
378 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
379 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
380 contact,
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);
385 g_free(self);
387 body = g_strdup_printf(
388 "v=0\r\n"
389 "o=- 0 0 IN IP4 %s\r\n"
390 "s=session\r\n"
391 "c=IN IP4 %s\r\n"
392 "t=0 0\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,
401 "INVITE",
404 hdr,
405 body,
406 dialog,
407 process_invite_response);
409 g_free(to);
410 g_free(roster_manager);
411 g_free(end_points);
412 g_free(referred_by_str);
413 g_free(body);
414 g_free(hdr);
415 g_free(contact);
418 static gboolean
419 process_message_response(struct sipe_core_private *sipe_private,
420 struct sipmsg *msg,
421 SIPE_UNUSED_PARAMETER struct transaction *trans)
423 gboolean ret = TRUE;
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;
427 gchar *key;
428 struct queued_message *message;
430 if (!session) {
431 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
432 g_free(with);
433 return FALSE;
436 dialog = sipe_dialog_find(session, with);
437 if (!dialog) {
438 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
439 g_free(with);
440 return FALSE;
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;
448 gchar *alias = NULL;
449 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
450 int warning = -1;
452 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
454 if (warn_hdr) {
455 gchar **parts = g_strsplit(warn_hdr, " ", 2);
456 if (parts[0]) {
457 warning = atoi(parts[0]);
459 g_strfreev(parts);
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);
491 dialog = NULL;
494 g_free(alias);
495 ret = FALSE;
496 } else {
497 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
498 if (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);
507 g_free(key);
508 g_free(with);
510 if (ret) sipe_im_process_queue(sipe_private, session);
511 return ret;
514 static gboolean
515 process_message_timeout(struct sipe_core_private *sipe_private,
516 struct sipmsg *msg,
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);
521 gchar *key;
522 sipe_backend_buddy pbuddy;
523 gchar *alias = NULL;
525 if (!session) {
526 SIPE_DEBUG_INFO_NOFORMAT("process_message_timeout: unable to find IM session");
527 g_free(with);
528 return TRUE;
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);
534 g_free(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,
542 msg->body);
544 g_free(alias);
545 g_free(with);
546 return TRUE;
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)
554 gchar *hdr;
555 gchar *tmp;
556 char *msgtext = NULL;
557 const gchar *msgr = "";
558 gchar *tmp2 = NULL;
560 if (content_type == NULL)
561 content_type = "text/plain";
563 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
564 char *msgformat;
565 gchar *msgr_value;
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);
571 g_free(msgformat);
572 if (msgr_value) {
573 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
574 g_free(msgr_value);
576 } else {
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);
586 g_free(tmp);
587 g_free(tmp2);
589 sip_transport_request_timeout(sipe_private,
590 "MESSAGE",
591 dialog->with,
592 dialog->with,
593 hdr,
594 msgtext,
595 dialog,
596 process_message_response,
598 process_message_timeout);
599 g_free(msgtext);
600 g_free(hdr);
603 void
604 sipe_im_process_queue (struct sipe_core_private *sipe_private,
605 struct sip_session * session)
607 GSList *entry2 = session->outgoing_message_queue;
608 while (entry2) {
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,
616 who,
617 msg->body);
618 g_free(who);
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,
641 const gchar *who,
642 const gchar *what)
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);
664 g_free(uri);
668 Local Variables:
669 mode: c
670 c-file-style: "bsd"
671 indent-tabs-mode: t
672 tab-width: 8
673 End: