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 const 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
->auth
= http_conn
->auth
;
108 res
->callback
= http_conn
->callback
;
109 res
->data
= http_conn
->data
;
111 res
->conn
= http_conn
->conn
;
112 res
->sec_ctx
= http_conn
->sec_ctx
;
113 res
->retries
= http_conn
->retries
;
115 res
->do_close
= NULL
;
121 http_conn_free(HttpConn
* http_conn
)
123 if (!http_conn
) return;
125 /* don't free "http_conn->http_session" - client should do */
126 g_free(http_conn
->method
);
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_is_closed(HttpConn
*http_conn
)
142 return http_conn
->closed
;
146 http_conn_session_create()
148 HttpSession
*res
= g_new0(HttpSession
, 1);
153 http_conn_session_free(HttpSession
*http_session
)
155 if (!http_session
) return;
157 g_free(http_session
->cookie
);
158 g_free(http_session
);
162 http_conn_auth_free(struct http_conn_auth
* auth
)
164 g_free(auth
->domain
);
166 g_free(auth
->password
);
171 http_conn_set_close(HttpConn
* http_conn
)
173 http_conn
->do_close
= http_conn
;
177 http_conn_close(HttpConn
*http_conn
, const char *message
)
179 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message
? message
: "");
181 g_return_if_fail(http_conn
);
183 sipe_backend_transport_disconnect(http_conn
->conn
);
184 http_conn_free(http_conn
);
188 * Extracts host, port and relative url
189 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
191 * Allocates memory, must be g_free'd.
194 http_conn_parse_url(const char *url
,
199 char **parts
= g_strsplit(url
, "://", 2);
205 /* Make sure we always return valid information */
206 if (host
) *host
= NULL
;
207 if (rel_url
) *rel_url
= NULL
;
211 } else if(!parts
[0]) {
216 no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
217 port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
224 tmp
= strstr(no_proto
, "/");
225 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
226 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
233 parts
= g_strsplit(host_port
, ":", 2);
236 if (host
) *host
= g_strdup(parts
[0]);
238 port_tmp
= parts
[1] ? (guint
) atoi(parts
[1]) : port_tmp
;
240 if (port
) *port
= port_tmp
;
247 static void http_conn_error(struct sipe_transport_connection
*conn
,
250 HttpConn
*http_conn
= HTTP_CONN
;
251 if (http_conn
->callback
) {
252 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, NULL
, http_conn
, http_conn
->data
);
254 http_conn_close(http_conn
, msg
);
257 static void http_conn_send0(HttpConn
*http_conn
,
258 const char *authorization
);
259 static void http_conn_connected(struct sipe_transport_connection
*conn
)
261 http_conn_send0(HTTP_CONN
, NULL
);
264 static void http_conn_input(struct sipe_transport_connection
*conn
);
265 static struct sipe_transport_connection
*http_conn_setup(HttpConn
*http_conn
,
266 struct sipe_core_public
*sipe_public
,
270 sipe_connect_setup setup
= {
281 http_conn_close(http_conn
, "Missing host");
285 return(sipe_backend_transport_connect(sipe_public
, &setup
));
289 http_conn_create(struct sipe_core_public
*sipe_public
,
290 HttpSession
*http_session
,
293 gboolean allow_redirect
,
294 const char *full_url
,
296 const char *content_type
,
297 const gchar
*additional_headers
,
299 HttpConnCallback callback
,
303 struct sipe_transport_connection
*conn
;
307 if (!full_url
|| (strlen(full_url
) == 0)) {
308 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
312 http_conn_parse_url(full_url
, &host
, &port
, &url
);
313 http_conn
= g_new0(HttpConn
, 1);
314 conn
= http_conn_setup(http_conn
, sipe_public
, conn_type
, host
, port
);
316 // http_conn_setup deallocates http_conn on error, don't free here
322 http_conn
->sipe_public
= sipe_public
;
323 conn
->user_data
= http_conn
;
325 http_conn
->http_session
= http_session
;
326 http_conn
->method
= g_strdup(method
);
327 http_conn
->conn_type
= conn_type
;
328 http_conn
->allow_redirect
= allow_redirect
;
329 http_conn
->host
= host
;
330 http_conn
->port
= port
;
331 http_conn
->url
= url
;
332 http_conn
->body
= g_strdup(body
);
333 http_conn
->content_type
= g_strdup(content_type
);
334 http_conn
->additional_headers
= additional_headers
;
335 http_conn
->auth
= auth
;
336 http_conn
->callback
= callback
;
337 http_conn
->data
= data
;
338 http_conn
->conn
= conn
;
345 http_conn_send0(HttpConn
*http_conn
,
346 const char *authorization
)
350 if (!http_conn
->host
|| !http_conn
->url
) return;
352 outstr
= g_string_new("");
353 g_string_append_printf(outstr
, HTTP_CONN_HEADER
,
354 http_conn
->method
? http_conn
->method
: "GET",
357 if (sipe_strequal(http_conn
->method
, "POST")) {
358 g_string_append_printf(outstr
, "Content-Length: %d\r\n",
359 http_conn
->body
? (int)strlen(http_conn
->body
) : 0);
361 g_string_append_printf(outstr
, "Content-Type: %s\r\n",
362 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
364 if (http_conn
->http_session
&& http_conn
->http_session
->cookie
) {
365 g_string_append_printf(outstr
, "Cookie: %s\r\n", http_conn
->http_session
->cookie
);
368 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
370 if (http_conn
->additional_headers
) {
371 g_string_append(outstr
, http_conn
->additional_headers
);
374 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
376 sipe_utils_message_debug("HTTP", outstr
->str
, NULL
, TRUE
);
377 sipe_backend_transport_message(http_conn
->conn
, outstr
->str
);
378 g_string_free(outstr
, TRUE
);
382 http_conn_send( HttpConn
*http_conn
,
384 const char *full_url
,
386 const char *content_type
,
387 HttpConnCallback callback
,
391 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
395 g_free(http_conn
->method
);
396 g_free(http_conn
->url
);
397 g_free(http_conn
->body
);
398 g_free(http_conn
->content_type
);
399 http_conn
->method
= g_strdup(method
);
400 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
401 http_conn
->body
= g_strdup(body
);
402 http_conn
->content_type
= g_strdup(content_type
);
403 http_conn
->callback
= callback
;
404 http_conn
->data
= data
;
406 http_conn_send0(http_conn
, NULL
);
410 http_conn_process_input_message(HttpConn
*http_conn
,
414 if ((msg
->response
== 300 ||
415 msg
->response
== 301 ||
416 msg
->response
== 302 ||
417 msg
->response
== 307) &&
418 http_conn
->allow_redirect
)
420 const char *location
= sipmsg_find_header(msg
, "Location");
424 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
425 http_conn_parse_url(location
, &host
, &port
, &url
);
428 http_conn
->do_close
= http_conn_clone(http_conn
);
429 http_conn
->sec_ctx
= NULL
;
431 g_free(http_conn
->host
);
432 g_free(http_conn
->url
);
434 http_conn
->host
= host
;
435 http_conn
->port
= port
;
436 http_conn
->url
= url
;
438 http_conn
->conn
= http_conn_setup(http_conn
,
439 http_conn
->sipe_public
,
440 http_conn
->conn_type
,
444 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
449 /* Authentication required */
450 else if (msg
->response
== 401) {
451 const gchar
*auth_hdr
= NULL
;
453 const char *auth_name
;
455 char *output_toked_base64
;
456 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
459 http_conn
->retries
++;
460 if (http_conn
->retries
> 2) {
461 if (http_conn
->callback
) {
462 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
464 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
465 http_conn_set_close(http_conn
);
470 if (http_conn
->auth
&& http_conn
->auth
->use_negotiate
)
471 auth_hdr
= sipmsg_find_auth_header(msg
, "Negotiate");
473 auth_type
= AUTH_TYPE_NEGOTIATE
;
474 auth_name
= "Negotiate";
477 auth_hdr
= sipmsg_find_auth_header(msg
, "NTLM");
478 auth_type
= AUTH_TYPE_NTLM
;
484 if (http_conn
->callback
) {
485 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
488 #define AUTHSTRING "NTLM and Negotiate authentications are"
489 #else /* !HAVE_SSPI */
490 #define AUTHSTRING "NTLM authentication is"
491 #endif /* HAVE_SSPI */
492 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
495 http_conn_set_close(http_conn
);
499 if (!http_conn
->sec_ctx
) {
501 sip_sec_create_context(auth_type
,
504 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
505 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
506 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
509 if (http_conn
->sec_ctx
) {
510 char **parts
= g_strsplit(auth_hdr
, " ", 0);
511 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
512 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
515 &output_toked_base64
,
522 if (http_conn
->callback
) {
523 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
525 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
526 http_conn_set_close(http_conn
);
530 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
531 g_free(output_toked_base64
);
533 http_conn_send0(http_conn
, authorization
);
534 g_free(authorization
);
538 const char *set_cookie_hdr
;
539 const char *content_type
= sipmsg_find_header(msg
, "Content-Type");
540 http_conn
->retries
= 0;
543 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
545 if (http_conn
->http_session
&& (set_cookie_hdr
= sipmsg_find_header(msg
, "Set-Cookie"))) {
550 g_free(http_conn
->http_session
->cookie
);
551 http_conn
->http_session
->cookie
= NULL
;
553 parts
= g_strsplit(set_cookie_hdr
, ";", 0);
554 for (i
= 0; parts
[i
]; i
++) {
555 if (!strstr(parts
[i
], "path=") &&
556 !strstr(parts
[i
], "domain=") &&
557 !strstr(parts
[i
], "expires=") &&
558 !strstr(parts
[i
], "secure"))
560 tmp
= http_conn
->http_session
->cookie
;
561 http_conn
->http_session
->cookie
= !tmp
?
563 g_strconcat(http_conn
->http_session
->cookie
, ";", parts
[i
], NULL
);
568 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
569 http_conn
->http_session
->cookie
? http_conn
->http_session
->cookie
: "");
572 if (http_conn
->callback
) {
573 (*http_conn
->callback
)(msg
->response
, msg
->body
, content_type
, http_conn
, http_conn
->data
);
578 static void http_conn_input(struct sipe_transport_connection
*conn
)
580 HttpConn
*http_conn
= HTTP_CONN
;
581 char *cur
= conn
->buffer
;
583 /* according to the RFC remove CRLF at the beginning */
584 while (*cur
== '\r' || *cur
== '\n') {
587 if (cur
!= conn
->buffer
)
588 sipe_utils_shrink_buffer(conn
, cur
);
590 while ((cur
= strstr(conn
->buffer
, "\r\n\r\n")) != NULL
) {
596 msg
= sipmsg_parse_header(conn
->buffer
);
598 /* HTTP/1.1 Transfer-Encoding: chunked */
599 if (msg
&& (msg
->bodylen
== SIPMSG_BODYLEN_CHUNKED
)) {
600 gchar
*start
= cur
+ 2;
601 GSList
*chunks
= NULL
;
602 gboolean incomplete
= TRUE
;
605 while (strlen(start
) > 0) {
607 guint length
= strtol(start
, &tmp
, 16);
614 if ((length
== 0) && (start
== tmp
))
616 msg
->bodylen
+= length
;
618 /* Chunk header not finished yet */
619 tmp
= strstr(tmp
, "\r\n");
623 /* Chunk not finished yet */
625 remainder
= conn
->buffer_used
- (tmp
- conn
->buffer
);
626 if (remainder
< length
+ 2)
630 start
= tmp
+ length
+ 2;
634 gchar
*dummy
= g_malloc(msg
->bodylen
+ 1);
636 GSList
*entry
= chunks
;
640 memcpy(p
, chunk
->start
, chunk
->length
);
647 sipe_utils_message_debug("HTTP",
653 sipe_utils_shrink_buffer(conn
, cur
);
659 /* Append completed chunk */
660 chunk
= g_new0(struct _chunk
, 1);
661 chunk
->length
= length
;
663 chunks
= g_slist_append(chunks
, chunk
);
667 GSList
*entry
= chunks
;
672 g_slist_free(chunks
);
676 /* restore header for next try */
684 remainder
= conn
->buffer_used
- (cur
- conn
->buffer
);
685 if (msg
&& remainder
>= (guint
) msg
->bodylen
) {
686 char *dummy
= g_malloc(msg
->bodylen
+ 1);
687 memcpy(dummy
, cur
, msg
->bodylen
);
688 dummy
[msg
->bodylen
] = '\0';
691 sipe_utils_message_debug("HTTP",
695 sipe_utils_shrink_buffer(conn
, cur
);
698 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder
, msg
->bodylen
, (int)strlen(conn
->buffer
));
702 /* restore header for next try */
708 /* important to set before callback call */
709 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
710 http_conn
->closed
= TRUE
;
713 http_conn_process_input_message(http_conn
, msg
);
718 if (http_conn
->closed
) {
719 http_conn_close(http_conn
->do_close
, "Server closed connection");
720 } else if (http_conn
->do_close
) {
721 http_conn_close(http_conn
->do_close
, "User initiated");