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.
38 #include "sipe-utils.h"
40 #include "http-conn.h"
44 * @param url (%s) Ex.: /EWS/Exchange.asmx
45 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
46 * @param content_length (%d) length of body part
47 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
49 #define HTTP_CONN_POST_HEADER \
50 "POST %s HTTP/1.1\r\n"\
52 "User-Agent: Sipe/" SIPE_VERSION "\r\n"\
53 "Content-Length: %d\r\n"\
54 "Content-Type: %s\r\n"
57 struct http_conn_struct
{
58 PurpleAccount
*account
;
66 HttpConnCallback callback
;
70 PurpleSslConnection
*gsc
;
73 time_t last_keepalive
;
74 struct sip_connection
*conn
;
75 SipSecContext sec_ctx
;
82 http_conn_clone(HttpConn
* http_conn
)
84 HttpConn
*res
= g_new0(HttpConn
, 1);
86 res
->account
= http_conn
->account
;
87 res
->conn_type
= g_strdup(http_conn
->conn_type
);
88 res
->host
= g_strdup(http_conn
->host
);
89 res
->port
= http_conn
->port
;
90 res
->url
= g_strdup(http_conn
->url
);
91 res
->body
= g_strdup(http_conn
->body
);
92 res
->content_type
= g_strdup(http_conn
->content_type
);
93 res
->auth
= http_conn
->auth
;
94 res
->callback
= http_conn
->callback
;
95 res
->data
= http_conn
->data
;
98 res
->gsc
= http_conn
->gsc
;
99 res
->fd
= http_conn
->fd
;
100 res
->listenport
= http_conn
->listenport
;
101 res
->last_keepalive
= http_conn
->last_keepalive
;
102 res
->conn
= http_conn
->conn
;
103 res
->sec_ctx
= http_conn
->sec_ctx
;
104 res
->retries
= http_conn
->retries
;
106 res
->do_close
= NULL
;
112 http_conn_free(HttpConn
* http_conn
)
114 if (!http_conn
) return;
116 g_free(http_conn
->conn_type
);
117 g_free(http_conn
->host
);
118 g_free(http_conn
->url
);
119 g_free(http_conn
->body
);
120 g_free(http_conn
->content_type
);
122 if (http_conn
->sec_ctx
) {
123 sip_sec_destroy_context(http_conn
->sec_ctx
);
130 http_conn_auth_free(struct http_conn_auth
* auth
)
132 g_free(auth
->domain
);
134 g_free(auth
->password
);
139 http_conn_set_close(HttpConn
* http_conn
)
141 http_conn
->do_close
= http_conn
;
145 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
);
148 http_conn_close(HttpConn
*http_conn
, const char *message
)
150 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message
? message
: "");
152 http_conn_invalidate_ssl_connection(http_conn
);
153 http_conn_free(http_conn
);
157 * Extracts host, port and relative url
158 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
160 * Allocates memory, must be g_free'd.
163 http_conn_parse_url(const char *url
,
168 char **parts
= g_strsplit(url
, "://", 2);
169 char *no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
170 int port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
175 tmp
= strstr(no_proto
, "/");
176 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
177 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
180 parts
= g_strsplit(host_port
, ":", 2);
181 if (host
) *host
= g_strdup(parts
[0]);
182 if (port
) *port
= parts
[1] ? atoi(parts
[1]) : port_tmp
;
189 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
190 PurpleSslErrorType error
,
193 HttpConn
*http_conn
= data
;
194 const char *message
= NULL
;
196 http_conn
->gsc
= NULL
;
199 case PURPLE_SSL_CONNECT_FAILED
:
200 message
= "Connection failed";
202 case PURPLE_SSL_HANDSHAKE_FAILED
:
203 message
= "SSL handshake failed";
205 case PURPLE_SSL_CERTIFICATE_INVALID
:
206 message
= "SSL certificate invalid";
210 if (http_conn
->callback
) {
211 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
213 http_conn_close(http_conn
, message
);
217 http_conn_connection_remove(struct sip_connection
*conn
)
220 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
227 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
)
230 PurpleSslConnection
*gsc
= http_conn
->gsc
;
232 /* Invalidate this connection. Next send will open a new one */
234 struct sip_connection
*conn
= http_conn
->conn
;
236 http_conn_connection_remove(conn
);
237 http_conn
->conn
= NULL
;
238 purple_ssl_close(gsc
);
240 http_conn
->gsc
= NULL
;
246 http_conn_process_input(HttpConn
*http_conn
);
249 http_conn_input_cb_ssl(gpointer data
,
250 PurpleSslConnection
*gsc
,
251 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
253 HttpConn
*http_conn
= data
;
254 struct sip_connection
*conn
= http_conn
? http_conn
->conn
: NULL
;
256 gboolean firstread
= TRUE
;
259 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
263 /* Read all available data from the SSL connection */
265 /* Increase input buffer size as needed */
266 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
267 conn
->inbuflen
+= SIMPLE_BUF_INC
;
268 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
269 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
272 /* Try to read as much as there is space left in the buffer */
273 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
274 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
276 if (len
< 0 && errno
== EAGAIN
) {
277 /* Try again later */
279 } else if (len
< 0) {
280 if (http_conn
->callback
) {
281 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
283 http_conn_close(http_conn
, "SSL read error");
285 } else if (firstread
&& (len
== 0)) {
286 if (http_conn
->callback
) {
287 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
289 http_conn_close(http_conn
, "Server has disconnected");
293 conn
->inbufused
+= len
;
296 /* Equivalence indicates that there is possibly more data to read */
297 } while (len
== readlen
);
299 conn
->inbuf
[conn
->inbufused
] = '\0';
300 http_conn_process_input(http_conn
);
303 http_conn_post0(HttpConn
*http_conn
,
304 const char *authorization
);
307 http_conn_input0_cb_ssl(gpointer data
,
308 PurpleSslConnection
*gsc
,
309 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
311 HttpConn
*http_conn
= data
;
313 http_conn
->fd
= gsc
->fd
;
314 http_conn
->gsc
= gsc
;
315 http_conn
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
316 //http_conn->connecting = FALSE;
317 http_conn
->last_keepalive
= time(NULL
);
319 http_conn
->conn
= g_new0(struct sip_connection
, 1);
321 purple_ssl_input_add(gsc
, http_conn_input_cb_ssl
, http_conn
);
323 http_conn_post0(http_conn
, NULL
);
327 http_conn_create(PurpleAccount
*account
,
328 const char *conn_type
,
329 const char *full_url
,
331 const char *content_type
,
333 HttpConnCallback callback
,
338 if (!full_url
|| (strlen(full_url
) == 0)) {
339 purple_debug_info("sipe-http", "no URL supplied!\n");
342 if (sipe_strequal(conn_type
, HTTP_CONN_SSL
) &&
343 !purple_ssl_is_supported())
345 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");
349 http_conn
= g_new0(HttpConn
, 1);
350 http_conn_parse_url(full_url
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
352 http_conn
->account
= account
;
353 http_conn
->conn_type
= g_strdup(conn_type
);
354 http_conn
->body
= g_strdup(body
);
355 http_conn
->content_type
= g_strdup(content_type
);
356 http_conn
->auth
= auth
;
357 http_conn
->callback
= callback
;
358 http_conn
->data
= data
;
360 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
, /* can we pass just NULL ? */
363 http_conn_input0_cb_ssl
,
364 http_conn_ssl_connect_failure
,
372 http_conn_process_input_message(HttpConn
*http_conn
,
376 http_conn_process_input(HttpConn
*http_conn
)
383 struct sip_connection
*conn
= http_conn
->conn
;
387 /* according to the RFC remove CRLF at the beginning */
388 while (*cur
== '\r' || *cur
== '\n') {
391 if (cur
!= conn
->inbuf
) {
392 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
393 conn
->inbufused
= strlen(conn
->inbuf
);
396 while ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
397 time_t currtime
= time(NULL
);
400 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
403 msg
= sipmsg_parse_header(conn
->inbuf
);
406 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
407 if (msg
&& restlen
>= msg
->bodylen
) {
408 dummy
= g_malloc(msg
->bodylen
+ 1);
409 memcpy(dummy
, cur
, msg
->bodylen
);
410 dummy
[msg
->bodylen
] = '\0';
413 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
414 conn
->inbufused
= strlen(conn
->inbuf
);
417 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
424 purple_debug_info("sipe-http", "body:\n%s\n", msg
->body
);
427 http_conn_process_input_message(http_conn
, msg
);
432 if (http_conn
->do_close
) {
433 http_conn_close(http_conn
->do_close
, "User initiated");
438 http_conn_sendout_pkt(HttpConn
*http_conn
,
441 time_t currtime
= time(NULL
);
442 int writelen
= strlen(buf
);
446 purple_debug(PURPLE_DEBUG_MISC
, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
449 if (http_conn
->fd
< 0) {
450 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
454 if (http_conn
->gsc
) {
455 ret
= purple_ssl_write(http_conn
->gsc
, buf
, writelen
);
458 if (ret
< 0 && errno
== EAGAIN
)
460 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
461 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
465 if (ret
< writelen
) {
466 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
471 http_conn_post0(HttpConn
*http_conn
,
472 const char *authorization
)
474 GString
*outstr
= g_string_new("");
476 g_string_append_printf(outstr
, HTTP_CONN_POST_HEADER
,
479 http_conn
->body
? (int)strlen(http_conn
->body
) : 0,
480 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
482 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
484 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
486 http_conn_sendout_pkt(http_conn
, outstr
->str
);
487 g_string_free(outstr
, TRUE
);
491 http_conn_post( HttpConn
*http_conn
,
492 const char *full_url
,
494 const char *content_type
,
495 HttpConnCallback callback
,
499 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
503 g_free(http_conn
->url
);
504 g_free(http_conn
->body
);
505 g_free(http_conn
->content_type
);
506 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
507 http_conn
->body
= g_strdup(body
);
508 http_conn
->content_type
= g_strdup(content_type
);
509 http_conn
->callback
= callback
;
510 http_conn
->data
= data
;
512 http_conn_post0(http_conn
, NULL
);
516 http_conn_process_input_message(HttpConn
*http_conn
,
520 if (msg
->response
== 300 ||
521 msg
->response
== 301 ||
522 msg
->response
== 302 ||
523 msg
->response
== 307)
525 const char *location
= sipmsg_find_header(msg
, "Location");
527 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location
? location
: "");
529 http_conn
->do_close
= http_conn_clone(http_conn
);
530 http_conn
->sec_ctx
= NULL
;
532 g_free(http_conn
->host
);
533 g_free(http_conn
->url
);
534 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
536 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
,
539 http_conn_input0_cb_ssl
,
540 http_conn_ssl_connect_failure
,
544 /* Authentication required */
545 else if (msg
->response
== 401) {
552 SipSecAuthType auth_type
;
553 const char *auth_name
;
555 char *output_toked_base64
;
556 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
559 http_conn
->retries
++;
560 if (http_conn
->retries
> 2) {
561 if (http_conn
->callback
) {
562 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
564 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
565 http_conn_set_close(http_conn
);
569 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
570 auth_type
= AUTH_TYPE_NTLM
;
574 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
575 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
577 auth_type
= AUTH_TYPE_NEGOTIATE
;
578 auth_name
= "Negotiate";
583 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
586 "NTLM and Negotiate authentications are"
588 "NTLM authentication is"
589 #endif //USE_KERBEROS
591 "NTLM authentication is"
597 if (!http_conn
->sec_ctx
) {
599 sip_sec_create_context(auth_type
,
602 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
603 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
604 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
607 if (http_conn
->sec_ctx
) {
608 char **parts
= g_strsplit(ptmp
, " ", 0);
609 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
610 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
613 &output_toked_base64
,
620 if (http_conn
->callback
) {
621 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
623 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
624 http_conn_set_close(http_conn
);
628 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
629 g_free(output_toked_base64
);
631 http_conn_post0(http_conn
, authorization
);
632 g_free(authorization
);
636 http_conn
->retries
= 0;
638 if (http_conn
->callback
) {
639 (*http_conn
->callback
)(msg
->response
, msg
->body
, http_conn
, http_conn
->data
);