conf: allow to reject incoming AV conference call
[siplcs.git] / src / core / sipe-domino.c
blob324222f95285e2ea9f318c7c11a555a2b42a68e8
1 /**
2 * @file sipe-domino.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 /**
26 For communication with Lotus Domino groupware server.
28 Server requirements: Domino 5.0.2 and above with Web Access.
30 1) Tries to read user's notes.ini for mail database name.
31 Windows registry keys for notes.ini location:
32 HKEY_CURRENT_USER\Software\Lotus\Notes\6.0\NotesIniPath
34 2) Authenticates to server (HTTPS POST, plaintext login/password over SSL)
35 https://[domino_server]/[databasename].nsf/?Login
36 Content-Type=application/x-www-form-urlencoded
37 Username=[email]&Password=[password] (params are url-encoded)
38 Saves auth cookie.
39 Set-Cookie=DomAuthSessId=17D0428F7B9D57D4D0B064AE42FD21F9; path=/
41 3) Queries Calendar data (HTTPS GET, result is XML)
42 https://[domino_server]/[databasename].nsf/[viewname]?ReadViewEntries
43 https://[domino_server]/[databasename].nsf/($Calendar)?ReadViewEntries&KeyType=time&StartKey=20090805T000000Z&UntilKey=20090806T000000Z&Count=-1&TZType=UTC
44 Uses auth cookie.
45 Cookie=DomAuthSessId=17D0428F7B9D57D4D0B064AE42FD21F9
47 It is able to retrieve our Calendar information (Meetings schedule,
48 subject and location) from Lotus Domino for subsequent publishing.
50 Ref. for more implementation details:
51 https://sourceforge.net/tracker/?func=detail&aid=2945346&group_id=194563&atid=949934
53 Similar functionality for iCalendar/CalDAV/Google would be great to implement too.
56 #include <string.h>
57 #include <ctype.h>
58 #include <stdio.h>
59 #include <errno.h>
60 #include <time.h>
62 #include <glib.h>
64 /* for registry read */
65 #ifdef _WIN32
66 #include "sipe-win32dep.h"
67 #endif
69 #include "http-conn.h"
70 #include "sipe-common.h"
71 #include "sipe-core.h"
72 #include "sipe-core-private.h"
73 #include "sipe.h"
74 #include "sipe-nls.h"
75 #include "sipe-backend.h"
76 #include "sipe-utils.h"
77 #include "sipe-cal.h"
78 #include "sipe-xml.h"
79 #include "sipe-domino.h"
82 /**
83 * POST request for Login to Domino server
84 * @param email (%s) Should be URL-encoded. Ex.: alice@cosmo.local
85 * @param password (%s) Should be URL-encoded.
87 #define SIPE_DOMINO_LOGIN_REQUEST \
88 "Username=%s&Password=%s"
90 /**
91 * GET request to Domino server
92 * to obtain our Calendar information.
93 * @param start_time (%s) Ex.: 20090805T000000Z
94 * @param end_time (%s) Ex.: 20090806T000000Z
96 #define SIPE_DOMINO_CALENDAR_REQUEST \
97 "/($Calendar)?ReadViewEntries&KeyType=time&StartKey=%s&UntilKey=%s&Count=-1&TZType=UTC"
100 <?xml version="1.0" encoding="UTF-8"?>
101 <viewentries timestamp="20100416T112140,02Z" toplevelentries="77" rangeentries="
102 1000">
103 <viewentry position="77" unid="C3A77CC76EAA7D08802576FD0043D7D0" noteid="27B42" siblings="77">
104 <entrydata columnnumber="0" name="$134">
105 <datetime>20100423T103000,00Z</datetime>
106 </entrydata>
107 <entrydata columnnumber="1" name="$149">
108 <number>158</number>
109 </entrydata>
110 <entrydata columnnumber="2" name="$144">
111 <datetime>20100423T103000,00Z</datetime>
112 </entrydata>
113 <entrydata columnnumber="3" name="$145">
114 <text>-</text>
115 </entrydata>
116 <entrydata columnnumber="4" name="$146">
117 <datetime>20100423T120000,00Z</datetime>
118 </entrydata>
119 <entrydata columnnumber="5" name="$147">
120 <textlist>
121 <text>G. S. ..I. L. T. Hall</text>
122 <text>Location: Auditorium - W. House</text>
123 <text>Chair: S. S.</text>
124 </textlist>
125 </entrydata>
126 </viewentry>
127 <viewentry .........
128 </viewentries>
131 #define VIEWENTITY_START0_TIME "$134"
132 #define VIEWENTITY_START_TIME "$144"
133 #define VIEWENTITY_END_TIME "$146"
134 #define VIEWENTITY_TEXT_LIST "$147"
137 static int
138 sipe_domino_get_slot_no(time_t fb_start, time_t in)
140 return (in - fb_start) / SIPE_FREE_BUSY_GRANULARITY_SEC;
143 static char *
144 sipe_domino_get_free_busy(time_t fb_start,
145 GSList *cal_events)
147 GSList *entry = cal_events;
148 char *res;
150 if (!cal_events) return NULL;
152 res = g_strnfill(SIPE_FREE_BUSY_PERIOD_SEC / SIPE_FREE_BUSY_GRANULARITY_SEC,
153 SIPE_CAL_FREE + '0');
155 while (entry) {
156 struct sipe_cal_event *cal_event = entry->data;
157 int start = sipe_domino_get_slot_no(fb_start, cal_event->start_time);
158 int end = sipe_domino_get_slot_no(fb_start, (cal_event->end_time - 1));
159 int i;
161 for (i = start; i <= end; i++) {
162 res[i] = SIPE_CAL_BUSY + '0';
164 entry = entry->next;
166 SIPE_DEBUG_INFO("sipe_domino_get_free_busy: res=\n%s", res);
167 return res;
170 static void
171 sipe_domino_process_calendar_response(int return_code,
172 const char *body,
173 const char *content_type,
174 HttpConn *conn,
175 void *data)
177 struct sipe_calendar *cal = data;
179 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_process_calendar_response: cb started.");
181 http_conn_set_close(conn);
182 cal->http_conn = NULL;
184 if (content_type && !g_str_has_prefix(content_type, "text/xml")) {
185 cal->is_domino_disabled = TRUE;
186 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_process_calendar_response: not XML, disabling.");
187 return;
190 if (return_code == 200 && body) {
191 struct sipe_core_private *sipe_private = cal->sipe_private;
192 const sipe_xml *node, *node2, *node3;
193 sipe_xml *xml;
195 SIPE_DEBUG_INFO("sipe_domino_process_calendar_response: SUCCESS, ret=%d", return_code);
196 xml = sipe_xml_parse(body, strlen(body));
198 sipe_cal_events_free(cal->cal_events);
199 cal->cal_events = NULL;
200 /* viewentry */
201 for (node = sipe_xml_child(xml, "viewentry");
202 node;
203 node = sipe_xml_twin(node))
205 struct sipe_cal_event *cal_event = g_new0(struct sipe_cal_event, 1);
206 cal->cal_events = g_slist_append(cal->cal_events, cal_event);
207 cal_event->cal_status = SIPE_CAL_BUSY;
208 cal_event->is_meeting = TRUE;
210 /* SIPE_DEBUG_INFO("viewentry unid=%s", sipe_xml_attribute(node, "unid")); */
212 /* entrydata */
213 for (node2 = sipe_xml_child(node, "entrydata");
214 node2;
215 node2 = sipe_xml_twin(node2))
217 const char *name = sipe_xml_attribute(node2, "name");
219 SIPE_DEBUG_INFO("\tentrydata name=%s", name);
221 if (sipe_strequal(name, VIEWENTITY_START0_TIME) ||
222 sipe_strequal(name, VIEWENTITY_START_TIME) ||
223 sipe_strequal(name, VIEWENTITY_END_TIME))
225 char *tmp = sipe_xml_data(sipe_xml_child(node2, "datetime"));
226 time_t time_val = sipe_utils_str_to_time(tmp);
228 if (sipe_strequal(name, VIEWENTITY_START_TIME)) {
229 cal_event->start_time = time_val;
230 } else if (sipe_strequal(name, VIEWENTITY_END_TIME)) {
231 cal_event->end_time = time_val;
234 SIPE_DEBUG_INFO("\t\tdatetime=%s", asctime(gmtime(&time_val)));
235 g_free(tmp);
236 } else if (sipe_strequal(name, VIEWENTITY_TEXT_LIST)) {
237 int i = 0;
239 /* test */
240 for (node3 = sipe_xml_child(node2, "textlist/text");
241 node3;
242 node3 = sipe_xml_twin(node3))
244 char *tmp = sipe_xml_data(node3);
246 if (!tmp) continue;
248 SIPE_DEBUG_INFO("\t\ttext=%s", tmp);
249 if (i == 0) {
250 cal_event->subject = g_strdup(tmp);
251 SIPE_DEBUG_INFO("\t\t*Subj.=%s", tmp);
252 } else {
253 /* plain English, don't localize! */
254 if (!g_ascii_strncasecmp(tmp, "Location:", 9)) {
255 if (strlen(tmp) > 9) {
256 cal_event->location = g_strdup(g_strstrip(tmp+9));
257 SIPE_DEBUG_INFO("\t\t*Loc.=%s", cal_event->location);
259 /* Translators: (!) should be as in localized Lotus Notes to be able to extract meeting location */
260 } else if (g_str_has_prefix(tmp, _("Location:"))) {
261 guint len = strlen(_("Location:"));
262 if (strlen(tmp) > len) {
263 cal_event->location = g_strdup(g_strstrip(tmp+len));
264 SIPE_DEBUG_INFO("\t\t*Loc.=%s", cal_event->location);
268 i++;
269 g_free(tmp);
274 sipe_xml_free(xml);
276 /* creates FreeBusy from cal->cal_events */
277 g_free(cal->free_busy);
278 cal->free_busy = sipe_domino_get_free_busy(cal->fb_start, cal->cal_events);
280 /* update SIP server */
281 cal->is_updated = TRUE;
282 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
283 /* sipe.h */
284 publish_calendar_status_self(sipe_private,
285 NULL);
286 } else {
287 /* sipe.h */
288 send_presence_soap(sipe_private, TRUE);
291 } else if (return_code < 0) {
292 SIPE_DEBUG_INFO("sipe_domino_process_calendar_response: rather FAILURE, ret=%d", return_code);
295 if (cal->http_session) {
296 http_conn_session_free(cal->http_session);
297 cal->http_session = NULL;
301 /* Domino doesn't like '-' and ':' in ISO timestamps */
302 static gchar *
303 sipe_domino_time_to_str(time_t timestamp)
305 char *res, *tmp;
307 res = sipe_utils_time_to_str(timestamp);
308 res = sipe_utils_str_replace((tmp = res), "-", "");
309 g_free(tmp);
310 res = sipe_utils_str_replace((tmp = res), ":", "");
311 g_free(tmp);
313 return res;
316 static void
317 sipe_domino_do_calendar_request(struct sipe_calendar *cal)
319 if (cal->domino_url) {
320 char *url_req;
321 char *url;
322 time_t end;
323 time_t now = time(NULL);
324 char *start_str;
325 char *end_str;
326 struct tm *now_tm;
328 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_do_calendar_request: going Calendar req.");
330 now_tm = gmtime(&now);
331 /* start -1 day, 00:00:00 */
332 now_tm->tm_sec = 0;
333 now_tm->tm_min = 0;
334 now_tm->tm_hour = 0;
335 cal->fb_start = sipe_mktime_tz(now_tm, "UTC");
336 cal->fb_start -= 24*60*60;
337 /* end = start + 4 days - 1 sec */
338 end = cal->fb_start + SIPE_FREE_BUSY_PERIOD_SEC - 1;
340 start_str = sipe_domino_time_to_str(cal->fb_start);
341 end_str = sipe_domino_time_to_str(end);
343 url_req = g_strdup_printf(SIPE_DOMINO_CALENDAR_REQUEST, start_str, end_str);
344 g_free(start_str);
345 g_free(end_str);
347 url = g_strconcat(cal->domino_url, url_req, NULL);
348 g_free(url_req);
349 if (!cal->http_conn || http_conn_is_closed(cal->http_conn)) {
350 cal->http_conn = http_conn_create(
351 (struct sipe_core_public *) cal->sipe_private,
352 cal->http_session,
353 HTTP_CONN_GET,
354 HTTP_CONN_SSL,
355 HTTP_CONN_NO_REDIRECT,
356 url,
357 NULL, /* body */
358 NULL, /* content-type */
359 cal->auth,
360 sipe_domino_process_calendar_response,
361 cal);
362 } else {
363 http_conn_send(cal->http_conn,
364 HTTP_CONN_GET,
365 url,
366 NULL, /* body */
367 NULL, /* content-type */
368 sipe_domino_process_calendar_response,
369 cal);
371 g_free(url);
375 static void
376 sipe_domino_process_login_response(int return_code,
377 /* temporary? */
378 SIPE_UNUSED_PARAMETER const char *body,
379 SIPE_UNUSED_PARAMETER const char *content_type,
380 HttpConn *conn,
381 void *data)
383 struct sipe_calendar *cal = data;
385 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_process_login_response: cb started.");
387 if (return_code >= 200 && return_code < 400) {
388 SIPE_DEBUG_INFO("sipe_domino_process_login_response: rather SUCCESS, ret=%d", return_code);
390 /* next query */
391 sipe_domino_do_calendar_request(cal);
393 } else if (return_code < 0 || return_code >= 400) {
394 SIPE_DEBUG_INFO("sipe_domino_process_login_response: rather FAILURE, ret=%d", return_code);
396 /* stop here */
397 /* cal->is_domino_disabled = TRUE; */
399 http_conn_set_close(conn);
400 cal->http_conn = NULL;
404 static gchar *sipe_domino_uri_escape(const gchar *string)
406 gchar *escaped;
408 if (!string) return(NULL);
409 if (!g_utf8_validate(string, -1, NULL)) return(NULL);
411 #if GLIB_CHECK_VERSION(2,16,0)
412 escaped = g_uri_escape_string(string, NULL, FALSE);
413 #else
414 /* loosely based on libpurple/util.c:purple_url_encode() */
416 GString *buf = g_string_new(NULL);
418 while (*string) {
419 gunichar c = g_utf8_get_char(string);
421 /* If the character is an ASCII character and is alphanumeric
422 * no need to escape */
423 if (c < 128 &&
424 (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~')) {
425 g_string_append_c(buf, c);
426 } else {
427 gchar *p, utf_char[6];
428 guint bytes = g_unichar_to_utf8(c, utf_char);
430 p = utf_char;
431 while (bytes-- > 0) {
432 g_string_append_printf(buf,
433 "%%%02X",
434 *p++ & 0xff);
438 string = g_utf8_next_char(string);
441 escaped = g_string_free(buf, FALSE);
443 #endif
445 return(escaped);
448 static void
449 sipe_domino_do_login_request(struct sipe_calendar *cal)
451 if (cal->domino_url) {
452 char *body;
453 const char *content_type = "application/x-www-form-urlencoded";
454 char *login_url = g_strconcat(cal->domino_url, "/?Login", NULL);
455 char *user;
456 char *password;
458 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_do_login_request: going Login req.");
460 if (!cal->auth) return;
462 /* @TODO replace purple_url_encode() with non-purple equiv. */
463 user = sipe_domino_uri_escape(cal->email);
464 password = sipe_domino_uri_escape(cal->auth->password);
466 body = g_strdup_printf(SIPE_DOMINO_LOGIN_REQUEST, user, password);
467 g_free(user);
468 g_free(password);
470 cal->http_conn = http_conn_create((struct sipe_core_public *) cal->sipe_private,
471 cal->http_session,
472 HTTP_CONN_POST,
473 HTTP_CONN_SSL,
474 HTTP_CONN_NO_REDIRECT,
475 login_url,
476 body,
477 content_type,
478 cal->auth,
479 sipe_domino_process_login_response,
480 cal);
481 g_free(login_url);
482 g_free(body);
486 /* in notes.ni
487 MailFile=mail5\mhe111bm.nsf
488 MailServer=CN=MSGM2222/OU=srv/O=xxcom
490 Output values should be freed if requested.
492 static void
493 sipe_domino_read_notes_ini(const char *filename_with_path, char **mail_server, char **mail_file)
495 char rbuf[256];
496 FILE *fp = fopen(filename_with_path, "r+");
498 if (fp) {
499 while (fgets(rbuf, sizeof (rbuf), fp)) {
500 char *prop = "MailFile=";
501 guint prop_len = strlen(prop);
503 /* SIPE_DEBUG_INFO("\t%s (%"G_GSIZE_FORMAT")", rbuf, strlen(rbuf)); */
504 if (mail_file && !g_ascii_strncasecmp(rbuf, prop, prop_len) && (strlen(rbuf) > prop_len)) {
505 *mail_file = g_strdup(g_strstrip((rbuf+prop_len)));
508 prop = "MailServer=";
509 prop_len = strlen(prop);
511 if (mail_server && !g_ascii_strncasecmp(rbuf, prop, prop_len) && (strlen(rbuf) > prop_len)) {
512 *mail_server = g_strdup(g_strstrip((rbuf+prop_len)));
515 fclose(fp);
516 } else {
517 SIPE_DEBUG_ERROR("sipe_domino_read_notes_ini(): could not open `%s': %s", filename_with_path, g_strerror (errno));
522 @param protocol Ex.: https
523 @param mail_server Ex.: CN=MSGM2222/OU=srv/O=xxcom
524 @param mail_file Ex.: mail5\mhe111bm.nsf
526 @return Ex.: https://msgm2222/mail5/mhe111bm.nsf
528 static char *
529 sipe_domino_compose_url(const char *protocol, const char *mail_server, const char *mail_file)
531 const char *ptr;
532 char *tmp, *tmp2, *tmp3;
534 g_return_val_if_fail(protocol, NULL);
535 g_return_val_if_fail(mail_server, NULL);
536 g_return_val_if_fail(mail_file, NULL);
538 /* mail_server: exptacting just common name */
539 if ((ptr = strstr(mail_server, "/"))) {
540 tmp = g_strndup(mail_server, (ptr-mail_server));
541 } else {
542 tmp = g_strdup(mail_server);
544 if ((!g_ascii_strncasecmp(tmp, "CN=", 3))) {
545 tmp2 = g_strdup(tmp+3);
546 } else {
547 tmp2 = g_strdup(tmp);
549 g_free(tmp);
550 tmp = g_ascii_strdown(tmp2, -1);
551 g_free(tmp2);
553 /* mail_file */
554 tmp3 = sipe_utils_str_replace(mail_file, "\\", "/");
556 tmp2 = g_strconcat(protocol, "://", tmp, "/", tmp3, NULL);
557 g_free(tmp);
558 g_free(tmp3);
560 return tmp2;
563 void
564 sipe_domino_update_calendar(struct sipe_core_private *sipe_private)
566 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
567 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: started.");
569 if (sip) {
570 sipe_cal_calendar_init(sipe_private, NULL);
572 /* check if URL is valid if provided */
573 if (sip->cal && !is_empty(sip->cal->domino_url)) {
574 char *tmp = g_ascii_strdown(sip->cal->domino_url, -1);
575 if (!g_str_has_suffix(tmp, ".nsf")) {
576 /* not valid Domino mail services URL */
577 sip->cal->is_domino_disabled = TRUE;
578 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: invalid Domino URI supplied, disabling.");
580 g_free(tmp);
583 /* Autodiscovery.
584 * Searches location of notes.ini in Registry, reads it, extracts mail server and mail file,
585 * composes HTTPS URL to Domino web, basing on that
587 if (sip->cal && is_empty(sip->cal->domino_url)) {
588 char *path = NULL;
589 #ifdef _WIN32
590 /* fine for Notes 8.5 too */
591 path = wpurple_read_reg_expand_string(HKEY_CURRENT_USER, "Software\\Lotus\\Notes\\8.0", "NotesIniPath");
592 if (is_empty(path)) {
593 g_free(path);
594 path = wpurple_read_reg_expand_string(HKEY_CURRENT_USER, "Software\\Lotus\\Notes\\7.0", "NotesIniPath");
595 if (is_empty(path)) {
596 g_free(path);
597 path = wpurple_read_reg_expand_string(HKEY_CURRENT_USER, "Software\\Lotus\\Notes\\6.0", "NotesIniPath");
598 if (is_empty(path)) {
599 g_free(path);
600 path = wpurple_read_reg_expand_string(HKEY_CURRENT_USER, "Software\\Lotus\\Notes\\5.0", "NotesIniPath");
604 SIPE_DEBUG_INFO("sipe_domino_update_calendar: notes.ini path:\n%s", path ? path : "");
605 #else
606 /* How to know location of notes.ini on *NIX ? */
607 #endif
609 /* get server url */
610 if (path) {
611 char *mail_server = NULL;
612 char *mail_file = NULL;
614 sipe_domino_read_notes_ini(path, &mail_server, &mail_file);
615 g_free(path);
616 SIPE_DEBUG_INFO("sipe_domino_update_calendar: mail_server=%s", mail_server ? mail_server : "");
617 SIPE_DEBUG_INFO("sipe_domino_update_calendar: mail_file=%s", mail_file ? mail_file : "");
619 g_free(sip->cal->domino_url);
620 sip->cal->domino_url = sipe_domino_compose_url("https", mail_server, mail_file);
621 g_free(mail_server);
622 g_free(mail_file);
623 SIPE_DEBUG_INFO("sipe_domino_update_calendar: sip->cal->domino_url=%s", sip->cal->domino_url ? sip->cal->domino_url : "");
624 } else {
625 /* No domino_url, no path discovered, disabling */
626 sip->cal->is_domino_disabled = TRUE;
627 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: Domino URI hasn't been discovered, neither provided, disabling.");
631 if (sip->cal) {
633 /* create session */
634 if (sip->cal->http_session) {
635 http_conn_session_free(sip->cal->http_session);
637 sip->cal->http_session = http_conn_session_create();
639 if (sip->cal->is_domino_disabled) {
640 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: disabled, exiting.");
641 return;
644 sipe_domino_do_login_request(sip->cal);
648 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: finished.");
652 Local Variables:
653 mode: c
654 c-file-style: "bsd"
655 indent-tabs-mode: t
656 tab-width: 8
657 End: