6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 * Operates with HTTPS connection.
27 * Support Negotiate (Windows only) and NTLM authentications, redirect, cookie, GET/POST.
39 #include "http-conn.h"
42 #include "sipe-backend.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-utils.h"
49 * @param method (%s) Ex.: GET or POST
50 * @param url (%s) Ex.: /EWS/Exchange.asmx
51 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
53 #define HTTP_CONN_HEADER \
56 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"
59 struct http_conn_struct
{
60 struct sipe_core_public
*sipe_public
;
65 gboolean allow_redirect
;
71 gchar
*additional_headers
;
73 HttpConnCallback callback
;
76 struct sipe_transport_connection
*conn
;
78 SipSecContext sec_ctx
;
81 HttpSession
*http_session
;
83 /* if server sends "Connection: close" header */
87 #define HTTP_CONN ((HttpConn *) conn->user_data)
89 struct http_session_struct
{
94 http_conn_clone(HttpConn
* http_conn
)
96 HttpConn
*res
= g_new0(HttpConn
, 1);
98 res
->http_session
= http_conn
->http_session
;
99 res
->method
= g_strdup(http_conn
->method
);
100 res
->conn_type
= http_conn
->conn_type
;
101 res
->allow_redirect
= http_conn
->allow_redirect
;
102 res
->host
= g_strdup(http_conn
->host
);
103 res
->port
= http_conn
->port
;
104 res
->url
= g_strdup(http_conn
->url
);
105 res
->body
= g_strdup(http_conn
->body
);
106 res
->content_type
= g_strdup(http_conn
->content_type
);
107 res
->additional_headers
= g_strdup(http_conn
->additional_headers
);
108 res
->auth
= http_conn
->auth
;
109 res
->callback
= http_conn
->callback
;
110 res
->data
= http_conn
->data
;
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 /* make sure also pending connections are released */
127 sipe_backend_transport_disconnect(http_conn
->conn
);
129 /* don't free "http_conn->http_session" - client should do */
130 g_free(http_conn
->method
);
131 g_free(http_conn
->host
);
132 g_free(http_conn
->url
);
133 g_free(http_conn
->body
);
134 g_free(http_conn
->content_type
);
135 g_free(http_conn
->additional_headers
);
137 if (http_conn
->sec_ctx
) {
138 sip_sec_destroy_context(http_conn
->sec_ctx
);
145 http_conn_is_closed(HttpConn
*http_conn
)
147 return http_conn
->closed
;
151 http_conn_session_create()
153 HttpSession
*res
= g_new0(HttpSession
, 1);
158 http_conn_session_free(HttpSession
*http_session
)
160 if (!http_session
) return;
162 g_free(http_session
->cookie
);
163 g_free(http_session
);
167 http_conn_set_close(HttpConn
* http_conn
)
169 http_conn
->do_close
= http_conn
;
173 http_conn_close(HttpConn
*http_conn
, const char *message
)
175 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message
? message
: "");
176 http_conn_free(http_conn
);
180 * Extracts host, port and relative url
181 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
183 * Allocates memory, must be g_free'd.
186 http_conn_parse_url(const char *url
,
191 char **parts
= g_strsplit(url
, "://", 2);
197 /* Make sure we always return valid information */
198 if (host
) *host
= NULL
;
199 if (rel_url
) *rel_url
= NULL
;
203 } else if(!parts
[0]) {
208 no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
209 port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
216 tmp
= strstr(no_proto
, "/");
217 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
218 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
225 parts
= g_strsplit(host_port
, ":", 2);
228 if (host
) *host
= g_strdup(parts
[0]);
230 port_tmp
= parts
[1] ? (guint
) atoi(parts
[1]) : port_tmp
;
232 if (port
) *port
= port_tmp
;
239 static void http_conn_error(struct sipe_transport_connection
*conn
,
242 HttpConn
*http_conn
= HTTP_CONN
;
243 if (http_conn
->callback
) {
244 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, NULL
, http_conn
, http_conn
->data
);
246 http_conn_close(http_conn
, msg
);
249 static void http_conn_send0(HttpConn
*http_conn
,
250 const char *authorization
);
251 static void http_conn_connected(struct sipe_transport_connection
*conn
)
253 http_conn_send0(HTTP_CONN
, NULL
);
256 static void http_conn_input(struct sipe_transport_connection
*conn
);
257 static struct sipe_transport_connection
*http_conn_setup(HttpConn
*http_conn
,
258 struct sipe_core_public
*sipe_public
,
262 sipe_connect_setup setup
= {
273 http_conn_close(http_conn
, "Missing host");
277 return(sipe_backend_transport_connect(sipe_public
, &setup
));
281 http_conn_create(struct sipe_core_public
*sipe_public
,
282 HttpSession
*http_session
,
285 gboolean allow_redirect
,
286 const char *full_url
,
288 const char *content_type
,
289 const gchar
*additional_headers
,
291 HttpConnCallback callback
,
295 struct sipe_transport_connection
*conn
;
299 if (!full_url
|| (strlen(full_url
) == 0)) {
300 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
304 http_conn_parse_url(full_url
, &host
, &port
, &url
);
305 http_conn
= g_new0(HttpConn
, 1);
306 conn
= http_conn_setup(http_conn
, sipe_public
, conn_type
, host
, port
);
308 // http_conn_setup deallocates http_conn on error, don't free here
314 http_conn
->sipe_public
= sipe_public
;
315 conn
->user_data
= http_conn
;
317 http_conn
->http_session
= http_session
;
318 http_conn
->method
= g_strdup(method
);
319 http_conn
->conn_type
= conn_type
;
320 http_conn
->allow_redirect
= allow_redirect
;
321 http_conn
->host
= host
;
322 http_conn
->port
= port
;
323 http_conn
->url
= url
;
324 http_conn
->body
= g_strdup(body
);
325 http_conn
->content_type
= g_strdup(content_type
);
326 http_conn
->additional_headers
= g_strdup(additional_headers
);
327 http_conn
->auth
= auth
;
328 http_conn
->callback
= callback
;
329 http_conn
->data
= data
;
330 http_conn
->conn
= conn
;
337 http_conn_send0(HttpConn
*http_conn
,
338 const char *authorization
)
342 if (!http_conn
->host
|| !http_conn
->url
) return;
344 outstr
= g_string_new("");
345 g_string_append_printf(outstr
, HTTP_CONN_HEADER
,
346 http_conn
->method
? http_conn
->method
: "GET",
349 if (sipe_strequal(http_conn
->method
, "POST")) {
350 g_string_append_printf(outstr
, "Content-Length: %d\r\n",
351 http_conn
->body
? (int)strlen(http_conn
->body
) : 0);
353 g_string_append_printf(outstr
, "Content-Type: %s\r\n",
354 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
356 if (http_conn
->http_session
&& http_conn
->http_session
->cookie
) {
357 g_string_append_printf(outstr
, "Cookie: %s\r\n", http_conn
->http_session
->cookie
);
360 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
362 if (http_conn
->additional_headers
) {
363 g_string_append(outstr
, http_conn
->additional_headers
);
366 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
368 sipe_utils_message_debug("HTTP", outstr
->str
, NULL
, TRUE
);
369 sipe_backend_transport_message(http_conn
->conn
, outstr
->str
);
370 g_string_free(outstr
, TRUE
);
374 http_conn_send( HttpConn
*http_conn
,
376 const char *full_url
,
378 const char *content_type
,
379 HttpConnCallback callback
,
383 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
387 g_free(http_conn
->method
);
388 g_free(http_conn
->url
);
389 g_free(http_conn
->body
);
390 g_free(http_conn
->content_type
);
391 http_conn
->method
= g_strdup(method
);
392 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
393 http_conn
->body
= g_strdup(body
);
394 http_conn
->content_type
= g_strdup(content_type
);
395 http_conn
->callback
= callback
;
396 http_conn
->data
= data
;
398 http_conn_send0(http_conn
, NULL
);
402 http_conn_process_input_message(HttpConn
*http_conn
,
406 if ((msg
->response
== 300 ||
407 msg
->response
== 301 ||
408 msg
->response
== 302 ||
409 msg
->response
== 307) &&
410 http_conn
->allow_redirect
)
412 const char *location
= sipmsg_find_header(msg
, "Location");
416 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
417 http_conn_parse_url(location
, &host
, &port
, &url
);
420 http_conn
->do_close
= http_conn_clone(http_conn
);
421 http_conn
->sec_ctx
= NULL
;
423 g_free(http_conn
->host
);
424 g_free(http_conn
->url
);
426 http_conn
->host
= host
;
427 http_conn
->port
= port
;
428 http_conn
->url
= url
;
430 http_conn
->conn
= http_conn_setup(http_conn
,
431 http_conn
->sipe_public
,
432 http_conn
->conn_type
,
436 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
441 /* Authentication required */
442 else if (msg
->response
== 401) {
443 const gchar
*auth_hdr
= NULL
;
445 const char *auth_name
;
447 char *output_toked_base64
;
448 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
451 http_conn
->retries
++;
452 if (http_conn
->retries
> 2) {
453 if (http_conn
->callback
) {
454 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
456 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
457 http_conn_set_close(http_conn
);
462 if (http_conn
->auth
&& http_conn
->auth
->use_negotiate
)
463 auth_hdr
= sipmsg_find_auth_header(msg
, "Negotiate");
465 auth_type
= AUTH_TYPE_NEGOTIATE
;
466 auth_name
= "Negotiate";
469 auth_hdr
= sipmsg_find_auth_header(msg
, "NTLM");
470 auth_type
= AUTH_TYPE_NTLM
;
476 if (http_conn
->callback
) {
477 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
480 #define AUTHSTRING "NTLM and Negotiate authentications are"
481 #else /* !HAVE_SSPI */
482 #define AUTHSTRING "NTLM authentication is"
483 #endif /* HAVE_SSPI */
484 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
487 http_conn_set_close(http_conn
);
491 if (!http_conn
->sec_ctx
) {
493 sip_sec_create_context(auth_type
,
496 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
497 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
498 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
501 if (http_conn
->sec_ctx
) {
502 char **parts
= g_strsplit(auth_hdr
, " ", 0);
503 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
504 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
507 &output_toked_base64
,
514 if (http_conn
->callback
) {
515 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
517 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
518 http_conn_set_close(http_conn
);
522 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
523 g_free(output_toked_base64
);
525 http_conn_send0(http_conn
, authorization
);
526 g_free(authorization
);
530 const char *set_cookie_hdr
;
531 http_conn
->retries
= 0;
534 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
536 if (http_conn
->http_session
&& (set_cookie_hdr
= sipmsg_find_header(msg
, "Set-Cookie"))) {
541 g_free(http_conn
->http_session
->cookie
);
542 http_conn
->http_session
->cookie
= NULL
;
544 parts
= g_strsplit(set_cookie_hdr
, ";", 0);
545 for (i
= 0; parts
[i
]; i
++) {
546 if (!strstr(parts
[i
], "path=") &&
547 !strstr(parts
[i
], "domain=") &&
548 !strstr(parts
[i
], "expires=") &&
549 !strstr(parts
[i
], "secure"))
551 tmp
= http_conn
->http_session
->cookie
;
552 http_conn
->http_session
->cookie
= !tmp
?
554 g_strconcat(http_conn
->http_session
->cookie
, ";", parts
[i
], NULL
);
559 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
560 http_conn
->http_session
->cookie
? http_conn
->http_session
->cookie
: "");
563 if (http_conn
->callback
) {
564 (*http_conn
->callback
)(msg
->response
, msg
->body
, msg
->headers
, http_conn
, http_conn
->data
);
569 static void http_conn_input(struct sipe_transport_connection
*conn
)
571 HttpConn
*http_conn
= HTTP_CONN
;
572 char *cur
= conn
->buffer
;
574 /* according to the RFC remove CRLF at the beginning */
575 while (*cur
== '\r' || *cur
== '\n') {
578 if (cur
!= conn
->buffer
)
579 sipe_utils_shrink_buffer(conn
, cur
);
581 while ((cur
= strstr(conn
->buffer
, "\r\n\r\n")) != NULL
) {
587 msg
= sipmsg_parse_header(conn
->buffer
);
589 /* HTTP/1.1 Transfer-Encoding: chunked */
590 if (msg
&& (msg
->bodylen
== SIPMSG_BODYLEN_CHUNKED
)) {
591 gchar
*start
= cur
+ 2;
592 GSList
*chunks
= NULL
;
593 gboolean incomplete
= TRUE
;
596 while (strlen(start
) > 0) {
598 guint length
= strtol(start
, &tmp
, 16);
605 if ((length
== 0) && (start
== tmp
))
607 msg
->bodylen
+= length
;
609 /* Chunk header not finished yet */
610 tmp
= strstr(tmp
, "\r\n");
614 /* Chunk not finished yet */
616 remainder
= conn
->buffer_used
- (tmp
- conn
->buffer
);
617 if (remainder
< length
+ 2)
621 start
= tmp
+ length
+ 2;
625 gchar
*dummy
= g_malloc(msg
->bodylen
+ 1);
627 GSList
*entry
= chunks
;
631 memcpy(p
, chunk
->start
, chunk
->length
);
638 sipe_utils_message_debug("HTTP",
644 sipe_utils_shrink_buffer(conn
, cur
);
650 /* Append completed chunk */
651 chunk
= g_new0(struct _chunk
, 1);
652 chunk
->length
= length
;
654 chunks
= g_slist_append(chunks
, chunk
);
658 GSList
*entry
= chunks
;
663 g_slist_free(chunks
);
667 /* restore header for next try */
675 remainder
= conn
->buffer_used
- (cur
- conn
->buffer
);
676 if (msg
&& remainder
>= (guint
) msg
->bodylen
) {
677 char *dummy
= g_malloc(msg
->bodylen
+ 1);
678 memcpy(dummy
, cur
, msg
->bodylen
);
679 dummy
[msg
->bodylen
] = '\0';
682 sipe_utils_message_debug("HTTP",
686 sipe_utils_shrink_buffer(conn
, cur
);
689 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder
, msg
->bodylen
, (int)strlen(conn
->buffer
));
693 /* restore header for next try */
699 /* important to set before callback call */
700 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
701 http_conn
->closed
= TRUE
;
704 http_conn_process_input_message(http_conn
, msg
);
709 if (http_conn
->closed
) {
710 http_conn_close(http_conn
->do_close
, "Server closed connection");
711 } else if (http_conn
->do_close
) {
712 http_conn_close(http_conn
->do_close
, "User initiated");