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.
41 #include "eventloop.h"
45 #include "sipe-common.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 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", 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);
179 char *no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
180 int port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
185 tmp
= strstr(no_proto
, "/");
186 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
187 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
190 parts
= g_strsplit(host_port
, ":", 2);
191 if (host
) *host
= g_strdup(parts
[0]);
192 if (port
) *port
= parts
[1] ? atoi(parts
[1]) : port_tmp
;
199 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
200 PurpleSslErrorType error
,
203 HttpConn
*http_conn
= data
;
204 const char *message
= NULL
;
206 http_conn
->gsc
= NULL
;
209 case PURPLE_SSL_CONNECT_FAILED
:
210 message
= "Connection failed";
212 case PURPLE_SSL_HANDSHAKE_FAILED
:
213 message
= "SSL handshake failed";
215 case PURPLE_SSL_CERTIFICATE_INVALID
:
216 message
= "SSL certificate invalid";
220 if (http_conn
->callback
) {
221 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
223 http_conn_close(http_conn
, message
);
227 http_conn_connection_remove(struct sip_connection
*conn
)
230 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
237 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
)
240 PurpleSslConnection
*gsc
= http_conn
->gsc
;
242 /* Invalidate this connection. Next send will open a new one */
244 struct sip_connection
*conn
= http_conn
->conn
;
246 http_conn_connection_remove(conn
);
247 http_conn
->conn
= NULL
;
248 purple_ssl_close(gsc
);
250 http_conn
->gsc
= NULL
;
256 http_conn_process_input(HttpConn
*http_conn
);
259 http_conn_input_cb_ssl(gpointer data
,
260 PurpleSslConnection
*gsc
,
261 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
263 HttpConn
*http_conn
= data
;
264 struct sip_connection
*conn
= http_conn
? http_conn
->conn
: NULL
;
266 gboolean firstread
= TRUE
;
269 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
273 /* Read all available data from the SSL connection */
275 /* Increase input buffer size as needed */
276 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
277 conn
->inbuflen
+= SIMPLE_BUF_INC
;
278 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
279 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
282 /* Try to read as much as there is space left in the buffer */
283 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
284 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
286 if (len
< 0 && errno
== EAGAIN
) {
287 /* Try again later */
289 } else if (len
< 0) {
290 if (http_conn
->callback
) {
291 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
293 http_conn_close(http_conn
, "SSL read error");
295 } else if (firstread
&& (len
== 0)) {
296 if (http_conn
->callback
) {
297 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
299 http_conn_close(http_conn
, "Server has disconnected");
303 conn
->inbufused
+= len
;
306 /* Equivalence indicates that there is possibly more data to read */
307 } while (len
== readlen
);
309 conn
->inbuf
[conn
->inbufused
] = '\0';
310 http_conn_process_input(http_conn
);
313 http_conn_post0(HttpConn
*http_conn
,
314 const char *authorization
);
317 http_conn_input0_cb_ssl(gpointer data
,
318 PurpleSslConnection
*gsc
,
319 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
321 HttpConn
*http_conn
= data
;
323 http_conn
->fd
= gsc
->fd
;
324 http_conn
->gsc
= gsc
;
325 http_conn
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
326 //http_conn->connecting = FALSE;
327 http_conn
->last_keepalive
= time(NULL
);
329 http_conn
->conn
= g_new0(struct sip_connection
, 1);
331 purple_ssl_input_add(gsc
, http_conn_input_cb_ssl
, http_conn
);
333 http_conn_post0(http_conn
, NULL
);
337 http_conn_create(PurpleAccount
*account
,
338 const char *conn_type
,
339 const char *full_url
,
341 const char *content_type
,
343 HttpConnCallback callback
,
348 if (!full_url
|| (strlen(full_url
) == 0)) {
349 purple_debug_info("sipe-http", "no URL supplied!\n");
352 if (sipe_strequal(conn_type
, HTTP_CONN_SSL
) &&
353 !purple_ssl_is_supported())
355 purple_debug_info("sipe-http", "SSL support is not installed. Either install SSL support or configure a different connection type in the account editor\n");
359 http_conn
= g_new0(HttpConn
, 1);
360 http_conn_parse_url(full_url
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
362 http_conn
->account
= account
;
363 http_conn
->conn_type
= g_strdup(conn_type
);
364 http_conn
->body
= g_strdup(body
);
365 http_conn
->content_type
= g_strdup(content_type
);
366 http_conn
->auth
= auth
;
367 http_conn
->callback
= callback
;
368 http_conn
->data
= data
;
370 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
, /* can we pass just NULL ? */
373 http_conn_input0_cb_ssl
,
374 http_conn_ssl_connect_failure
,
382 http_conn_process_input_message(HttpConn
*http_conn
,
386 http_conn_process_input(HttpConn
*http_conn
)
393 struct sip_connection
*conn
= http_conn
->conn
;
397 /* according to the RFC remove CRLF at the beginning */
398 while (*cur
== '\r' || *cur
== '\n') {
401 if (cur
!= conn
->inbuf
) {
402 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
403 conn
->inbufused
= strlen(conn
->inbuf
);
406 while ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
407 time_t currtime
= time(NULL
);
410 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
413 msg
= sipmsg_parse_header(conn
->inbuf
);
416 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
417 if (msg
&& restlen
>= msg
->bodylen
) {
418 dummy
= g_malloc(msg
->bodylen
+ 1);
419 memcpy(dummy
, cur
, msg
->bodylen
);
420 dummy
[msg
->bodylen
] = '\0';
423 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
424 conn
->inbufused
= strlen(conn
->inbuf
);
427 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
434 purple_debug_info("sipe-http", "body:\n%s\n", msg
->body
);
437 http_conn_process_input_message(http_conn
, msg
);
442 if (http_conn
->do_close
) {
443 http_conn_close(http_conn
->do_close
, "User initiated");
448 http_conn_sendout_pkt(HttpConn
*http_conn
,
451 time_t currtime
= time(NULL
);
452 int writelen
= strlen(buf
);
456 purple_debug(PURPLE_DEBUG_MISC
, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
459 if (http_conn
->fd
< 0) {
460 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
464 if (http_conn
->gsc
) {
465 ret
= purple_ssl_write(http_conn
->gsc
, buf
, writelen
);
468 if (ret
< 0 && errno
== EAGAIN
)
470 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
471 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
475 if (ret
< writelen
) {
476 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
481 http_conn_post0(HttpConn
*http_conn
,
482 const char *authorization
)
484 GString
*outstr
= g_string_new("");
486 g_string_append_printf(outstr
, HTTP_CONN_POST_HEADER
,
489 http_conn
->body
? (int)strlen(http_conn
->body
) : 0,
490 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
492 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
494 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
496 http_conn_sendout_pkt(http_conn
, outstr
->str
);
497 g_string_free(outstr
, TRUE
);
501 http_conn_post( HttpConn
*http_conn
,
502 const char *full_url
,
504 const char *content_type
,
505 HttpConnCallback callback
,
509 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
513 g_free(http_conn
->url
);
514 g_free(http_conn
->body
);
515 g_free(http_conn
->content_type
);
516 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
517 http_conn
->body
= g_strdup(body
);
518 http_conn
->content_type
= g_strdup(content_type
);
519 http_conn
->callback
= callback
;
520 http_conn
->data
= data
;
522 http_conn_post0(http_conn
, NULL
);
526 http_conn_process_input_message(HttpConn
*http_conn
,
530 if (msg
->response
== 300 ||
531 msg
->response
== 301 ||
532 msg
->response
== 302 ||
533 msg
->response
== 307)
535 const char *location
= sipmsg_find_header(msg
, "Location");
537 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location
? location
: "");
539 http_conn
->do_close
= http_conn_clone(http_conn
);
540 http_conn
->sec_ctx
= NULL
;
542 g_free(http_conn
->host
);
543 g_free(http_conn
->url
);
544 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
546 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
,
549 http_conn_input0_cb_ssl
,
550 http_conn_ssl_connect_failure
,
554 /* Authentication required */
555 else if (msg
->response
== 401) {
562 SipSecAuthType auth_type
;
563 const char *auth_name
;
565 char *output_toked_base64
;
566 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
569 http_conn
->retries
++;
570 if (http_conn
->retries
> 2) {
571 if (http_conn
->callback
) {
572 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
574 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
575 http_conn_set_close(http_conn
);
579 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
580 auth_type
= AUTH_TYPE_NTLM
;
584 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
585 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
587 auth_type
= AUTH_TYPE_NEGOTIATE
;
588 auth_name
= "Negotiate";
593 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
596 "NTLM and Negotiate authentications are"
597 #else /* !HAVE_KERBEROS */
598 "NTLM authentication is"
599 #endif /* HAVE_KERBEROS */
601 "NTLM authentication is"
607 if (!http_conn
->sec_ctx
) {
609 sip_sec_create_context(auth_type
,
612 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
613 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
614 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
617 if (http_conn
->sec_ctx
) {
618 char **parts
= g_strsplit(ptmp
, " ", 0);
619 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
620 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
623 &output_toked_base64
,
630 if (http_conn
->callback
) {
631 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
633 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
634 http_conn_set_close(http_conn
);
638 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
639 g_free(output_toked_base64
);
641 http_conn_post0(http_conn
, authorization
);
642 g_free(authorization
);
646 http_conn
->retries
= 0;
648 if (http_conn
->callback
) {
649 (*http_conn
->callback
)(msg
->response
, msg
->body
, http_conn
, http_conn
->data
);