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
25 * Operates with HTTPS connection.
26 * Support Negotiate (Windows only) and NTLM authentications, redirect.
40 #include "eventloop.h"
44 #include "sipe-common.h"
47 #include "sipe-backend.h"
48 #include "sipe-utils.h"
49 #include "http-conn.h"
54 * @param url (%s) Ex.: /EWS/Exchange.asmx
55 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
56 * @param content_length (%d) length of body part
57 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
59 #define HTTP_CONN_POST_HEADER \
60 "POST %s HTTP/1.1\r\n"\
62 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"\
63 "Content-Length: %d\r\n"\
64 "Content-Type: %s\r\n"
67 struct http_conn_struct
{
68 PurpleAccount
*account
;
76 HttpConnCallback callback
;
80 PurpleSslConnection
*gsc
;
83 time_t last_keepalive
;
84 struct sip_connection
*conn
;
85 SipSecContext sec_ctx
;
92 http_conn_clone(HttpConn
* http_conn
)
94 HttpConn
*res
= g_new0(HttpConn
, 1);
96 res
->account
= http_conn
->account
;
97 res
->conn_type
= g_strdup(http_conn
->conn_type
);
98 res
->host
= g_strdup(http_conn
->host
);
99 res
->port
= http_conn
->port
;
100 res
->url
= g_strdup(http_conn
->url
);
101 res
->body
= g_strdup(http_conn
->body
);
102 res
->content_type
= g_strdup(http_conn
->content_type
);
103 res
->auth
= http_conn
->auth
;
104 res
->callback
= http_conn
->callback
;
105 res
->data
= http_conn
->data
;
108 res
->gsc
= http_conn
->gsc
;
109 res
->fd
= http_conn
->fd
;
110 res
->listenport
= http_conn
->listenport
;
111 res
->last_keepalive
= http_conn
->last_keepalive
;
112 res
->conn
= http_conn
->conn
;
113 res
->sec_ctx
= http_conn
->sec_ctx
;
114 res
->retries
= http_conn
->retries
;
116 res
->do_close
= NULL
;
122 http_conn_free(HttpConn
* http_conn
)
124 if (!http_conn
) return;
126 g_free(http_conn
->conn_type
);
127 g_free(http_conn
->host
);
128 g_free(http_conn
->url
);
129 g_free(http_conn
->body
);
130 g_free(http_conn
->content_type
);
132 if (http_conn
->sec_ctx
) {
133 sip_sec_destroy_context(http_conn
->sec_ctx
);
140 http_conn_auth_free(struct http_conn_auth
* auth
)
142 g_free(auth
->domain
);
144 g_free(auth
->password
);
149 http_conn_set_close(HttpConn
* http_conn
)
151 http_conn
->do_close
= http_conn
;
155 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
);
158 http_conn_close(HttpConn
*http_conn
, const char *message
)
160 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message
? message
: "");
162 http_conn_invalidate_ssl_connection(http_conn
);
163 http_conn_free(http_conn
);
167 * Extracts host, port and relative url
168 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
170 * Allocates memory, must be g_free'd.
173 http_conn_parse_url(const char *url
,
178 char **parts
= g_strsplit(url
, "://", 2);
186 } else if(!parts
[0]) {
191 no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
192 port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
199 tmp
= strstr(no_proto
, "/");
200 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
201 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
208 parts
= g_strsplit(host_port
, ":", 2);
211 if (host
) *host
= g_strdup(parts
[0]);
213 port_tmp
= parts
[1] ? atoi(parts
[1]) : port_tmp
;
215 if (port
) *port
= port_tmp
;
223 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
224 PurpleSslErrorType error
,
227 HttpConn
*http_conn
= data
;
228 const char *message
= NULL
;
230 http_conn
->gsc
= NULL
;
233 case PURPLE_SSL_CONNECT_FAILED
:
234 message
= "Connection failed";
236 case PURPLE_SSL_HANDSHAKE_FAILED
:
237 message
= "SSL handshake failed";
239 case PURPLE_SSL_CERTIFICATE_INVALID
:
240 message
= "SSL certificate invalid";
244 if (http_conn
->callback
) {
245 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
247 http_conn_close(http_conn
, message
);
251 http_conn_connection_remove(struct sip_connection
*conn
)
254 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
261 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
)
264 PurpleSslConnection
*gsc
= http_conn
->gsc
;
266 /* Invalidate this connection. Next send will open a new one */
268 struct sip_connection
*conn
= http_conn
->conn
;
270 http_conn_connection_remove(conn
);
271 http_conn
->conn
= NULL
;
272 purple_ssl_close(gsc
);
274 http_conn
->gsc
= NULL
;
280 http_conn_process_input(HttpConn
*http_conn
);
283 http_conn_input_cb_ssl(gpointer data
,
284 PurpleSslConnection
*gsc
,
285 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
287 HttpConn
*http_conn
= data
;
288 struct sip_connection
*conn
= http_conn
? http_conn
->conn
: NULL
;
290 gboolean firstread
= TRUE
;
293 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
297 /* Read all available data from the SSL connection */
299 /* Increase input buffer size as needed */
300 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
301 conn
->inbuflen
+= SIMPLE_BUF_INC
;
302 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
303 SIPE_DEBUG_INFO("http_conn_input_cb_ssl: new input buffer length %d", conn
->inbuflen
);
306 /* Try to read as much as there is space left in the buffer */
307 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
308 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
310 if (len
< 0 && errno
== EAGAIN
) {
311 /* Try again later */
313 } else if (len
< 0) {
314 if (http_conn
->callback
) {
315 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
317 http_conn_close(http_conn
, "SSL read error");
319 } else if (firstread
&& (len
== 0)) {
320 if (http_conn
->callback
) {
321 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
323 http_conn_close(http_conn
, "Server has disconnected");
327 conn
->inbufused
+= len
;
330 /* Equivalence indicates that there is possibly more data to read */
331 } while (len
== readlen
);
333 conn
->inbuf
[conn
->inbufused
] = '\0';
334 http_conn_process_input(http_conn
);
337 http_conn_post0(HttpConn
*http_conn
,
338 const char *authorization
);
341 http_conn_input0_cb_ssl(gpointer data
,
342 PurpleSslConnection
*gsc
,
343 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
345 HttpConn
*http_conn
= data
;
347 http_conn
->fd
= gsc
->fd
;
348 http_conn
->gsc
= gsc
;
349 http_conn
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
350 //http_conn->connecting = FALSE;
351 http_conn
->last_keepalive
= time(NULL
);
353 http_conn
->conn
= g_new0(struct sip_connection
, 1);
355 purple_ssl_input_add(gsc
, http_conn_input_cb_ssl
, http_conn
);
357 http_conn_post0(http_conn
, NULL
);
361 http_conn_create(PurpleAccount
*account
,
362 const char *conn_type
,
363 const char *full_url
,
365 const char *content_type
,
367 HttpConnCallback callback
,
372 if (!full_url
|| (strlen(full_url
) == 0)) {
373 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
376 if (sipe_strequal(conn_type
, HTTP_CONN_SSL
) &&
377 !purple_ssl_is_supported())
379 SIPE_DEBUG_INFO_NOFORMAT("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor.");
383 http_conn
= g_new0(HttpConn
, 1);
384 http_conn_parse_url(full_url
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
386 http_conn
->account
= account
;
387 http_conn
->conn_type
= g_strdup(conn_type
);
388 http_conn
->body
= g_strdup(body
);
389 http_conn
->content_type
= g_strdup(content_type
);
390 http_conn
->auth
= auth
;
391 http_conn
->callback
= callback
;
392 http_conn
->data
= data
;
394 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
, /* can we pass just NULL ? */
397 http_conn_input0_cb_ssl
,
398 http_conn_ssl_connect_failure
,
406 http_conn_process_input_message(HttpConn
*http_conn
,
410 http_conn_process_input(HttpConn
*http_conn
)
417 struct sip_connection
*conn
= http_conn
->conn
;
421 /* according to the RFC remove CRLF at the beginning */
422 while (*cur
== '\r' || *cur
== '\n') {
425 if (cur
!= conn
->inbuf
) {
426 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
427 conn
->inbufused
= strlen(conn
->inbuf
);
430 while ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
431 time_t currtime
= time(NULL
);
434 SIPE_DEBUG_INFO("received - %s******\n%s\n******", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
437 msg
= sipmsg_parse_header(conn
->inbuf
);
440 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
441 if (msg
&& restlen
>= msg
->bodylen
) {
442 dummy
= g_malloc(msg
->bodylen
+ 1);
443 memcpy(dummy
, cur
, msg
->bodylen
);
444 dummy
[msg
->bodylen
] = '\0';
447 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
448 conn
->inbufused
= strlen(conn
->inbuf
);
451 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
458 SIPE_DEBUG_INFO("body:\n%s", msg
->body
);
461 http_conn_process_input_message(http_conn
, msg
);
466 if (http_conn
->do_close
) {
467 http_conn_close(http_conn
->do_close
, "User initiated");
472 http_conn_sendout_pkt(HttpConn
*http_conn
,
475 time_t currtime
= time(NULL
);
476 int writelen
= strlen(buf
);
480 SIPE_DEBUG_INFO("sending - %s******\n%s\n******", ctime(&currtime
), tmp
= fix_newlines(buf
));
483 if (http_conn
->fd
< 0) {
484 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: http_conn->fd < 0, exiting");
488 if (http_conn
->gsc
) {
489 ret
= purple_ssl_write(http_conn
->gsc
, buf
, writelen
);
492 if (ret
< 0 && errno
== EAGAIN
)
494 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
495 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: ret <= 0, exiting");
499 if (ret
< writelen
) {
500 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: ret < writelen, exiting");
505 http_conn_post0(HttpConn
*http_conn
,
506 const char *authorization
)
508 GString
*outstr
= g_string_new("");
510 g_string_append_printf(outstr
, HTTP_CONN_POST_HEADER
,
513 http_conn
->body
? (int)strlen(http_conn
->body
) : 0,
514 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
516 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
518 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
520 http_conn_sendout_pkt(http_conn
, outstr
->str
);
521 g_string_free(outstr
, TRUE
);
525 http_conn_post( HttpConn
*http_conn
,
526 const char *full_url
,
528 const char *content_type
,
529 HttpConnCallback callback
,
533 SIPE_DEBUG_INFO_NOFORMAT("http_conn_post: NULL http_conn, exiting.");
537 g_free(http_conn
->url
);
538 g_free(http_conn
->body
);
539 g_free(http_conn
->content_type
);
540 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
541 http_conn
->body
= g_strdup(body
);
542 http_conn
->content_type
= g_strdup(content_type
);
543 http_conn
->callback
= callback
;
544 http_conn
->data
= data
;
546 http_conn_post0(http_conn
, NULL
);
550 http_conn_process_input_message(HttpConn
*http_conn
,
554 if (msg
->response
== 300 ||
555 msg
->response
== 301 ||
556 msg
->response
== 302 ||
557 msg
->response
== 307)
559 const char *location
= sipmsg_find_header(msg
, "Location");
561 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
563 http_conn
->do_close
= http_conn_clone(http_conn
);
564 http_conn
->sec_ctx
= NULL
;
566 g_free(http_conn
->host
);
567 g_free(http_conn
->url
);
568 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
570 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
,
573 http_conn_input0_cb_ssl
,
574 http_conn_ssl_connect_failure
,
578 /* Authentication required */
579 else if (msg
->response
== 401) {
586 SipSecAuthType auth_type
;
587 const char *auth_name
;
589 char *output_toked_base64
;
590 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
593 http_conn
->retries
++;
594 if (http_conn
->retries
> 2) {
595 if (http_conn
->callback
) {
596 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
598 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
599 http_conn_set_close(http_conn
);
603 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
604 auth_type
= AUTH_TYPE_NTLM
;
608 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
609 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
611 auth_type
= AUTH_TYPE_NEGOTIATE
;
612 auth_name
= "Negotiate";
617 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
620 "NTLM and Negotiate authentications are"
621 #else /* !HAVE_LIBKRB5 */
622 "NTLM authentication is"
623 #endif /* HAVE_LIBKRB5 */
625 "NTLM authentication is"
631 if (!http_conn
->sec_ctx
) {
633 sip_sec_create_context(auth_type
,
636 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
637 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
638 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
641 if (http_conn
->sec_ctx
) {
642 char **parts
= g_strsplit(ptmp
, " ", 0);
643 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
644 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
647 &output_toked_base64
,
654 if (http_conn
->callback
) {
655 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
657 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
658 http_conn_set_close(http_conn
);
662 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
663 g_free(output_toked_base64
);
665 http_conn_post0(http_conn
, authorization
);
666 g_free(authorization
);
670 http_conn
->retries
= 0;
672 if (http_conn
->callback
) {
673 (*http_conn
->callback
)(msg
->response
, msg
->body
, http_conn
, http_conn
->data
);