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.
34 #include "sipe-utils.h"
36 #include "http-conn.h"
40 * @param url (%s) Ex.: /EWS/Exchange.asmx
41 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
42 * @param content_length (%d) length of body part
43 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
45 #define HTTP_CONN_POST_HEADER \
46 "POST %s HTTP/1.1\r\n"\
48 "User-Agent: Sipe/" SIPE_VERSION "\r\n"\
49 "Content-Length: %d\r\n"\
50 "Content-Type: %s\r\n"
53 struct http_conn_struct
{
54 PurpleAccount
*account
;
62 HttpConnCallback callback
;
66 PurpleSslConnection
*gsc
;
69 time_t last_keepalive
;
70 struct sip_connection
*conn
;
71 SipSecContext sec_ctx
;
78 http_conn_clone(HttpConn
* http_conn
)
80 HttpConn
*res
= g_new0(HttpConn
, 1);
82 res
->account
= http_conn
->account
;
83 res
->conn_type
= g_strdup(http_conn
->conn_type
);
84 res
->host
= g_strdup(http_conn
->host
);
85 res
->port
= http_conn
->port
;
86 res
->url
= g_strdup(http_conn
->url
);
87 res
->body
= g_strdup(http_conn
->body
);
88 res
->content_type
= g_strdup(http_conn
->content_type
);
89 res
->auth
= http_conn
->auth
;
90 res
->callback
= http_conn
->callback
;
91 res
->data
= http_conn
->data
;
94 res
->gsc
= http_conn
->gsc
;
95 res
->fd
= http_conn
->fd
;
96 res
->listenport
= http_conn
->listenport
;
97 res
->last_keepalive
= http_conn
->last_keepalive
;
98 res
->conn
= http_conn
->conn
;
99 res
->sec_ctx
= http_conn
->sec_ctx
;
100 res
->retries
= http_conn
->retries
;
102 res
->do_close
= NULL
;
108 http_conn_free(HttpConn
* http_conn
)
110 if (!http_conn
) return;
112 g_free(http_conn
->conn_type
);
113 g_free(http_conn
->host
);
114 g_free(http_conn
->url
);
115 g_free(http_conn
->body
);
116 g_free(http_conn
->content_type
);
118 if (http_conn
->sec_ctx
) {
119 sip_sec_destroy_context(http_conn
->sec_ctx
);
126 http_conn_auth_free(struct http_conn_auth
* auth
)
128 g_free(auth
->domain
);
130 g_free(auth
->password
);
135 http_conn_set_close(HttpConn
* http_conn
)
137 http_conn
->do_close
= http_conn
;
141 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
);
144 http_conn_close(HttpConn
*http_conn
, const char *message
)
146 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message
? message
: "");
148 http_conn_invalidate_ssl_connection(http_conn
);
149 http_conn_free(http_conn
);
153 * Extracts host, port and relative url
154 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
156 * Allocates memory, must be g_free'd.
159 http_conn_parse_url(const char *url
,
164 char **parts
= g_strsplit(url
, "://", 2);
165 char *no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
166 int port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
171 tmp
= strstr(no_proto
, "/");
172 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
173 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
176 parts
= g_strsplit(host_port
, ":", 2);
177 if (host
) *host
= g_strdup(parts
[0]);
178 if (port
) *port
= parts
[1] ? atoi(parts
[1]) : port_tmp
;
185 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
186 PurpleSslErrorType error
,
189 HttpConn
*http_conn
= data
;
190 const char *message
= NULL
;
192 http_conn
->gsc
= NULL
;
195 case PURPLE_SSL_CONNECT_FAILED
:
196 message
= "Connection failed";
198 case PURPLE_SSL_HANDSHAKE_FAILED
:
199 message
= "SSL handshake failed";
201 case PURPLE_SSL_CERTIFICATE_INVALID
:
202 message
= "SSL certificate invalid";
206 if (http_conn
->callback
) {
207 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
209 http_conn_close(http_conn
, message
);
213 http_conn_connection_remove(struct sip_connection
*conn
)
216 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
223 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
)
226 PurpleSslConnection
*gsc
= http_conn
->gsc
;
228 /* Invalidate this connection. Next send will open a new one */
230 struct sip_connection
*conn
= http_conn
->conn
;
232 http_conn_connection_remove(conn
);
233 http_conn
->conn
= NULL
;
234 purple_ssl_close(gsc
);
236 http_conn
->gsc
= NULL
;
242 http_conn_process_input(HttpConn
*http_conn
);
245 http_conn_input_cb_ssl(gpointer data
,
246 PurpleSslConnection
*gsc
,
247 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
249 HttpConn
*http_conn
= data
;
250 struct sip_connection
*conn
= http_conn
? http_conn
->conn
: NULL
;
252 gboolean firstread
= TRUE
;
255 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
259 /* Read all available data from the SSL connection */
261 /* Increase input buffer size as needed */
262 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
263 conn
->inbuflen
+= SIMPLE_BUF_INC
;
264 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
265 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
268 /* Try to read as much as there is space left in the buffer */
269 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
270 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
272 if (len
< 0 && errno
== EAGAIN
) {
273 /* Try again later */
275 } else if (len
< 0) {
276 if (http_conn
->callback
) {
277 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
279 http_conn_close(http_conn
, "SSL read error");
281 } else if (firstread
&& (len
== 0)) {
282 if (http_conn
->callback
) {
283 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
285 http_conn_close(http_conn
, "Server has disconnected");
289 conn
->inbufused
+= len
;
292 /* Equivalence indicates that there is possibly more data to read */
293 } while (len
== readlen
);
295 conn
->inbuf
[conn
->inbufused
] = '\0';
296 http_conn_process_input(http_conn
);
299 http_conn_post0(HttpConn
*http_conn
,
300 const char *authorization
);
303 http_conn_input0_cb_ssl(gpointer data
,
304 PurpleSslConnection
*gsc
,
305 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
307 HttpConn
*http_conn
= data
;
309 http_conn
->fd
= gsc
->fd
;
310 http_conn
->gsc
= gsc
;
311 http_conn
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
312 //http_conn->connecting = FALSE;
313 http_conn
->last_keepalive
= time(NULL
);
315 http_conn
->conn
= g_new0(struct sip_connection
, 1);
317 purple_ssl_input_add(gsc
, http_conn_input_cb_ssl
, http_conn
);
319 http_conn_post0(http_conn
, NULL
);
323 http_conn_create(PurpleAccount
*account
,
324 const char *conn_type
,
325 const char *full_url
,
327 const char *content_type
,
329 HttpConnCallback callback
,
334 if (!full_url
|| (strlen(full_url
) == 0)) {
335 purple_debug_info("sipe-http", "no URL supplied!\n");
338 if (sipe_strequal(conn_type
, HTTP_CONN_SSL
) &&
339 !purple_ssl_is_supported())
341 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");
345 http_conn
= g_new0(HttpConn
, 1);
346 http_conn_parse_url(full_url
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
348 http_conn
->account
= account
;
349 http_conn
->conn_type
= g_strdup(conn_type
);
350 http_conn
->body
= g_strdup(body
);
351 http_conn
->content_type
= g_strdup(content_type
);
352 http_conn
->auth
= auth
;
353 http_conn
->callback
= callback
;
354 http_conn
->data
= data
;
356 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
, /* can we pass just NULL ? */
359 http_conn_input0_cb_ssl
,
360 http_conn_ssl_connect_failure
,
368 http_conn_process_input_message(HttpConn
*http_conn
,
372 http_conn_process_input(HttpConn
*http_conn
)
379 struct sip_connection
*conn
= http_conn
->conn
;
383 /* according to the RFC remove CRLF at the beginning */
384 while (*cur
== '\r' || *cur
== '\n') {
387 if (cur
!= conn
->inbuf
) {
388 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
389 conn
->inbufused
= strlen(conn
->inbuf
);
392 while ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
393 time_t currtime
= time(NULL
);
396 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
399 msg
= sipmsg_parse_header(conn
->inbuf
);
402 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
403 if (msg
&& restlen
>= msg
->bodylen
) {
404 dummy
= g_malloc(msg
->bodylen
+ 1);
405 memcpy(dummy
, cur
, msg
->bodylen
);
406 dummy
[msg
->bodylen
] = '\0';
409 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
410 conn
->inbufused
= strlen(conn
->inbuf
);
413 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
420 purple_debug_info("sipe-http", "body:\n%s\n", msg
->body
);
423 http_conn_process_input_message(http_conn
, msg
);
428 if (http_conn
->do_close
) {
429 http_conn_close(http_conn
->do_close
, "User initiated");
434 http_conn_sendout_pkt(HttpConn
*http_conn
,
437 time_t currtime
= time(NULL
);
438 int writelen
= strlen(buf
);
442 purple_debug(PURPLE_DEBUG_MISC
, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
445 if (http_conn
->fd
< 0) {
446 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
450 if (http_conn
->gsc
) {
451 ret
= purple_ssl_write(http_conn
->gsc
, buf
, writelen
);
454 if (ret
< 0 && errno
== EAGAIN
)
456 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
457 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
461 if (ret
< writelen
) {
462 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
467 http_conn_post0(HttpConn
*http_conn
,
468 const char *authorization
)
470 GString
*outstr
= g_string_new("");
472 g_string_append_printf(outstr
, HTTP_CONN_POST_HEADER
,
475 http_conn
->body
? (int)strlen(http_conn
->body
) : 0,
476 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
478 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
480 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
482 http_conn_sendout_pkt(http_conn
, outstr
->str
);
483 g_string_free(outstr
, TRUE
);
487 http_conn_post( HttpConn
*http_conn
,
488 const char *full_url
,
490 const char *content_type
,
491 HttpConnCallback callback
,
495 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
499 g_free(http_conn
->url
);
500 g_free(http_conn
->body
);
501 g_free(http_conn
->content_type
);
502 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
503 http_conn
->body
= g_strdup(body
);
504 http_conn
->content_type
= g_strdup(content_type
);
505 http_conn
->callback
= callback
;
506 http_conn
->data
= data
;
508 http_conn_post0(http_conn
, NULL
);
512 http_conn_process_input_message(HttpConn
*http_conn
,
516 if (msg
->response
== 300 ||
517 msg
->response
== 301 ||
518 msg
->response
== 302 ||
519 msg
->response
== 307)
521 char *location
= sipmsg_find_header(msg
, "Location");
523 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location
? location
: "");
525 http_conn
->do_close
= http_conn_clone(http_conn
);
526 http_conn
->sec_ctx
= NULL
;
528 g_free(http_conn
->host
);
529 g_free(http_conn
->url
);
530 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
532 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
,
535 http_conn_input0_cb_ssl
,
536 http_conn_ssl_connect_failure
,
540 /* Authentication required */
541 else if (msg
->response
== 401) {
549 SipSecAuthType auth_type
;
550 const char *auth_name
;
552 char *output_toked_base64
;
554 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
557 http_conn
->retries
++;
558 if (http_conn
->retries
> 2) {
559 if (http_conn
->callback
) {
560 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
562 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
563 http_conn_set_close(http_conn
);
567 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
568 auth_type
= AUTH_TYPE_NTLM
;
572 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
573 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
575 auth_type
= AUTH_TYPE_NEGOTIATE
;
576 auth_name
= "Negotiate";
581 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
584 "NTLM and Negotiate authentications are"
586 "NTLM authentication is"
587 #endif //USE_KERBEROS
589 "NTLM authentication is"
595 if (!http_conn
->sec_ctx
) {
596 sip_sec_create_context(&http_conn
->sec_ctx
,
600 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
601 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
602 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
605 parts
= g_strsplit(ptmp
, " ", 0);
606 spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
607 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
610 &output_toked_base64
,
616 if (http_conn
->callback
) {
617 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
619 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
620 http_conn_set_close(http_conn
);
624 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
625 g_free(output_toked_base64
);
627 http_conn_post0(http_conn
, authorization
);
628 g_free(authorization
);
632 http_conn
->retries
= 0;
634 if (http_conn
->callback
) {
635 (*http_conn
->callback
)(msg
->response
, msg
->body
, http_conn
, http_conn
->data
);