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
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)
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
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.
64 /* for registry read */
66 #include "sipe-win32dep.h"
69 #include "http-conn.h"
70 #include "sipe-common.h"
71 #include "sipe-core.h"
72 #include "sipe-core-private.h"
75 #include "sipe-backend.h"
76 #include "sipe-utils.h"
79 #include "sipe-domino.h"
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"
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="
103 <viewentry position="77" unid="C3A77CC76EAA7D08802576FD0043D7D0" noteid="27B42" siblings="77">
104 <entrydata columnnumber="0" name="$134">
105 <datetime>20100423T103000,00Z</datetime>
107 <entrydata columnnumber="1" name="$149">
110 <entrydata columnnumber="2" name="$144">
111 <datetime>20100423T103000,00Z</datetime>
113 <entrydata columnnumber="3" name="$145">
116 <entrydata columnnumber="4" name="$146">
117 <datetime>20100423T120000,00Z</datetime>
119 <entrydata columnnumber="5" name="$147">
121 <text>G. S. ..I. L. T. Hall</text>
122 <text>Location: Auditorium - W. House</text>
123 <text>Chair: S. S.</text>
131 #define VIEWENTITY_START0_TIME "$134"
132 #define VIEWENTITY_START_TIME "$144"
133 #define VIEWENTITY_END_TIME "$146"
134 #define VIEWENTITY_TEXT_LIST "$147"
138 sipe_domino_get_slot_no(time_t fb_start
, time_t in
)
140 return (in
- fb_start
) / SIPE_FREE_BUSY_GRANULARITY_SEC
;
144 sipe_domino_get_free_busy(time_t fb_start
,
147 GSList
*entry
= cal_events
;
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');
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));
161 for (i
= start
; i
<= end
; i
++) {
162 res
[i
] = SIPE_CAL_BUSY
+ '0';
166 SIPE_DEBUG_INFO("sipe_domino_get_free_busy: res=\n%s", res
);
171 sipe_domino_process_calendar_response(int return_code
,
173 const char *content_type
,
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.");
190 if (return_code
== 200 && body
) {
191 struct sipe_core_private
*sipe_private
= cal
->sipe_private
;
192 const sipe_xml
*node
, *node2
, *node3
;
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
;
201 for (node
= sipe_xml_child(xml
, "viewentry");
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")); */
213 for (node2
= sipe_xml_child(node
, "entrydata");
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
)));
236 } else if (sipe_strequal(name
, VIEWENTITY_TEXT_LIST
)) {
240 for (node3
= sipe_xml_child(node2
, "textlist/text");
242 node3
= sipe_xml_twin(node3
))
244 char *tmp
= sipe_xml_data(node3
);
248 SIPE_DEBUG_INFO("\t\ttext=%s", tmp
);
250 cal_event
->subject
= g_strdup(tmp
);
251 SIPE_DEBUG_INFO("\t\t*Subj.=%s", tmp
);
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
);
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
)) {
284 publish_calendar_status_self(sipe_private
,
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 */
303 sipe_domino_time_to_str(time_t timestamp
)
307 res
= sipe_utils_time_to_str(timestamp
);
308 res
= sipe_utils_str_replace((tmp
= res
), "-", "");
310 res
= sipe_utils_str_replace((tmp
= res
), ":", "");
317 sipe_domino_do_calendar_request(struct sipe_calendar
*cal
)
319 if (cal
->domino_url
) {
323 time_t now
= time(NULL
);
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 */
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
);
347 url
= g_strconcat(cal
->domino_url
, url_req
, NULL
);
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
,
355 HTTP_CONN_NO_REDIRECT
,
358 NULL
, /* content-type */
360 sipe_domino_process_calendar_response
,
363 http_conn_send(cal
->http_conn
,
367 NULL
, /* content-type */
368 sipe_domino_process_calendar_response
,
376 sipe_domino_process_login_response(int return_code
,
378 SIPE_UNUSED_PARAMETER
const char *body
,
379 SIPE_UNUSED_PARAMETER
const char *content_type
,
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
);
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
);
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
)
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
);
414 /* loosely based on libpurple/util.c:purple_url_encode() */
416 GString
*buf
= g_string_new(NULL
);
419 gunichar c
= g_utf8_get_char(string
);
421 /* If the character is an ASCII character and is alphanumeric
422 * no need to escape */
424 (isalnum(c
) || c
== '-' || c
== '.' || c
== '_' || c
== '~')) {
425 g_string_append_c(buf
, c
);
427 gchar
*p
, utf_char
[6];
428 guint bytes
= g_unichar_to_utf8(c
, utf_char
);
431 while (bytes
-- > 0) {
432 g_string_append_printf(buf
,
438 string
= g_utf8_next_char(string
);
441 escaped
= g_string_free(buf
, FALSE
);
449 sipe_domino_do_login_request(struct sipe_calendar
*cal
)
451 if (cal
->domino_url
) {
453 const char *content_type
= "application/x-www-form-urlencoded";
454 char *login_url
= g_strconcat(cal
->domino_url
, "/?Login", NULL
);
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
);
470 cal
->http_conn
= http_conn_create((struct sipe_core_public
*) cal
->sipe_private
,
474 HTTP_CONN_NO_REDIRECT
,
479 sipe_domino_process_login_response
,
487 MailFile=mail5\mhe111bm.nsf
488 MailServer=CN=MSGM2222/OU=srv/O=xxcom
490 Output values should be freed if requested.
493 sipe_domino_read_notes_ini(const char *filename_with_path
, char **mail_server
, char **mail_file
)
496 FILE *fp
= fopen(filename_with_path
, "r+");
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
)));
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
529 sipe_domino_compose_url(const char *protocol
, const char *mail_server
, const char *mail_file
)
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
));
542 tmp
= g_strdup(mail_server
);
544 if ((!g_ascii_strncasecmp(tmp
, "CN=", 3))) {
545 tmp2
= g_strdup(tmp
+3);
547 tmp2
= g_strdup(tmp
);
550 tmp
= g_ascii_strdown(tmp2
, -1);
554 tmp3
= sipe_utils_str_replace(mail_file
, "\\", "/");
556 tmp2
= g_strconcat(protocol
, "://", tmp
, "/", tmp3
, NULL
);
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.");
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.");
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
)) {
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
)) {
594 path
= wpurple_read_reg_expand_string(HKEY_CURRENT_USER
, "Software\\Lotus\\Notes\\7.0", "NotesIniPath");
595 if (is_empty(path
)) {
597 path
= wpurple_read_reg_expand_string(HKEY_CURRENT_USER
, "Software\\Lotus\\Notes\\6.0", "NotesIniPath");
598 if (is_empty(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
: "");
606 /* How to know location of notes.ini on *NIX ? */
611 char *mail_server
= NULL
;
612 char *mail_file
= NULL
;
614 sipe_domino_read_notes_ini(path
, &mail_server
, &mail_file
);
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
);
623 SIPE_DEBUG_INFO("sipe_domino_update_calendar: sip->cal->domino_url=%s", sip
->cal
->domino_url
? sip
->cal
->domino_url
: "");
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.");
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.");
644 sipe_domino_do_login_request(sip
->cal
);
648 SIPE_DEBUG_INFO_NOFORMAT("sipe_domino_update_calendar: finished.");