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 "circbuffer.h"
42 #include "connection.h"
46 #include "eventloop.h"
54 #include "sipe-utils.h"
55 #include "http-conn.h"
60 * @param url (%s) Ex.: /EWS/Exchange.asmx
61 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
62 * @param content_length (%d) length of body part
63 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
65 #define HTTP_CONN_POST_HEADER \
66 "POST %s HTTP/1.1\r\n"\
68 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"\
69 "Content-Length: %d\r\n"\
70 "Content-Type: %s\r\n"
73 struct http_conn_struct
{
74 PurpleAccount
*account
;
82 HttpConnCallback callback
;
86 PurpleSslConnection
*gsc
;
89 time_t last_keepalive
;
90 struct sip_connection
*conn
;
91 SipSecContext sec_ctx
;
98 http_conn_clone(HttpConn
* http_conn
)
100 HttpConn
*res
= g_new0(HttpConn
, 1);
102 res
->account
= http_conn
->account
;
103 res
->conn_type
= g_strdup(http_conn
->conn_type
);
104 res
->host
= g_strdup(http_conn
->host
);
105 res
->port
= http_conn
->port
;
106 res
->url
= g_strdup(http_conn
->url
);
107 res
->body
= g_strdup(http_conn
->body
);
108 res
->content_type
= g_strdup(http_conn
->content_type
);
109 res
->auth
= http_conn
->auth
;
110 res
->callback
= http_conn
->callback
;
111 res
->data
= http_conn
->data
;
114 res
->gsc
= http_conn
->gsc
;
115 res
->fd
= http_conn
->fd
;
116 res
->listenport
= http_conn
->listenport
;
117 res
->last_keepalive
= http_conn
->last_keepalive
;
118 res
->conn
= http_conn
->conn
;
119 res
->sec_ctx
= http_conn
->sec_ctx
;
120 res
->retries
= http_conn
->retries
;
122 res
->do_close
= NULL
;
128 http_conn_free(HttpConn
* http_conn
)
130 if (!http_conn
) return;
132 g_free(http_conn
->conn_type
);
133 g_free(http_conn
->host
);
134 g_free(http_conn
->url
);
135 g_free(http_conn
->body
);
136 g_free(http_conn
->content_type
);
138 if (http_conn
->sec_ctx
) {
139 sip_sec_destroy_context(http_conn
->sec_ctx
);
146 http_conn_auth_free(struct http_conn_auth
* auth
)
148 g_free(auth
->domain
);
150 g_free(auth
->password
);
155 http_conn_set_close(HttpConn
* http_conn
)
157 http_conn
->do_close
= http_conn
;
161 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
);
164 http_conn_close(HttpConn
*http_conn
, const char *message
)
166 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message
? message
: "");
168 http_conn_invalidate_ssl_connection(http_conn
);
169 http_conn_free(http_conn
);
173 * Extracts host, port and relative url
174 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
176 * Allocates memory, must be g_free'd.
179 http_conn_parse_url(const char *url
,
184 char **parts
= g_strsplit(url
, "://", 2);
185 char *no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
186 int port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
191 tmp
= strstr(no_proto
, "/");
192 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
193 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
196 parts
= g_strsplit(host_port
, ":", 2);
197 if (host
) *host
= g_strdup(parts
[0]);
198 if (port
) *port
= parts
[1] ? atoi(parts
[1]) : port_tmp
;
205 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
206 PurpleSslErrorType error
,
209 HttpConn
*http_conn
= data
;
210 const char *message
= NULL
;
212 http_conn
->gsc
= NULL
;
215 case PURPLE_SSL_CONNECT_FAILED
:
216 message
= "Connection failed";
218 case PURPLE_SSL_HANDSHAKE_FAILED
:
219 message
= "SSL handshake failed";
221 case PURPLE_SSL_CERTIFICATE_INVALID
:
222 message
= "SSL certificate invalid";
226 if (http_conn
->callback
) {
227 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
229 http_conn_close(http_conn
, message
);
233 http_conn_connection_remove(struct sip_connection
*conn
)
236 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
243 http_conn_invalidate_ssl_connection(HttpConn
*http_conn
)
246 PurpleSslConnection
*gsc
= http_conn
->gsc
;
248 /* Invalidate this connection. Next send will open a new one */
250 struct sip_connection
*conn
= http_conn
->conn
;
252 http_conn_connection_remove(conn
);
253 http_conn
->conn
= NULL
;
254 purple_ssl_close(gsc
);
256 http_conn
->gsc
= NULL
;
262 http_conn_process_input(HttpConn
*http_conn
);
265 http_conn_input_cb_ssl(gpointer data
,
266 PurpleSslConnection
*gsc
,
267 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
269 HttpConn
*http_conn
= data
;
270 struct sip_connection
*conn
= http_conn
? http_conn
->conn
: NULL
;
272 gboolean firstread
= TRUE
;
275 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
279 /* Read all available data from the SSL connection */
281 /* Increase input buffer size as needed */
282 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
283 conn
->inbuflen
+= SIMPLE_BUF_INC
;
284 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
285 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
288 /* Try to read as much as there is space left in the buffer */
289 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
290 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
292 if (len
< 0 && errno
== EAGAIN
) {
293 /* Try again later */
295 } else if (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
, "SSL read error");
301 } else if (firstread
&& (len
== 0)) {
302 if (http_conn
->callback
) {
303 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, http_conn
, http_conn
->data
);
305 http_conn_close(http_conn
, "Server has disconnected");
309 conn
->inbufused
+= len
;
312 /* Equivalence indicates that there is possibly more data to read */
313 } while (len
== readlen
);
315 conn
->inbuf
[conn
->inbufused
] = '\0';
316 http_conn_process_input(http_conn
);
319 http_conn_post0(HttpConn
*http_conn
,
320 const char *authorization
);
323 http_conn_input0_cb_ssl(gpointer data
,
324 PurpleSslConnection
*gsc
,
325 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
327 HttpConn
*http_conn
= data
;
329 http_conn
->fd
= gsc
->fd
;
330 http_conn
->gsc
= gsc
;
331 http_conn
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
332 //http_conn->connecting = FALSE;
333 http_conn
->last_keepalive
= time(NULL
);
335 http_conn
->conn
= g_new0(struct sip_connection
, 1);
337 purple_ssl_input_add(gsc
, http_conn_input_cb_ssl
, http_conn
);
339 http_conn_post0(http_conn
, NULL
);
343 http_conn_create(PurpleAccount
*account
,
344 const char *conn_type
,
345 const char *full_url
,
347 const char *content_type
,
349 HttpConnCallback callback
,
354 if (!full_url
|| (strlen(full_url
) == 0)) {
355 purple_debug_info("sipe-http", "no URL supplied!\n");
358 if (sipe_strequal(conn_type
, HTTP_CONN_SSL
) &&
359 !purple_ssl_is_supported())
361 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");
365 http_conn
= g_new0(HttpConn
, 1);
366 http_conn_parse_url(full_url
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
368 http_conn
->account
= account
;
369 http_conn
->conn_type
= g_strdup(conn_type
);
370 http_conn
->body
= g_strdup(body
);
371 http_conn
->content_type
= g_strdup(content_type
);
372 http_conn
->auth
= auth
;
373 http_conn
->callback
= callback
;
374 http_conn
->data
= data
;
376 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
, /* can we pass just NULL ? */
379 http_conn_input0_cb_ssl
,
380 http_conn_ssl_connect_failure
,
388 http_conn_process_input_message(HttpConn
*http_conn
,
392 http_conn_process_input(HttpConn
*http_conn
)
399 struct sip_connection
*conn
= http_conn
->conn
;
403 /* according to the RFC remove CRLF at the beginning */
404 while (*cur
== '\r' || *cur
== '\n') {
407 if (cur
!= conn
->inbuf
) {
408 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
409 conn
->inbufused
= strlen(conn
->inbuf
);
412 while ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
) {
413 time_t currtime
= time(NULL
);
416 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
419 msg
= sipmsg_parse_header(conn
->inbuf
);
422 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
423 if (msg
&& restlen
>= msg
->bodylen
) {
424 dummy
= g_malloc(msg
->bodylen
+ 1);
425 memcpy(dummy
, cur
, msg
->bodylen
);
426 dummy
[msg
->bodylen
] = '\0';
429 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
430 conn
->inbufused
= strlen(conn
->inbuf
);
433 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
440 purple_debug_info("sipe-http", "body:\n%s\n", msg
->body
);
443 http_conn_process_input_message(http_conn
, msg
);
448 if (http_conn
->do_close
) {
449 http_conn_close(http_conn
->do_close
, "User initiated");
454 http_conn_sendout_pkt(HttpConn
*http_conn
,
457 time_t currtime
= time(NULL
);
458 int writelen
= strlen(buf
);
462 purple_debug(PURPLE_DEBUG_MISC
, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
465 if (http_conn
->fd
< 0) {
466 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
470 if (http_conn
->gsc
) {
471 ret
= purple_ssl_write(http_conn
->gsc
, buf
, writelen
);
474 if (ret
< 0 && errno
== EAGAIN
)
476 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
477 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
481 if (ret
< writelen
) {
482 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
487 http_conn_post0(HttpConn
*http_conn
,
488 const char *authorization
)
490 GString
*outstr
= g_string_new("");
492 g_string_append_printf(outstr
, HTTP_CONN_POST_HEADER
,
495 http_conn
->body
? (int)strlen(http_conn
->body
) : 0,
496 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
498 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
500 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
502 http_conn_sendout_pkt(http_conn
, outstr
->str
);
503 g_string_free(outstr
, TRUE
);
507 http_conn_post( HttpConn
*http_conn
,
508 const char *full_url
,
510 const char *content_type
,
511 HttpConnCallback callback
,
515 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
519 g_free(http_conn
->url
);
520 g_free(http_conn
->body
);
521 g_free(http_conn
->content_type
);
522 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
523 http_conn
->body
= g_strdup(body
);
524 http_conn
->content_type
= g_strdup(content_type
);
525 http_conn
->callback
= callback
;
526 http_conn
->data
= data
;
528 http_conn_post0(http_conn
, NULL
);
532 http_conn_process_input_message(HttpConn
*http_conn
,
536 if (msg
->response
== 300 ||
537 msg
->response
== 301 ||
538 msg
->response
== 302 ||
539 msg
->response
== 307)
541 const char *location
= sipmsg_find_header(msg
, "Location");
543 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location
? location
: "");
545 http_conn
->do_close
= http_conn_clone(http_conn
);
546 http_conn
->sec_ctx
= NULL
;
548 g_free(http_conn
->host
);
549 g_free(http_conn
->url
);
550 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
552 http_conn
->gsc
= purple_ssl_connect(http_conn
->account
,
555 http_conn_input0_cb_ssl
,
556 http_conn_ssl_connect_failure
,
560 /* Authentication required */
561 else if (msg
->response
== 401) {
568 SipSecAuthType auth_type
;
569 const char *auth_name
;
571 char *output_toked_base64
;
572 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
575 http_conn
->retries
++;
576 if (http_conn
->retries
> 2) {
577 if (http_conn
->callback
) {
578 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
580 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
581 http_conn_set_close(http_conn
);
585 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
586 auth_type
= AUTH_TYPE_NTLM
;
590 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
591 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
593 auth_type
= AUTH_TYPE_NEGOTIATE
;
594 auth_name
= "Negotiate";
599 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
602 "NTLM and Negotiate authentications are"
603 #else /* !HAVE_KERBEROS */
604 "NTLM authentication is"
605 #endif /* HAVE_KERBEROS */
607 "NTLM authentication is"
613 if (!http_conn
->sec_ctx
) {
615 sip_sec_create_context(auth_type
,
618 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
619 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
620 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
623 if (http_conn
->sec_ctx
) {
624 char **parts
= g_strsplit(ptmp
, " ", 0);
625 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
626 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
629 &output_toked_base64
,
636 if (http_conn
->callback
) {
637 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, http_conn
, http_conn
->data
);
639 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
640 http_conn_set_close(http_conn
);
644 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
645 g_free(output_toked_base64
);
647 http_conn_post0(http_conn
, authorization
);
648 g_free(authorization
);
652 http_conn
->retries
= 0;
654 if (http_conn
->callback
) {
655 (*http_conn
->callback
)(msg
->response
, msg
->body
, http_conn
, http_conn
->data
);