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_http_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);
206 } else if(!parts
[0]) {
211 no_proto
= parts
[1] ? g_strdup(parts
[1]) : g_strdup(parts
[0]);
212 port_tmp
= sipe_strequal(parts
[0], "https") ? 443 : 80;
219 tmp
= strstr(no_proto
, "/");
220 if (tmp
&& rel_url
) *rel_url
= g_strdup(tmp
);
221 host_port
= tmp
? g_strndup(no_proto
, tmp
- no_proto
) : g_strdup(no_proto
);
228 parts
= g_strsplit(host_port
, ":", 2);
231 if (host
) *host
= g_strdup(parts
[0]);
233 port_tmp
= parts
[1] ? (guint
) atoi(parts
[1]) : port_tmp
;
235 if (port
) *port
= port_tmp
;
242 static void http_conn_transport_error(HttpConn
*http_conn
, const gchar
*msg
)
244 if (http_conn
->callback
) {
245 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, NULL
, http_conn
, http_conn
->data
);
247 http_conn_close(http_conn
, msg
);
250 void sipe_core_transport_http_input_error(struct sipe_transport_connection
*conn
,
253 http_conn_transport_error(HTTP_CONN
, msg
);
258 http_conn_create(struct sipe_core_public
*sipe_public
,
259 HttpSession
*http_session
,
262 gboolean allow_redirect
,
263 const char *full_url
,
265 const char *content_type
,
267 HttpConnCallback callback
,
271 struct sipe_transport_connection
*conn
;
275 if (!full_url
|| (strlen(full_url
) == 0)) {
276 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
280 http_conn_parse_url(full_url
, &host
, &port
, &url
);
281 conn
= sipe_backend_transport_http_connect(sipe_public
,
291 http_conn
= g_new0(HttpConn
, 1);
293 http_conn
->sipe_public
= sipe_public
;
294 conn
->user_data
= http_conn
;
296 http_conn
->http_session
= http_session
;
297 http_conn
->method
= g_strdup(method
);
298 http_conn
->conn_type
= conn_type
;
299 http_conn
->allow_redirect
= allow_redirect
;
300 http_conn
->host
= host
;
301 http_conn
->port
= port
;
302 http_conn
->url
= url
;
303 http_conn
->body
= g_strdup(body
);
304 http_conn
->content_type
= g_strdup(content_type
);
305 http_conn
->auth
= auth
;
306 http_conn
->callback
= callback
;
307 http_conn
->data
= data
;
308 http_conn
->conn
= conn
;
315 http_conn_send0(HttpConn
*http_conn
,
316 const char *authorization
)
318 GString
*outstr
= g_string_new("");
320 g_string_append_printf(outstr
, HTTP_CONN_HEADER
,
321 http_conn
->method
? http_conn
->method
: "GET",
324 if (sipe_strequal(http_conn
->method
, "POST")) {
325 g_string_append_printf(outstr
, "Content-Length: %d\r\n",
326 http_conn
->body
? (int)strlen(http_conn
->body
) : 0);
328 g_string_append_printf(outstr
, "Content-Type: %s\r\n",
329 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
331 if (http_conn
->http_session
&& http_conn
->http_session
->cookie
) {
332 g_string_append_printf(outstr
, "Cookie: %s\r\n", http_conn
->http_session
->cookie
);
335 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
337 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
339 sipe_backend_transport_http_message(http_conn
->conn
, outstr
->str
);
340 g_string_free(outstr
, TRUE
);
343 void sipe_core_transport_http_connected(struct sipe_transport_connection
*conn
)
345 http_conn_send0(HTTP_CONN
, NULL
);
349 http_conn_send( HttpConn
*http_conn
,
351 const char *full_url
,
353 const char *content_type
,
354 HttpConnCallback callback
,
358 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
362 g_free(http_conn
->method
);
363 g_free(http_conn
->url
);
364 g_free(http_conn
->body
);
365 g_free(http_conn
->content_type
);
366 http_conn
->method
= g_strdup(method
);
367 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
368 http_conn
->body
= g_strdup(body
);
369 http_conn
->content_type
= g_strdup(content_type
);
370 http_conn
->callback
= callback
;
371 http_conn
->data
= data
;
373 http_conn_send0(http_conn
, NULL
);
377 http_conn_process_input_message(HttpConn
*http_conn
,
381 if ((msg
->response
== 300 ||
382 msg
->response
== 301 ||
383 msg
->response
== 302 ||
384 msg
->response
== 307) &&
385 http_conn
->allow_redirect
)
387 const char *location
= sipmsg_find_header(msg
, "Location");
389 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
391 http_conn
->do_close
= http_conn_clone(http_conn
);
392 http_conn
->sec_ctx
= NULL
;
394 g_free(http_conn
->host
);
395 g_free(http_conn
->url
);
396 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
398 http_conn
->conn
= sipe_backend_transport_http_connect(http_conn
->sipe_public
,
399 http_conn
->conn_type
,
403 /* Authentication required */
404 else if (msg
->response
== 401) {
412 const char *auth_name
;
414 char *output_toked_base64
;
415 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
418 http_conn
->retries
++;
419 if (http_conn
->retries
> 2) {
420 if (http_conn
->callback
) {
421 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
423 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
424 http_conn_set_close(http_conn
);
428 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
429 auth_type
= AUTH_TYPE_NTLM
;
433 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
434 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
436 auth_type
= AUTH_TYPE_NEGOTIATE
;
437 auth_name
= "Negotiate";
442 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
445 "NTLM and Negotiate authentications are"
446 #else /* !HAVE_LIBKRB5 */
447 "NTLM authentication is"
448 #endif /* HAVE_LIBKRB5 */
450 "NTLM authentication is"
456 if (!http_conn
->sec_ctx
) {
458 sip_sec_create_context(auth_type
,
461 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
462 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
463 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
466 if (http_conn
->sec_ctx
) {
467 char **parts
= g_strsplit(ptmp
, " ", 0);
468 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
469 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
472 &output_toked_base64
,
479 if (http_conn
->callback
) {
480 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
482 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
483 http_conn_set_close(http_conn
);
487 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
488 g_free(output_toked_base64
);
490 http_conn_send0(http_conn
, authorization
);
491 g_free(authorization
);
495 const char *set_cookie_hdr
;
496 const char *content_type
= sipmsg_find_header(msg
, "Content-Type");
497 http_conn
->retries
= 0;
500 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
502 if (http_conn
->http_session
&& (set_cookie_hdr
= sipmsg_find_header(msg
, "Set-Cookie"))) {
507 g_free(http_conn
->http_session
->cookie
);
508 http_conn
->http_session
->cookie
= NULL
;
510 parts
= g_strsplit(set_cookie_hdr
, ";", 0);
511 for (i
= 0; parts
[i
]; i
++) {
512 if (!strstr(parts
[i
], "path=") &&
513 !strstr(parts
[i
], "domain=") &&
514 !strstr(parts
[i
], "expires=") &&
515 !strstr(parts
[i
], "secure"))
517 tmp
= http_conn
->http_session
->cookie
;
518 http_conn
->http_session
->cookie
= !tmp
?
520 g_strconcat(http_conn
->http_session
->cookie
, ";", parts
[i
], NULL
);
525 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
526 http_conn
->http_session
->cookie
? http_conn
->http_session
->cookie
: "");
529 if (http_conn
->callback
) {
530 (*http_conn
->callback
)(msg
->response
, msg
->body
, content_type
, http_conn
, http_conn
->data
);
535 void sipe_core_transport_http_message(struct sipe_transport_connection
*conn
)
537 HttpConn
*http_conn
= HTTP_CONN
;
538 char *cur
= conn
->buffer
;
540 /* according to the RFC remove CRLF at the beginning */
541 while (*cur
== '\r' || *cur
== '\n') {
544 if (cur
!= conn
->buffer
)
545 sipe_utils_shrink_buffer(conn
, cur
);
547 while ((cur
= strstr(conn
->buffer
, "\r\n\r\n")) != NULL
) {
551 time_t currtime
= time(NULL
);
554 SIPE_DEBUG_INFO("received - %s******\n%s\n******", ctime(&currtime
), tmp
= fix_newlines(conn
->buffer
));
557 msg
= sipmsg_parse_header(conn
->buffer
);
560 remainder
= conn
->buffer_used
- (cur
- conn
->buffer
);
561 if (msg
&& remainder
>= (guint
) msg
->bodylen
) {
562 char *dummy
= g_malloc(msg
->bodylen
+ 1);
563 memcpy(dummy
, cur
, msg
->bodylen
);
564 dummy
[msg
->bodylen
] = '\0';
567 sipe_utils_shrink_buffer(conn
, cur
);
570 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder
, msg
->bodylen
, (int)strlen(conn
->buffer
));
577 SIPE_DEBUG_INFO("body:\n%s", msg
->body
);
580 /* important to set before callback call */
581 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
582 http_conn
->closed
= TRUE
;
585 http_conn_process_input_message(http_conn
, msg
);
590 if (http_conn
->closed
) {
591 http_conn_close(http_conn
->do_close
, "Server closed connection");
592 } else if (http_conn
->do_close
) {
593 http_conn_close(http_conn
->do_close
, "User initiated");