core cleanup: rename core & backend API headers
[siplcs.git] / src / core / sipe-conf.c
blob581b31b54c432dc71aa1a29c51209bd004deaaca
1 /**
2 * @file sipe-conf.c
4 * pidgin-sipe
6 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
32 #include <glib.h>
34 #include "conversation.h"
35 #include "debug.h"
37 #include "sipe-common.h"
38 #include "sipmsg.h"
39 #include "sip-sec.h"
40 #include "sipe-chat.h"
41 #include "sipe-conf.h"
42 #include "sipe-dialog.h"
43 #include "sipe-nls.h"
44 #include "sipe-session.h"
45 #include "sipe-utils.h"
46 #include "sipe.h"
48 /**
49 * Add Conference request to FocusFactory.
50 * @param focus_factory_uri (%s) Ex.: sip:bob7@boston.local;gruu;opaque=app:conf:focusfactory
51 * @param from (%s) Ex.: sip:bob7@boston.local
52 * @param request_id (%d) Ex.: 1094520
53 * @param conference_id (%s) Ex.: 8386E6AEAAA41E4AA6627BA76D43B6D1
54 * @param expiry_time (%s) Ex.: 2009-07-13T17:57:09Z , Default duration: 7 hours
56 #define SIPE_SEND_CONF_ADD \
57 "<?xml version=\"1.0\"?>"\
58 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" "\
59 "xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
60 "C3PVersion=\"1\" "\
61 "to=\"%s\" "\
62 "from=\"%s\" "\
63 "requestId=\"%d\">"\
64 "<addConference>"\
65 "<ci:conference-info xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" entity=\"\" xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\">"\
66 "<ci:conference-description>"\
67 "<ci:subject/>"\
68 "<msci:conference-id>%s</msci:conference-id>"\
69 "<msci:expiry-time>%s</msci:expiry-time>"\
70 "<msci:admission-policy>openAuthenticated</msci:admission-policy>"\
71 "</ci:conference-description>"\
72 "<msci:conference-view>"\
73 "<msci:entity-view entity=\"chat\"/>"\
74 "</msci:conference-view>"\
75 "</ci:conference-info>"\
76 "</addConference>"\
77 "</request>"
79 /**
80 * AddUser request to Focus.
81 * Params:
82 * focus_URI, from, request_id, focus_URI, from, endpoint_GUID
84 #define SIPE_SEND_CONF_ADD_USER \
85 "<?xml version=\"1.0\"?>"\
86 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
87 "C3PVersion=\"1\" "\
88 "to=\"%s\" "\
89 "from=\"%s\" "\
90 "requestId=\"%d\">"\
91 "<addUser>"\
92 "<conferenceKeys confEntity=\"%s\"/>"\
93 "<ci:user xmlns:ci=\"urn:ietf:params:xml:ns:conference-info\" entity=\"%s\">"\
94 "<ci:roles>"\
95 "<ci:entry>attendee</ci:entry>"\
96 "</ci:roles>"\
97 "<ci:endpoint entity=\"{%s}\" xmlns:msci=\"http://schemas.microsoft.com/rtc/2005/08/confinfoextensions\"/>"\
98 "</ci:user>"\
99 "</addUser>"\
100 "</request>"
103 * ModifyUserRoles request to Focus. Makes user a leader.
104 * @param focus_uri (%s)
105 * @param from (%s)
106 * @param request_id (%d)
107 * @param focus_uri (%s)
108 * @param who (%s)
110 #define SIPE_SEND_CONF_MODIFY_USER_ROLES \
111 "<?xml version=\"1.0\"?>"\
112 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
113 "C3PVersion=\"1\" "\
114 "to=\"%s\" "\
115 "from=\"%s\" "\
116 "requestId=\"%d\">"\
117 "<modifyUserRoles>"\
118 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"\
119 "<user-roles xmlns=\"urn:ietf:params:xml:ns:conference-info\">"\
120 "<entry>presenter</entry>"\
121 "</user-roles>"\
122 "</modifyUserRoles>"\
123 "</request>"
126 * ModifyConferenceLock request to Focus. Locks/unlocks conference.
127 * @param focus_uri (%s)
128 * @param from (%s)
129 * @param request_id (%d)
130 * @param focus_uri (%s)
131 * @param locked (%s) "true" or "false" values applicable
133 #define SIPE_SEND_CONF_MODIFY_CONF_LOCK \
134 "<?xml version=\"1.0\"?>"\
135 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
136 "C3PVersion=\"1\" "\
137 "to=\"%s\" "\
138 "from=\"%s\" "\
139 "requestId=\"%d\">"\
140 "<modifyConferenceLock>"\
141 "<conferenceKeys confEntity=\"%s\"/>"\
142 "<locked>%s</locked>"\
143 "</modifyConferenceLock>"\
144 "</request>"
147 * ModifyConferenceLock request to Focus. Locks/unlocks conference.
148 * @param focus_uri (%s)
149 * @param from (%s)
150 * @param request_id (%d)
151 * @param focus_uri (%s)
152 * @param who (%s)
154 #define SIPE_SEND_CONF_DELETE_USER \
155 "<?xml version=\"1.0\"?>"\
156 "<request xmlns=\"urn:ietf:params:xml:ns:cccp\" xmlns:mscp=\"http://schemas.microsoft.com/rtc/2005/08/cccpextensions\" "\
157 "C3PVersion=\"1\" "\
158 "to=\"%s\" "\
159 "from=\"%s\" "\
160 "requestId=\"%d\">"\
161 "<deleteUser>"\
162 "<userKeys confEntity=\"%s\" userEntity=\"%s\"/>"\
163 "</deleteUser>"\
164 "</request>"
167 * Invite counterparty to join conference.
168 * @param focus_uri (%s)
169 * @param subject (%s) of conference
171 #define SIPE_SEND_CONF_INVITE \
172 "<Conferencing version=\"2.0\">"\
173 "<focus-uri>%s</focus-uri>"\
174 "<subject>%s</subject>"\
175 "<im available=\"true\">"\
176 "<first-im/>"\
177 "</im>"\
178 "</Conferencing>"
181 * Generates random GUID.
182 * This method is borrowed from pidgin's msnutils.c
184 static char *
185 rand_guid()
187 return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X",
188 rand() % 0xAAFF + 0x1111,
189 rand() % 0xAAFF + 0x1111,
190 rand() % 0xAAFF + 0x1111,
191 rand() % 0xAAFF + 0x1111,
192 rand() % 0xAAFF + 0x1111,
193 rand() % 0xAAFF + 0x1111,
194 rand() % 0xAAFF + 0x1111,
195 rand() % 0xAAFF + 0x1111);
199 * @param expires not respected if set to negative value (E.g. -1)
201 static void
202 sipe_subscribe_conference(struct sipe_account_data *sip,
203 struct sip_session *session,
204 const int expires)
206 gchar *expires_hdr = (expires >= 0) ? g_strdup_printf("Expires: %d\r\n", expires) : g_strdup("");
207 gchar *contact = get_contact(sip);
208 gchar *hdr = g_strdup_printf(
209 "Event: conference\r\n"
210 "%s"
211 "Accept: application/conference-info+xml\r\n"
212 "Supported: com.microsoft.autoextend\r\n"
213 "Supported: ms-benotify\r\n"
214 "Proxy-Require: ms-benotify\r\n"
215 "Supported: ms-piggyback-first-notify\r\n"
216 "Contact: %s\r\n",
217 expires_hdr,
218 contact);
219 g_free(expires_hdr);
220 g_free(contact);
222 send_sip_request(sip->gc,
223 "SUBSCRIBE",
224 session->focus_uri,
225 session->focus_uri,
226 hdr,
228 NULL,
229 process_subscribe_response);
230 g_free(hdr);
233 /** Invite us to the focus callback */
234 static gboolean
235 process_invite_conf_focus_response(struct sipe_account_data *sip,
236 struct sipmsg *msg,
237 SIPE_UNUSED_PARAMETER struct transaction *trans)
239 struct sip_session *session = NULL;
240 char *focus_uri = parse_from(sipmsg_find_header(msg, "To"));
242 session = sipe_session_find_conference(sip, focus_uri);
244 if (!session) {
245 purple_debug_info("sipe", "process_invite_conf_focus_response: unable to find conf session with focus=%s\n", focus_uri);
246 g_free(focus_uri);
247 return FALSE;
250 if (!session->focus_dialog) {
251 purple_debug_info("sipe", "process_invite_conf_focus_response: session's focus_dialog is NULL\n");
252 g_free(focus_uri);
253 return FALSE;
256 sipe_dialog_parse(session->focus_dialog, msg, TRUE);
258 if (msg->response >= 200) {
259 /* send ACK to focus */
260 session->focus_dialog->cseq = 0;
261 send_sip_request(sip->gc, "ACK", session->focus_dialog->with, session->focus_dialog->with, NULL, NULL, session->focus_dialog, NULL);
262 session->focus_dialog->outgoing_invite = NULL;
263 session->focus_dialog->is_established = TRUE;
266 if (msg->response >= 400) {
267 purple_debug_info("sipe", "process_invite_conf_focus_response: INVITE response is not 200. Failed to join focus.\n");
268 /* @TODO notify user of failure to join focus */
269 sipe_session_remove(sip, session);
270 g_free(focus_uri);
271 return FALSE;
272 } else if (msg->response == 200) {
273 xmlnode *xn_response = xmlnode_from_str(msg->body, msg->bodylen);
274 const gchar *code = xmlnode_get_attrib(xn_response, "code");
275 if (sipe_strequal(code, "success")) {
276 /* subscribe to focus */
277 sipe_subscribe_conference(sip, session, -1);
279 xmlnode_free(xn_response);
282 g_free(focus_uri);
283 return TRUE;
286 /** Invite us to the focus */
287 void
288 sipe_invite_conf_focus(struct sipe_account_data *sip,
289 struct sip_session *session)
291 gchar *hdr;
292 gchar *contact;
293 gchar *body;
294 gchar *self;
296 if (session->focus_dialog && session->focus_dialog->is_established) {
297 purple_debug_info("sipe", "session with %s already has a dialog open\n", session->focus_uri);
298 return;
301 if(!session->focus_dialog) {
302 session->focus_dialog = g_new0(struct sip_dialog, 1);
303 session->focus_dialog->callid = gencallid();
304 session->focus_dialog->with = g_strdup(session->focus_uri);
305 session->focus_dialog->endpoint_GUID = rand_guid();
307 if (!(session->focus_dialog->ourtag)) {
308 session->focus_dialog->ourtag = gentag();
311 contact = get_contact(sip);
312 hdr = g_strdup_printf(
313 "Supported: ms-sender\r\n"
314 "Contact: %s\r\n"
315 "Content-Type: application/cccp+xml\r\n",
316 contact);
317 g_free(contact);
319 /* @TODO put request_id to queue to further compare with incoming one */
320 /* focus_URI, from, request_id, focus_URI, from, endpoint_GUID */
321 self = sip_uri_self(sip);
322 body = g_strdup_printf(
323 SIPE_SEND_CONF_ADD_USER,
324 session->focus_dialog->with,
325 self,
326 session->request_id++,
327 session->focus_dialog->with,
328 self,
329 session->focus_dialog->endpoint_GUID);
330 g_free(self);
332 session->focus_dialog->outgoing_invite = send_sip_request(sip->gc,
333 "INVITE",
334 session->focus_dialog->with,
335 session->focus_dialog->with,
336 hdr,
337 body,
338 session->focus_dialog,
339 process_invite_conf_focus_response);
340 g_free(body);
341 g_free(hdr);
344 /** Modify User Role */
345 void
346 sipe_conf_modify_user_role(struct sipe_account_data *sip,
347 struct sip_session *session,
348 const gchar* who)
350 gchar *hdr;
351 gchar *body;
352 gchar *self;
354 if (!session->focus_dialog || !session->focus_dialog->is_established) {
355 purple_debug_info("sipe", "sipe_conf_modify_user_role: no dialog with focus, exiting.\n");
356 return;
359 hdr = g_strdup(
360 "Content-Type: application/cccp+xml\r\n");
362 /* @TODO put request_id to queue to further compare with incoming one */
363 self = sip_uri_self(sip);
364 body = g_strdup_printf(
365 SIPE_SEND_CONF_MODIFY_USER_ROLES,
366 session->focus_dialog->with,
367 self,
368 session->request_id++,
369 session->focus_dialog->with,
370 who);
371 g_free(self);
373 send_sip_request(sip->gc,
374 "INFO",
375 session->focus_dialog->with,
376 session->focus_dialog->with,
377 hdr,
378 body,
379 session->focus_dialog,
380 NULL);
381 g_free(body);
382 g_free(hdr);
385 /** Modify Conference Lock */
386 void
387 sipe_conf_modify_conference_lock(struct sipe_account_data *sip,
388 struct sip_session *session,
389 const gboolean locked)
391 gchar *hdr;
392 gchar *body;
393 gchar *self;
395 if (!session->focus_dialog || !session->focus_dialog->is_established) {
396 purple_debug_info("sipe", "sipe_conf_modify_conference_lock: no dialog with focus, exiting.\n");
397 return;
400 hdr = g_strdup(
401 "Content-Type: application/cccp+xml\r\n");
403 /* @TODO put request_id to queue to further compare with incoming one */
404 self = sip_uri_self(sip);
405 body = g_strdup_printf(
406 SIPE_SEND_CONF_MODIFY_CONF_LOCK,
407 session->focus_dialog->with,
408 self,
409 session->request_id++,
410 session->focus_dialog->with,
411 locked ? "true" : "false");
412 g_free(self);
414 send_sip_request(sip->gc,
415 "INFO",
416 session->focus_dialog->with,
417 session->focus_dialog->with,
418 hdr,
419 body,
420 session->focus_dialog,
421 NULL);
422 g_free(body);
423 g_free(hdr);
426 /** Modify Delete User */
427 void
428 sipe_conf_delete_user(struct sipe_account_data *sip,
429 struct sip_session *session,
430 const gchar* who)
432 gchar *hdr;
433 gchar *body;
434 gchar *self;
436 if (!session->focus_dialog || !session->focus_dialog->is_established) {
437 purple_debug_info("sipe", "sipe_conf_delete_user: no dialog with focus, exiting.\n");
438 return;
441 hdr = g_strdup(
442 "Content-Type: application/cccp+xml\r\n");
444 /* @TODO put request_id to queue to further compare with incoming one */
445 self = sip_uri_self(sip);
446 body = g_strdup_printf(
447 SIPE_SEND_CONF_DELETE_USER,
448 session->focus_dialog->with,
449 self,
450 session->request_id++,
451 session->focus_dialog->with,
452 who);
453 g_free(self);
455 send_sip_request(sip->gc,
456 "INFO",
457 session->focus_dialog->with,
458 session->focus_dialog->with,
459 hdr,
460 body,
461 session->focus_dialog,
462 NULL);
463 g_free(body);
464 g_free(hdr);
467 /** Invite counterparty to join conference callback */
468 static gboolean
469 process_invite_conf_response(struct sipe_account_data *sip,
470 struct sipmsg *msg,
471 SIPE_UNUSED_PARAMETER struct transaction *trans)
473 struct sip_dialog *dialog = g_new0(struct sip_dialog, 1);
475 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
476 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
477 dialog->with = parse_from(sipmsg_find_header(msg, "To"));
478 sipe_dialog_parse(dialog, msg, TRUE);
480 if (msg->response >= 200) {
481 /* send ACK to counterparty */
482 dialog->cseq--;
483 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
484 dialog->outgoing_invite = NULL;
485 dialog->is_established = TRUE;
488 if (msg->response >= 400) {
489 purple_debug_info("sipe", "process_invite_conf_response: INVITE response is not 200. Failed to invite %s.\n", dialog->with);
490 /* @TODO notify user of failure to invite counterparty */
491 sipe_dialog_free(dialog);
492 return FALSE;
495 if (msg->response >= 200) {
496 struct sip_session *session = sipe_session_find_im(sip, dialog->with);
497 struct sip_dialog *im_dialog = sipe_dialog_find(session, dialog->with);
499 /* close IM session to counterparty */
500 if (im_dialog) {
501 send_sip_request(sip->gc, "BYE", im_dialog->with, im_dialog->with, NULL, NULL, im_dialog, NULL);
502 sipe_dialog_remove(session, dialog->with);
506 sipe_dialog_free(dialog);
507 return TRUE;
511 * Invites counterparty to join conference.
513 void
514 sipe_invite_conf(struct sipe_account_data *sip,
515 struct sip_session *session,
516 const gchar* who)
518 gchar *hdr;
519 gchar *contact;
520 gchar *body;
521 struct sip_dialog *dialog = NULL;
523 /* It will be short lived special dialog.
524 * Will not be stored in session.
526 dialog = g_new0(struct sip_dialog, 1);
527 dialog->callid = gencallid();
528 dialog->with = g_strdup(who);
529 dialog->ourtag = gentag();
531 contact = get_contact(sip);
532 hdr = g_strdup_printf(
533 "Supported: ms-sender\r\n"
534 "Contact: %s\r\n"
535 "Content-Type: application/ms-conf-invite+xml\r\n",
536 contact);
537 g_free(contact);
539 body = g_strdup_printf(
540 SIPE_SEND_CONF_INVITE,
541 session->focus_uri,
542 session->subject ? session->subject : ""
545 send_sip_request( sip->gc,
546 "INVITE",
547 dialog->with,
548 dialog->with,
549 hdr,
550 body,
551 dialog,
552 process_invite_conf_response);
554 sipe_dialog_free(dialog);
555 g_free(body);
556 g_free(hdr);
559 /** Create conference callback */
560 static gboolean
561 process_conf_add_response(struct sipe_account_data *sip,
562 struct sipmsg *msg,
563 struct transaction *trans)
565 if (msg->response >= 400) {
566 purple_debug_info("sipe", "process_conf_add_response: SERVICE response is not 200. Failed to create conference.\n");
567 /* @TODO notify user of failure to create conference */
568 return FALSE;
570 if (msg->response == 200) {
571 xmlnode *xn_response = xmlnode_from_str(msg->body, msg->bodylen);
572 if (sipe_strequal("success", xmlnode_get_attrib(xn_response, "code")))
574 gchar *who = trans->payload->data;
575 struct sip_session *session;
576 xmlnode *xn_conference_info = xmlnode_get_descendant(xn_response, "addConference", "conference-info", NULL);
578 session = sipe_session_add_chat(sip);
579 session->is_multiparty = FALSE;
580 session->focus_uri = g_strdup(xmlnode_get_attrib(xn_conference_info, "entity"));
581 purple_debug_info("sipe", "process_conf_add_response: session->focus_uri=%s\n",
582 session->focus_uri ? session->focus_uri : "");
584 session->pending_invite_queue = slist_insert_unique_sorted(
585 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
587 /* add self to conf */
588 sipe_invite_conf_focus(sip, session);
590 xmlnode_free(xn_response);
593 return TRUE;
597 * Creates conference.
599 void
600 sipe_conf_add(struct sipe_account_data *sip,
601 const gchar* who)
603 gchar *hdr;
604 gchar *conference_id;
605 gchar *contact;
606 gchar *body;
607 gchar *self;
608 struct transaction *trans;
609 struct sip_dialog *dialog = NULL;
610 time_t expiry = time(NULL) + 7*60*60; /* 7 hours */
611 char *expiry_time;
612 struct transaction_payload *payload;
614 contact = get_contact(sip);
615 hdr = g_strdup_printf(
616 "Supported: ms-sender\r\n"
617 "Contact: %s\r\n"
618 "Content-Type: application/cccp+xml\r\n",
619 contact);
620 g_free(contact);
622 expiry_time = sipe_utils_time_to_str(expiry);
623 self = sip_uri_self(sip);
624 conference_id = genconfid();
625 body = g_strdup_printf(
626 SIPE_SEND_CONF_ADD,
627 sip->focus_factory_uri,
628 self,
629 rand(),
630 conference_id,
631 expiry_time);
632 g_free(self);
633 g_free(conference_id);
634 g_free(expiry_time);
636 trans = send_sip_request( sip->gc,
637 "SERVICE",
638 sip->focus_factory_uri,
639 sip->focus_factory_uri,
640 hdr,
641 body,
642 NULL,
643 process_conf_add_response);
645 payload = g_new0(struct transaction_payload, 1);
646 payload->destroy = g_free;
647 payload->data = g_strdup(who);
648 trans->payload = payload;
650 sipe_dialog_free(dialog);
651 g_free(body);
652 g_free(hdr);
655 void
656 process_incoming_invite_conf(struct sipe_account_data *sip,
657 struct sipmsg *msg)
659 struct sip_session *session = NULL;
660 struct sip_dialog *dialog = NULL;
661 xmlnode *xn_conferencing = xmlnode_from_str(msg->body, msg->bodylen);
662 xmlnode *xn_focus_uri = xmlnode_get_child(xn_conferencing, "focus-uri");
663 char *focus_uri = xmlnode_get_data(xn_focus_uri);
664 gchar *newTag = gentag();
665 const gchar *oldHeader = sipmsg_find_header(msg, "To");
666 gchar *newHeader;
668 xmlnode_free(xn_conferencing);
670 /* send OK */
671 purple_debug_info("sipe", "We have received invitation to Conference. Focus URI=%s\n", focus_uri);
673 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
674 sipmsg_remove_header_now(msg, "To");
675 sipmsg_add_header_now(msg, "To", newHeader);
676 g_free(newHeader);
678 /* temporary dialog with invitor */
679 /* take data before 'msg' will be modified by send_sip_response */
680 dialog = g_new0(struct sip_dialog, 1);
681 dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
682 dialog->cseq = parse_cseq(sipmsg_find_header(msg, "CSeq"));
683 dialog->with = parse_from(sipmsg_find_header(msg, "From"));
684 sipe_dialog_parse(dialog, msg, FALSE);
686 send_sip_response(sip->gc, msg, 200, "OK", NULL);
688 session = sipe_session_add_chat(sip);
689 session->focus_uri = focus_uri;
690 session->is_multiparty = FALSE;
692 /* send BYE to invitor */
693 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
694 sipe_dialog_free(dialog);
696 /* add self to conf */
697 sipe_invite_conf_focus(sip, session);
700 void
701 sipe_process_conference(struct sipe_account_data *sip,
702 struct sipmsg *msg)
704 xmlnode *xn_conference_info;
705 xmlnode *node;
706 xmlnode *xn_subject;
707 const gchar *focus_uri;
708 struct sip_session *session;
709 gboolean just_joined = FALSE;
711 if (msg->response != 0 && msg->response != 200) return;
713 if (msg->bodylen == 0 || msg->body == NULL || !sipe_strequal(sipmsg_find_header(msg, "Event"), "conference")) return;
715 xn_conference_info = xmlnode_from_str(msg->body, msg->bodylen);
716 if (!xn_conference_info) return;
718 focus_uri = xmlnode_get_attrib(xn_conference_info, "entity");
719 session = sipe_session_find_conference(sip, focus_uri);
721 if (!session) {
722 purple_debug_info("sipe", "sipe_process_conference: unable to find conf session with focus=%s\n", focus_uri);
723 return;
726 if (session->focus_uri && !session->conv) {
727 gchar *chat_title = sipe_chat_get_name(session->focus_uri);
728 gchar *self = sip_uri_self(sip);
729 /* can't be find by chat id as it won't survive acc reinstantation */
730 PurpleConversation *conv = NULL;
732 if (chat_title) {
733 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT,
734 chat_title,
735 sip->account);
737 /* to be able to rejoin existing chat/window */
738 if (conv && !purple_conv_chat_has_left(PURPLE_CONV_CHAT(conv))) {
739 PURPLE_CONV_CHAT(conv)->left = TRUE;
741 /* create prpl chat */
742 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_title);
743 session->chat_title = chat_title;
744 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
745 just_joined = TRUE;
746 /* @TODO ask for full state (re-subscribe) if it was a partial one -
747 * this is to obtain full list of conference participants.
749 g_free(self);
752 /* subject */
753 if ((xn_subject = xmlnode_get_descendant(xn_conference_info, "conference-description", "subject", NULL))) {
754 g_free(session->subject);
755 session->subject = xmlnode_get_data(xn_subject);
756 purple_conv_chat_set_topic(PURPLE_CONV_CHAT(session->conv), NULL, session->subject);
757 purple_debug_info("sipe", "sipe_process_conference: subject=%s\n", session->subject ? session->subject : "");
760 /* IM MCU URI */
761 if (!session->im_mcu_uri) {
762 for (node = xmlnode_get_descendant(xn_conference_info, "conference-description", "conf-uris", "entry", NULL);
763 node;
764 node = xmlnode_get_next_twin(node))
766 gchar *purpose = xmlnode_get_data(xmlnode_get_child(node, "purpose"));
768 if (sipe_strequal("chat", purpose)) {
769 g_free(purpose);
770 session->im_mcu_uri = xmlnode_get_data(xmlnode_get_child(node, "uri"));
771 purple_debug_info("sipe", "sipe_process_conference: im_mcu_uri=%s\n", session->im_mcu_uri);
772 break;
774 g_free(purpose);
778 /* users */
779 for (node = xmlnode_get_descendant(xn_conference_info, "users", "user", NULL); node; node = xmlnode_get_next_twin(node)) {
780 xmlnode *endpoint = NULL;
781 const gchar *user_uri = xmlnode_get_attrib(node, "entity");
782 const gchar *state = xmlnode_get_attrib(node, "state");
783 gchar *role = xmlnode_get_data(xmlnode_get_descendant(node, "roles", "entry", NULL));
784 PurpleConvChatBuddyFlags flags = PURPLE_CBFLAGS_NONE;
785 PurpleConvChat *chat = PURPLE_CONV_CHAT(session->conv);
786 gboolean is_in_im_mcu = FALSE;
787 gchar *self = sip_uri_self(sip);
789 if (sipe_strequal(role, "presenter")) {
790 flags |= PURPLE_CBFLAGS_OP;
793 if (sipe_strequal("deleted", state)) {
794 if (purple_conv_chat_find_user(chat, user_uri)) {
795 purple_conv_chat_remove_user(chat, user_uri, NULL /* reason */);
797 } else {
798 /* endpoints */
799 for (endpoint = xmlnode_get_child(node, "endpoint"); endpoint; endpoint = xmlnode_get_next_twin(endpoint)) {
800 if (sipe_strequal("chat", xmlnode_get_attrib(endpoint, "session-type"))) {
801 gchar *status = xmlnode_get_data(xmlnode_get_child(endpoint, "status"));
802 if (sipe_strequal("connected", status)) {
803 is_in_im_mcu = TRUE;
804 if (!purple_conv_chat_find_user(chat, user_uri)) {
805 purple_conv_chat_add_user(chat, user_uri, NULL, flags,
806 !just_joined && g_strcasecmp(user_uri, self));
807 } else {
808 purple_conv_chat_user_set_flags(chat, user_uri, flags);
811 g_free(status);
812 break;
815 if (!is_in_im_mcu) {
816 if (purple_conv_chat_find_user(chat, user_uri)) {
817 purple_conv_chat_remove_user(chat, user_uri, NULL /* reason */);
821 g_free(role);
822 g_free(self);
825 /* entity-view, locked */
826 for (node = xmlnode_get_descendant(xn_conference_info, "conference-view", "entity-view", NULL);
827 node;
828 node = xmlnode_get_next_twin(node)) {
830 xmlnode *xn_type = xmlnode_get_descendant(node, "entity-state", "media", "entry", "type", NULL);
831 gchar *tmp = NULL;
832 if (xn_type && sipe_strequal("chat", (tmp = xmlnode_get_data(xn_type)))) {
833 xmlnode *xn_locked = xmlnode_get_descendant(node, "entity-state", "locked", NULL);
834 if (xn_locked) {
835 gchar *locked = xmlnode_get_data(xn_locked);
836 gboolean prev_locked = session->locked;
837 session->locked = sipe_strequal(locked, "true");
838 if (prev_locked && !session->locked) {
839 sipe_present_info(sip, session,
840 _("This conference is no longer locked. Additional participants can now join."));
842 if (!prev_locked && session->locked) {
843 sipe_present_info(sip, session,
844 _("This conference is locked. Nobody else can join the conference while it is locked."));
847 purple_debug_info("sipe", "sipe_process_conference: session->locked=%s\n",
848 session->locked ? "TRUE" : "FALSE");
849 g_free(locked);
852 g_free(tmp);
854 xmlnode_free(xn_conference_info);
856 if (session->im_mcu_uri) {
857 struct sip_dialog *dialog = sipe_dialog_find(session, session->im_mcu_uri);
858 if (!dialog) {
859 dialog = sipe_dialog_add(session);
861 dialog->callid = g_strdup(session->callid);
862 dialog->with = g_strdup(session->im_mcu_uri);
864 /* send INVITE to IM MCU */
865 sipe_invite(sip, session, dialog->with, NULL, NULL, NULL, FALSE);
869 sipe_process_pending_invite_queue(sip, session);
872 void
873 sipe_conf_immcu_closed(struct sipe_account_data *sip,
874 struct sip_session *session)
876 sipe_present_info(sip, session,
877 _("You have been disconnected from this conference."));
878 purple_conv_chat_clear_users(PURPLE_CONV_CHAT(session->conv));
881 void
882 conf_session_close(struct sipe_account_data *sip,
883 struct sip_session *session)
885 if (session) {
886 /* unsubscribe from focus */
887 sipe_subscribe_conference(sip, session, 0);
889 if (session->focus_dialog) {
890 /* send BYE to focus */
891 send_sip_request(sip->gc,
892 "BYE",
893 session->focus_dialog->with,
894 session->focus_dialog->with,
895 NULL,
896 NULL,
897 session->focus_dialog,
898 NULL);
903 void
904 sipe_process_imdn(struct sipe_account_data *sip,
905 struct sipmsg *msg)
907 gchar *with = parse_from(sipmsg_find_header(msg, "From"));
908 const gchar *call_id = sipmsg_find_header(msg, "Call-ID");
909 static struct sip_session *session;
910 xmlnode *xn_imdn;
911 xmlnode *node;
912 gchar *message_id;
913 gchar *message;
915 session = sipe_session_find_chat_by_callid(sip, call_id);
916 if (!session) {
917 session = sipe_session_find_im(sip, with);
919 if (!session) {
920 purple_debug_info("sipe", "sipe_process_imdn: unable to find conf session with call_id=%s\n", call_id);
921 g_free(with);
922 return;
925 xn_imdn = xmlnode_from_str(msg->body, msg->bodylen);
926 message_id = xmlnode_get_data(xmlnode_get_child(xn_imdn, "message-id"));
928 message = g_hash_table_lookup(session->conf_unconfirmed_messages, message_id);
930 /* recipient */
931 for (node = xmlnode_get_child(xn_imdn, "recipient"); node; node = xmlnode_get_next_twin(node)) {
932 gchar *tmp = parse_from(xmlnode_get_attrib(node, "uri"));
933 gchar *uri = parse_from(tmp);
934 sipe_present_message_undelivered_err(sip, session, -1, -1, uri, message);
935 g_free(tmp);
936 g_free(uri);
939 xmlnode_free(xn_imdn);
941 g_hash_table_remove(session->conf_unconfirmed_messages, message_id);
942 purple_debug_info("sipe", "sipe_process_imdn: removed message %s from conf_unconfirmed_messages(count=%d)\n",
943 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
944 g_free(message_id);
945 g_free(with);
950 Local Variables:
951 mode: c
952 c-file-style: "bsd"
953 indent-tabs-mode: t
954 tab-width: 8
955 End: