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
;
72 HttpConnCallback callback
;
75 struct sipe_transport_connection
*conn
;
77 SipSecContext sec_ctx
;
80 HttpSession
*http_session
;
82 /* if server sends "Connection: close" header */
86 #define HTTP_CONN ((HttpConn *) conn->user_data)
88 struct http_session_struct
{
93 http_conn_clone(HttpConn
* http_conn
)
95 HttpConn
*res
= g_new0(HttpConn
, 1);
97 res
->http_session
= http_conn
->http_session
;
98 res
->method
= g_strdup(http_conn
->method
);
99 res
->conn_type
= http_conn
->conn_type
;
100 res
->allow_redirect
= http_conn
->allow_redirect
;
101 res
->host
= g_strdup(http_conn
->host
);
102 res
->port
= http_conn
->port
;
103 res
->url
= g_strdup(http_conn
->url
);
104 res
->body
= g_strdup(http_conn
->body
);
105 res
->content_type
= g_strdup(http_conn
->content_type
);
106 res
->auth
= http_conn
->auth
;
107 res
->callback
= http_conn
->callback
;
108 res
->data
= http_conn
->data
;
110 res
->conn
= http_conn
->conn
;
111 res
->sec_ctx
= http_conn
->sec_ctx
;
112 res
->retries
= http_conn
->retries
;
114 res
->do_close
= NULL
;
120 http_conn_free(HttpConn
* http_conn
)
122 if (!http_conn
) return;
124 /* don't free "http_conn->http_session" - client should do */
125 g_free(http_conn
->method
);
126 g_free(http_conn
->host
);
127 g_free(http_conn
->url
);
128 g_free(http_conn
->body
);
129 g_free(http_conn
->content_type
);
131 if (http_conn
->sec_ctx
) {
132 sip_sec_destroy_context(http_conn
->sec_ctx
);
139 http_conn_is_closed(HttpConn
*http_conn
)
141 return http_conn
->closed
;
145 http_conn_session_create()
147 HttpSession
*res
= g_new0(HttpSession
, 1);
152 http_conn_session_free(HttpSession
*http_session
)
154 if (!http_session
) return;
156 g_free(http_session
->cookie
);
157 g_free(http_session
);
161 http_conn_auth_free(struct http_conn_auth
* auth
)
163 g_free(auth
->domain
);
165 g_free(auth
->password
);
170 http_conn_set_close(HttpConn
* http_conn
)
172 http_conn
->do_close
= http_conn
;
176 http_conn_close(HttpConn
*http_conn
, const char *message
)
178 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message
? message
: "");
180 g_return_if_fail(http_conn
);
182 sipe_backend_transport_disconnect(http_conn
->conn
);
183 http_conn_free(http_conn
);
187 * Extracts host, port and relative url
188 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
190 * Allocates memory, must be g_free'd.
193 http_conn_parse_url(const char *url
,
198 char **parts
= g_strsplit(url
, "://", 2);
204 /* Make sure we always return valid information */
205 if (host
) *host
= NULL
;
206 if (rel_url
) *rel_url
= NULL
;
210 } else if(!parts
[0]) {
215 no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
216 port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
223 tmp
= strstr(no_proto
, "/");
224 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
225 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
232 parts
= g_strsplit(host_port
, ":", 2);
235 if (host
) *host
= g_strdup(parts
[0]);
237 port_tmp
= parts
[1] ? (guint
) atoi(parts
[1]) : port_tmp
;
239 if (port
) *port
= port_tmp
;
246 static void http_conn_error(struct sipe_transport_connection
*conn
,
249 HttpConn
*http_conn
= HTTP_CONN
;
250 if (http_conn
->callback
) {
251 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, NULL
, http_conn
, http_conn
->data
);
253 http_conn_close(http_conn
, msg
);
256 static void http_conn_send0(HttpConn
*http_conn
,
257 const char *authorization
);
258 static void http_conn_connected(struct sipe_transport_connection
*conn
)
260 http_conn_send0(HTTP_CONN
, NULL
);
263 static void http_conn_input(struct sipe_transport_connection
*conn
);
264 static struct sipe_transport_connection
*http_conn_setup(HttpConn
*http_conn
,
265 struct sipe_core_public
*sipe_public
,
269 sipe_connect_setup setup
= {
280 http_conn_close(http_conn
, "Missing host");
284 return(sipe_backend_transport_connect(sipe_public
, &setup
));
288 http_conn_create(struct sipe_core_public
*sipe_public
,
289 HttpSession
*http_session
,
292 gboolean allow_redirect
,
293 const char *full_url
,
295 const char *content_type
,
297 HttpConnCallback callback
,
301 struct sipe_transport_connection
*conn
;
305 if (!full_url
|| (strlen(full_url
) == 0)) {
306 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
310 http_conn_parse_url(full_url
, &host
, &port
, &url
);
311 http_conn
= g_new0(HttpConn
, 1);
312 conn
= http_conn_setup(http_conn
, sipe_public
, conn_type
, host
, port
);
314 // http_conn_setup deallocates http_conn on error, don't free here
320 http_conn
->sipe_public
= sipe_public
;
321 conn
->user_data
= http_conn
;
323 http_conn
->http_session
= http_session
;
324 http_conn
->method
= g_strdup(method
);
325 http_conn
->conn_type
= conn_type
;
326 http_conn
->allow_redirect
= allow_redirect
;
327 http_conn
->host
= host
;
328 http_conn
->port
= port
;
329 http_conn
->url
= url
;
330 http_conn
->body
= g_strdup(body
);
331 http_conn
->content_type
= g_strdup(content_type
);
332 http_conn
->auth
= auth
;
333 http_conn
->callback
= callback
;
334 http_conn
->data
= data
;
335 http_conn
->conn
= conn
;
342 http_conn_send0(HttpConn
*http_conn
,
343 const char *authorization
)
347 if (!http_conn
->host
|| !http_conn
->url
) return;
349 outstr
= g_string_new("");
350 g_string_append_printf(outstr
, HTTP_CONN_HEADER
,
351 http_conn
->method
? http_conn
->method
: "GET",
354 if (sipe_strequal(http_conn
->method
, "POST")) {
355 g_string_append_printf(outstr
, "Content-Length: %d\r\n",
356 http_conn
->body
? (int)strlen(http_conn
->body
) : 0);
358 g_string_append_printf(outstr
, "Content-Type: %s\r\n",
359 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
361 if (http_conn
->http_session
&& http_conn
->http_session
->cookie
) {
362 g_string_append_printf(outstr
, "Cookie: %s\r\n", http_conn
->http_session
->cookie
);
365 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
367 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
369 sipe_utils_message_debug("HTTP", outstr
->str
, NULL
, TRUE
);
370 sipe_backend_transport_message(http_conn
->conn
, outstr
->str
);
371 g_string_free(outstr
, TRUE
);
375 http_conn_send( HttpConn
*http_conn
,
377 const char *full_url
,
379 const char *content_type
,
380 HttpConnCallback callback
,
384 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
388 g_free(http_conn
->method
);
389 g_free(http_conn
->url
);
390 g_free(http_conn
->body
);
391 g_free(http_conn
->content_type
);
392 http_conn
->method
= g_strdup(method
);
393 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
394 http_conn
->body
= g_strdup(body
);
395 http_conn
->content_type
= g_strdup(content_type
);
396 http_conn
->callback
= callback
;
397 http_conn
->data
= data
;
399 http_conn_send0(http_conn
, NULL
);
403 http_conn_process_input_message(HttpConn
*http_conn
,
407 if ((msg
->response
== 300 ||
408 msg
->response
== 301 ||
409 msg
->response
== 302 ||
410 msg
->response
== 307) &&
411 http_conn
->allow_redirect
)
413 const char *location
= sipmsg_find_header(msg
, "Location");
417 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
418 http_conn_parse_url(location
, &host
, &port
, &url
);
421 http_conn
->do_close
= http_conn_clone(http_conn
);
422 http_conn
->sec_ctx
= NULL
;
424 g_free(http_conn
->host
);
425 g_free(http_conn
->url
);
427 http_conn
->host
= host
;
428 http_conn
->port
= port
;
429 http_conn
->url
= url
;
431 http_conn
->conn
= http_conn_setup(http_conn
,
432 http_conn
->sipe_public
,
433 http_conn
->conn_type
,
437 SIPE_DEBUG_ERROR_NOFORMAT("http_conn_process_input_message: no redirect host");
442 /* Authentication required */
443 else if (msg
->response
== 401) {
451 const char *auth_name
;
453 char *output_toked_base64
;
454 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
457 http_conn
->retries
++;
458 if (http_conn
->retries
> 2) {
459 if (http_conn
->callback
) {
460 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
462 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
463 http_conn_set_close(http_conn
);
467 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
468 auth_type
= AUTH_TYPE_NTLM
;
472 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
473 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
475 auth_type
= AUTH_TYPE_NEGOTIATE
;
476 auth_name
= "Negotiate";
481 if (http_conn
->callback
) {
482 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
486 #define AUTHSTRING "NTLM and Negotiate authentications are"
487 #else /* !HAVE_LIBKRB5 */
488 #define AUTHSTRING "NTLM authentication is"
489 #endif /* HAVE_LIBKRB5 */
491 #define AUTHSTRING "NTLM authentication is"
493 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
496 http_conn_set_close(http_conn
);
500 if (!http_conn
->sec_ctx
) {
502 sip_sec_create_context(auth_type
,
505 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
506 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
507 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
510 if (http_conn
->sec_ctx
) {
511 char **parts
= g_strsplit(ptmp
, " ", 0);
512 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
513 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
516 &output_toked_base64
,
523 if (http_conn
->callback
) {
524 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
526 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
527 http_conn_set_close(http_conn
);
531 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
532 g_free(output_toked_base64
);
534 http_conn_send0(http_conn
, authorization
);
535 g_free(authorization
);
539 const char *set_cookie_hdr
;
540 const char *content_type
= sipmsg_find_header(msg
, "Content-Type");
541 http_conn
->retries
= 0;
544 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
546 if (http_conn
->http_session
&& (set_cookie_hdr
= sipmsg_find_header(msg
, "Set-Cookie"))) {
551 g_free(http_conn
->http_session
->cookie
);
552 http_conn
->http_session
->cookie
= NULL
;
554 parts
= g_strsplit(set_cookie_hdr
, ";", 0);
555 for (i
= 0; parts
[i
]; i
++) {
556 if (!strstr(parts
[i
], "path=") &&
557 !strstr(parts
[i
], "domain=") &&
558 !strstr(parts
[i
], "expires=") &&
559 !strstr(parts
[i
], "secure"))
561 tmp
= http_conn
->http_session
->cookie
;
562 http_conn
->http_session
->cookie
= !tmp
?
564 g_strconcat(http_conn
->http_session
->cookie
, ";", parts
[i
], NULL
);
569 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
570 http_conn
->http_session
->cookie
? http_conn
->http_session
->cookie
: "");
573 if (http_conn
->callback
) {
574 (*http_conn
->callback
)(msg
->response
, msg
->body
, content_type
, http_conn
, http_conn
->data
);
579 static void http_conn_input(struct sipe_transport_connection
*conn
)
581 HttpConn
*http_conn
= HTTP_CONN
;
582 char *cur
= conn
->buffer
;
584 /* according to the RFC remove CRLF at the beginning */
585 while (*cur
== '\r' || *cur
== '\n') {
588 if (cur
!= conn
->buffer
)
589 sipe_utils_shrink_buffer(conn
, cur
);
591 while ((cur
= strstr(conn
->buffer
, "\r\n\r\n")) != NULL
) {
597 msg
= sipmsg_parse_header(conn
->buffer
);
600 remainder
= conn
->buffer_used
- (cur
- conn
->buffer
);
601 if (msg
&& remainder
>= (guint
) msg
->bodylen
) {
602 char *dummy
= g_malloc(msg
->bodylen
+ 1);
603 memcpy(dummy
, cur
, msg
->bodylen
);
604 dummy
[msg
->bodylen
] = '\0';
607 sipe_utils_message_debug("HTTP",
611 sipe_utils_shrink_buffer(conn
, cur
);
614 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder
, msg
->bodylen
, (int)strlen(conn
->buffer
));
618 /* restore header for next try */
623 /* important to set before callback call */
624 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
625 http_conn
->closed
= TRUE
;
628 http_conn_process_input_message(http_conn
, msg
);
633 if (http_conn
->closed
) {
634 http_conn_close(http_conn
->do_close
, "Server closed connection");
635 } else if (http_conn
->do_close
) {
636 http_conn_close(http_conn
->do_close
, "User initiated");