1 /**
2  * @file sipe-chat.c
3  *
4  * pidgin-sipe
5  *
6  * Copyright (C) 2009-2017 SIPE Project <http://sipe.sourceforge.net/>
7  *
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.
12  *
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.
17  *
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
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 
31 #include <glib.h>
32 
33 #include "sipe-common.h"
34 #include "sipmsg.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"
42 #include "sipe-groupchat.h"
43 #include "sipe-im.h"
44 #include "sipe-nls.h"
45 #include "sipe-schedule.h"
46 #include "sipe-session.h"
47 #include "sipe-user.h"
48 #include "sipe-utils.h"
49 #include "sipe-xml.h"
50 
51 /**
52  * Invite @who to chat
53  *
54  * @param sipe_private SIPE core private data
55  * @param session SIPE session for chat
56  * @param who URI whom to invite to chat.
57  */
58 static void
59 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
60 		    struct sip_session *session,
61 		    const gchar *who);
62 
63 static GList *chat_sessions = NULL;
64 
sipe_chat_create_session(guint type,const gchar * id,const gchar * title)65 struct sipe_chat_session *sipe_chat_create_session(guint type,
66 						   const gchar *id,
67 						   const gchar *title)
68 {
69 	struct sipe_chat_session *session = g_new0(struct sipe_chat_session, 1);
70 	if (id)
71 		session->id = g_strdup(id);
72 	session->title = g_strdup(title);
73 	session->type  = type;
74 	chat_sessions  = g_list_prepend(chat_sessions, session);
75 	return(session);
76 }
77 
sipe_chat_remove_session(struct sipe_chat_session * session)78 void sipe_chat_remove_session(struct sipe_chat_session *session)
79 {
80 	chat_sessions = g_list_remove(chat_sessions, session);
81 	sipe_backend_chat_session_destroy(session->backend);
82 	g_free(session->title);
83 	g_free(session->id);
84 	g_free(session->join_url);
85 	g_free(session->dial_in_conf_id);
86 	g_free(session->organizer);
87 
88 	if (session->appshare_ask_ctx) {
89 		sipe_user_close_ask(session->appshare_ask_ctx);
90 	}
91 
92 	g_free(session);
93 }
94 
sipe_chat_destroy(void)95 void sipe_chat_destroy(void)
96 {
97 	while (chat_sessions) {
98 		struct sipe_chat_session *chat_session = chat_sessions->data;
99 		SIPE_DEBUG_INFO("sipe_chat_destroy: '%s' (%s)",
100 				chat_session->title, chat_session->id);
101 		sipe_chat_remove_session(chat_session);
102 	}
103 }
104 
sipe_core_chat_id(SIPE_UNUSED_PARAMETER struct sipe_core_public * sipe_public,struct sipe_chat_session * chat_session)105 const gchar *sipe_core_chat_id(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
106 			       struct sipe_chat_session *chat_session)
107 {
108 	return(chat_session->id);
109 }
110 
sipe_core_chat_type(struct sipe_chat_session * chat_session)111 guint sipe_core_chat_type(struct sipe_chat_session *chat_session)
112 {
113 	return(chat_session ? chat_session->type : SIPE_CHAT_TYPE_UNKNOWN);
114 }
115 
sipe_core_chat_invite(struct sipe_core_public * sipe_public,struct sipe_chat_session * chat_session,const char * name)116 void sipe_core_chat_invite(struct sipe_core_public *sipe_public,
117 			   struct sipe_chat_session *chat_session,
118 			   const char *name)
119 {
120 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
121 
122 	SIPE_DEBUG_INFO("sipe_core_chat_create: who '%s'", name);
123 
124 	switch (chat_session->type) {
125 	case SIPE_CHAT_TYPE_MULTIPARTY:
126 	case SIPE_CHAT_TYPE_CONFERENCE:
127 		{
128 			struct sip_session *session = sipe_session_find_chat(sipe_private,
129 									     chat_session);
130 
131 			if (session) {
132 				gchar *uri = sip_uri(name);
133 				sipe_invite_to_chat(sipe_private, session, uri);
134 				g_free(uri);
135 			}
136 		}
137 		break;
138 	case SIPE_CHAT_TYPE_GROUPCHAT:
139 		/* @TODO */
140 		SIPE_DEBUG_INFO_NOFORMAT("GROUP CHAT: INVITE NOT IMPLEMENTED!");
141 		break;
142 	default:
143 		break;
144 	}
145 }
146 
sipe_core_chat_rejoin(struct sipe_core_public * sipe_public,struct sipe_chat_session * chat_session)147 void sipe_core_chat_rejoin(struct sipe_core_public *sipe_public,
148 			   struct sipe_chat_session *chat_session)
149 {
150 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
151 
152 	SIPE_DEBUG_INFO("sipe_core_chat_rejoin: '%s'", chat_session->title);
153 
154 	switch (chat_session->type) {
155 	case SIPE_CHAT_TYPE_MULTIPARTY:
156 		{
157 			struct sip_session *session = sipe_session_add_chat(sipe_private,
158 									    chat_session,
159 									    TRUE,
160 									    NULL);
161 			gchar *self = sip_uri_self(sipe_private);
162 
163 			sipe_invite_to_chat(sipe_private, session, self);
164 			sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
165 						 chat_session->backend,
166 						 self,
167 						 chat_session->title);
168 			g_free(self);
169 		}
170 		break;
171 	case SIPE_CHAT_TYPE_CONFERENCE:
172 		sipe_conf_create(sipe_private, chat_session, NULL);
173 		break;
174 	case SIPE_CHAT_TYPE_GROUPCHAT:
175 		sipe_groupchat_rejoin(sipe_private, chat_session);
176 		break;
177 	default:
178 		break;
179 	}
180 }
181 
sipe_core_chat_leave(struct sipe_core_public * sipe_public,struct sipe_chat_session * chat_session)182 void sipe_core_chat_leave(struct sipe_core_public *sipe_public,
183 			  struct sipe_chat_session *chat_session)
184 {
185 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
186 
187 	SIPE_DEBUG_INFO("sipe_core_chat_leave: '%s'", chat_session->title);
188 
189 	switch (chat_session->type) {
190 	case SIPE_CHAT_TYPE_MULTIPARTY:
191 	case SIPE_CHAT_TYPE_CONFERENCE:
192 		{
193 			struct sip_session *session = sipe_session_find_chat(sipe_private,
194 									     chat_session);
195 
196 			if (session) {
197 				sipe_session_close(sipe_private, session);
198 			}
199 		}
200 		break;
201 	case SIPE_CHAT_TYPE_GROUPCHAT:
202 		sipe_groupchat_leave(sipe_private, chat_session);
203 		break;
204 	default:
205 		break;
206 	}
207 }
208 
sipe_core_chat_send(struct sipe_core_public * sipe_public,struct sipe_chat_session * chat_session,const char * what)209 void sipe_core_chat_send(struct sipe_core_public *sipe_public,
210 			 struct sipe_chat_session *chat_session,
211 			 const char *what)
212 {
213 	struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
214 
215 	SIPE_DEBUG_INFO("sipe_core_chat_send: '%s' to '%s'",
216 			what, chat_session->title);
217 
218 	switch (chat_session->type) {
219 	case SIPE_CHAT_TYPE_MULTIPARTY:
220 	case SIPE_CHAT_TYPE_CONFERENCE:
221 		{
222 			struct sip_session *session = sipe_session_find_chat(sipe_private,
223 									     chat_session);
224 
225 			if (session) {
226 				sipe_session_enqueue_message(session,
227 							     what,
228 							     NULL);
229 				sipe_im_process_queue(sipe_private, session);
230 			}
231 		}
232 		break;
233 	case SIPE_CHAT_TYPE_GROUPCHAT:
234 		sipe_groupchat_send(sipe_private, chat_session, what);
235 		break;
236 	default:
237 		break;
238 	}
239 }
240 
241 gchar *
sipe_chat_get_name(void)242 sipe_chat_get_name(void)
243 {
244 	/**
245 	 * A non-volatile ID counter.
246 	 * Should survive connection drop & reconnect.
247 	 */
248 	static guint chat_seq = 0;
249 
250 	/* Generate next ID */
251 	gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++chat_seq);
252 	SIPE_DEBUG_INFO("sipe_chat_get_name: added new: %s", chat_name);
253 
254 	return chat_name;
255 }
256 
257 static void
sipe_refer(struct sipe_core_private * sipe_private,struct sip_session * session,const gchar * who)258 sipe_refer(struct sipe_core_private *sipe_private,
259 	   struct sip_session *session,
260 	   const gchar *who)
261 {
262 	gchar *hdr;
263 	gchar *contact;
264 	struct sip_dialog *dialog = sipe_dialog_find(session,
265 						     session->chat_session->id);
266 	const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
267 
268 	contact = get_contact(sipe_private);
269 	hdr = g_strdup_printf(
270 		"Contact: %s\r\n"
271 		"Refer-to: <%s>\r\n"
272 		"Referred-By: <sip:%s>%s%s;epid=%s\r\n"
273 		"Require: com.microsoft.rtc-multiparty\r\n",
274 		contact,
275 		who,
276 		sipe_private->username,
277 		ourtag ? ";tag=" : "",
278 		ourtag ? ourtag : "",
279 		sip_transport_epid(sipe_private));
280 
281 	sip_transport_request(sipe_private,
282 			      "REFER",
283 			      session->chat_session->id,
284 			      session->chat_session->id,
285 			      hdr,
286 			      NULL,
287 			      dialog,
288 			      NULL);
289 
290 	g_free(hdr);
291 	g_free(contact);
292 }
293 
294 static gboolean
sipe_is_election_finished(struct sip_session * session)295 sipe_is_election_finished(struct sip_session *session)
296 {
297 	gboolean res = TRUE;
298 
299 	SIPE_DIALOG_FOREACH {
300 		if (dialog->election_vote == 0) {
301 			res = FALSE;
302 			break;
303 		}
304 	} SIPE_DIALOG_FOREACH_END;
305 
306 	if (res) {
307 		session->is_voting_in_progress = FALSE;
308 	}
309 	return res;
310 }
311 
312 static gboolean
313 process_info_response(struct sipe_core_private *sipe_private,
314 		      struct sipmsg *msg,
315 		      struct transaction *trans);
316 
317 static void
sipe_send_election_set_rm(struct sipe_core_private * sipe_private,struct sip_dialog * dialog)318 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
319 			  struct sip_dialog *dialog)
320 {
321 	const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
322 
323 	gchar *body = g_strdup_printf(
324 		"<?xml version=\"1.0\"?>\r\n"
325 		"<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
326 		"<SetRM uri=\"sip:%s\"/></action>\r\n",
327 		sipe_private->username);
328 
329 	sip_transport_info(sipe_private,
330 			   hdr,
331 			   body,
332 			   dialog,
333 			   process_info_response);
334 
335 	g_free(body);
336 }
337 
338 void
sipe_process_pending_invite_queue(struct sipe_core_private * sipe_private,struct sip_session * session)339 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
340 				  struct sip_session *session)
341 {
342 	gchar *invitee;
343 	GSList *entry = session->pending_invite_queue;
344 
345 	while (entry) {
346 		invitee = entry->data;
347 		sipe_invite_to_chat(sipe_private, session, invitee);
348 		entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
349 		g_free(invitee);
350 	}
351 }
352 
sipe_chat_set_roster_manager(struct sip_session * session,const gchar * roster_manager)353 void sipe_chat_set_roster_manager(struct sip_session *session,
354 				  const gchar *roster_manager)
355 {
356 	struct sipe_chat_session *chat_session = session->chat_session;
357 
358 	g_free(chat_session->id);
359 	chat_session->id = NULL;
360 	if (roster_manager)
361 		chat_session->id = g_strdup(roster_manager);
362 }
363 
364 static void
sipe_election_result(struct sipe_core_private * sipe_private,void * sess)365 sipe_election_result(struct sipe_core_private *sipe_private,
366 		     void *sess)
367 {
368 	struct sip_session *session = (struct sip_session *)sess;
369 	const gchar *rival = NULL;
370 
371 	if (session->chat_session->id) {
372 		SIPE_DEBUG_INFO(
373 			"sipe_election_result: RM has already been elected in the meantime. It is %s",
374 			session->chat_session->id);
375 		return;
376 	}
377 
378 	session->is_voting_in_progress = FALSE;
379 
380 	SIPE_DIALOG_FOREACH {
381 		if (dialog->election_vote < 0) {
382 			rival = dialog->with;
383 			break;
384 		}
385 	} SIPE_DIALOG_FOREACH_END;
386 
387 	if (rival) {
388 		SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
389 	} else {
390 		gchar *self = sip_uri_self(sipe_private);
391 
392 		SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
393 
394 		sipe_chat_set_roster_manager(session, self);
395 		g_free(self);
396 
397 		SIPE_DIALOG_FOREACH {
398 			/* send SetRM to each chat participant*/
399 			sipe_send_election_set_rm(sipe_private, dialog);
400 		} SIPE_DIALOG_FOREACH_END;
401 	}
402 	session->bid = 0;
403 
404 	sipe_process_pending_invite_queue(sipe_private, session);
405 }
406 
407 static gboolean
process_info_response(struct sipe_core_private * sipe_private,struct sipmsg * msg,SIPE_UNUSED_PARAMETER struct transaction * trans)408 process_info_response(struct sipe_core_private *sipe_private,
409 		      struct sipmsg *msg,
410 		      SIPE_UNUSED_PARAMETER struct transaction *trans)
411 {
412 	const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
413 	const gchar *callid = sipmsg_find_header(msg, "Call-ID");
414 	struct sip_dialog *dialog;
415 	struct sip_session *session;
416 
417 	session = sipe_session_find_chat_by_callid(sipe_private, callid);
418 	if (!session) {
419 		SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
420 		return FALSE;
421 	}
422 
423 	if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
424 		sipe_xml *xn_action                    = sipe_xml_parse(msg->body, msg->bodylen);
425 		const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
426 		const sipe_xml *xn_set_rm_response     = sipe_xml_child(xn_action, "SetRMResponse");
427 
428 		if (xn_request_rm_response) {
429 			const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
430 			const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
431 
432 			dialog = sipe_dialog_find(session, with);
433 			if (!dialog) {
434 				SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
435 				sipe_xml_free(xn_action);
436 				return FALSE;
437 			}
438 
439 			if (allow && !g_ascii_strcasecmp(allow, "true")) {
440 				SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
441 				dialog->election_vote = 1;
442 			} else if (allow && !g_ascii_strcasecmp(allow, "false")) {
443 				SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
444 				dialog->election_vote = -1;
445 			}
446 
447 			if (sipe_is_election_finished(session)) {
448 				sipe_election_result(sipe_private,
449 						     session);
450 			}
451 
452 		} else if (xn_set_rm_response) {
453 
454 		}
455 		sipe_xml_free(xn_action);
456 
457 	}
458 
459 	return TRUE;
460 }
461 
462 static void
sipe_send_election_request_rm(struct sipe_core_private * sipe_private,struct sip_dialog * dialog,int bid)463 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
464 			      struct sip_dialog *dialog,
465 			      int bid)
466 {
467 	const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
468 
469 	gchar *body = g_strdup_printf(
470 		"<?xml version=\"1.0\"?>\r\n"
471 		"<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
472 		"<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
473 		sipe_private->username, bid);
474 
475 	sip_transport_info(sipe_private,
476 			   hdr,
477 			   body,
478 			   dialog,
479 			   process_info_response);
480 
481 	g_free(body);
482 }
483 
484 static void
sipe_election_start(struct sipe_core_private * sipe_private,struct sip_session * session)485 sipe_election_start(struct sipe_core_private *sipe_private,
486 		    struct sip_session *session)
487 {
488 	if (session->is_voting_in_progress) {
489 		SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
490 		return;
491 	} else {
492 		session->is_voting_in_progress = TRUE;
493 	}
494 	session->bid = rand();
495 
496 	SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
497 
498 	SIPE_DIALOG_FOREACH {
499 		/* reset election_vote for each chat participant */
500 		dialog->election_vote = 0;
501 
502 		/* send RequestRM to each chat participant*/
503 		sipe_send_election_request_rm(sipe_private, dialog, session->bid);
504 	} SIPE_DIALOG_FOREACH_END;
505 
506 	sipe_schedule_seconds(sipe_private,
507 			      "<+election-result>",
508 			      session,
509 			      15,
510 			      sipe_election_result,
511 			      NULL);
512 }
513 
514 static void
sipe_invite_to_chat(struct sipe_core_private * sipe_private,struct sip_session * session,const gchar * who)515 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
516 		    struct sip_session *session,
517 		    const gchar *who)
518 {
519 	/* a conference */
520 	if (session->chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)
521 	{
522 		sipe_invite_conf(sipe_private, session, who);
523 	}
524 	else /* a multi-party chat */
525 	{
526 		gchar *self = sip_uri_self(sipe_private);
527 		if (session->chat_session->id) {
528 			if (sipe_strcase_equal(session->chat_session->id, self)) {
529 				sipe_im_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
530 			} else {
531 				sipe_refer(sipe_private, session, who);
532 			}
533 		} else {
534 			SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_to_chat: no RM available");
535 
536 			session->pending_invite_queue = sipe_utils_slist_insert_unique_sorted(session->pending_invite_queue,
537 											      g_strdup(who),
538 											      (GCompareFunc)strcmp,
539 											      g_free);
540 			sipe_election_start(sipe_private, session);
541 		}
542 		g_free(self);
543 	}
544 }
545 
546 /*
547   Local Variables:
548   mode: c
549   c-file-style: "bsd"
550   indent-tabs-mode: t
551   tab-width: 8
552   End:
553 */
554