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);
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_error(struct sipe_transport_connection
*conn
,
245 HttpConn
*http_conn
= HTTP_CONN
;
246 if (http_conn
->callback
) {
247 (*http_conn
->callback
)(HTTP_CONN_ERROR
, NULL
, NULL
, http_conn
, http_conn
->data
);
249 http_conn_close(http_conn
, msg
);
252 static void http_conn_send0(HttpConn
*http_conn
,
253 const char *authorization
);
254 static void http_conn_connected(struct sipe_transport_connection
*conn
)
256 http_conn_send0(HTTP_CONN
, NULL
);
259 static void http_conn_input(struct sipe_transport_connection
*conn
);
260 static struct sipe_transport_connection
*http_conn_setup(HttpConn
*http_conn
,
261 struct sipe_core_public
*sipe_public
,
265 sipe_connect_setup setup
= {
274 return(sipe_backend_transport_connect(sipe_public
, &setup
));
278 http_conn_create(struct sipe_core_public
*sipe_public
,
279 HttpSession
*http_session
,
282 gboolean allow_redirect
,
283 const char *full_url
,
285 const char *content_type
,
287 HttpConnCallback callback
,
291 struct sipe_transport_connection
*conn
;
295 if (!full_url
|| (strlen(full_url
) == 0)) {
296 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
300 http_conn_parse_url(full_url
, &host
, &port
, &url
);
301 http_conn
= g_new0(HttpConn
, 1);
302 conn
= http_conn_setup(http_conn
, sipe_public
, conn_type
, host
, port
);
310 http_conn
->sipe_public
= sipe_public
;
311 conn
->user_data
= http_conn
;
313 http_conn
->http_session
= http_session
;
314 http_conn
->method
= g_strdup(method
);
315 http_conn
->conn_type
= conn_type
;
316 http_conn
->allow_redirect
= allow_redirect
;
317 http_conn
->host
= host
;
318 http_conn
->port
= port
;
319 http_conn
->url
= url
;
320 http_conn
->body
= g_strdup(body
);
321 http_conn
->content_type
= g_strdup(content_type
);
322 http_conn
->auth
= auth
;
323 http_conn
->callback
= callback
;
324 http_conn
->data
= data
;
325 http_conn
->conn
= conn
;
332 http_conn_send0(HttpConn
*http_conn
,
333 const char *authorization
)
335 GString
*outstr
= g_string_new("");
337 g_string_append_printf(outstr
, HTTP_CONN_HEADER
,
338 http_conn
->method
? http_conn
->method
: "GET",
341 if (sipe_strequal(http_conn
->method
, "POST")) {
342 g_string_append_printf(outstr
, "Content-Length: %d\r\n",
343 http_conn
->body
? (int)strlen(http_conn
->body
) : 0);
345 g_string_append_printf(outstr
, "Content-Type: %s\r\n",
346 http_conn
->content_type
? http_conn
->content_type
: "text/plain");
348 if (http_conn
->http_session
&& http_conn
->http_session
->cookie
) {
349 g_string_append_printf(outstr
, "Cookie: %s\r\n", http_conn
->http_session
->cookie
);
352 g_string_append_printf(outstr
, "Authorization: %s\r\n", authorization
);
354 g_string_append_printf(outstr
, "\r\n%s", http_conn
->body
? http_conn
->body
: "");
356 sipe_backend_transport_message(http_conn
->conn
, outstr
->str
);
357 g_string_free(outstr
, TRUE
);
361 http_conn_send( HttpConn
*http_conn
,
363 const char *full_url
,
365 const char *content_type
,
366 HttpConnCallback callback
,
370 SIPE_DEBUG_INFO_NOFORMAT("http_conn_send: NULL http_conn, exiting.");
374 g_free(http_conn
->method
);
375 g_free(http_conn
->url
);
376 g_free(http_conn
->body
);
377 g_free(http_conn
->content_type
);
378 http_conn
->method
= g_strdup(method
);
379 http_conn_parse_url(full_url
, NULL
, NULL
, &http_conn
->url
);
380 http_conn
->body
= g_strdup(body
);
381 http_conn
->content_type
= g_strdup(content_type
);
382 http_conn
->callback
= callback
;
383 http_conn
->data
= data
;
385 http_conn_send0(http_conn
, NULL
);
389 http_conn_process_input_message(HttpConn
*http_conn
,
393 if ((msg
->response
== 300 ||
394 msg
->response
== 301 ||
395 msg
->response
== 302 ||
396 msg
->response
== 307) &&
397 http_conn
->allow_redirect
)
399 const char *location
= sipmsg_find_header(msg
, "Location");
401 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location
? location
: "");
403 http_conn
->do_close
= http_conn_clone(http_conn
);
404 http_conn
->sec_ctx
= NULL
;
406 g_free(http_conn
->host
);
407 g_free(http_conn
->url
);
408 http_conn_parse_url(location
, &http_conn
->host
, &http_conn
->port
, &http_conn
->url
);
409 http_conn
->conn
= http_conn_setup(http_conn
,
410 http_conn
->sipe_public
,
411 http_conn
->conn_type
,
415 /* Authentication required */
416 else if (msg
->response
== 401) {
424 const char *auth_name
;
426 char *output_toked_base64
;
427 int use_sso
= !http_conn
->auth
|| (http_conn
->auth
&& !http_conn
->auth
->user
);
430 http_conn
->retries
++;
431 if (http_conn
->retries
> 2) {
432 if (http_conn
->callback
) {
433 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
435 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
436 http_conn_set_close(http_conn
);
440 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
441 auth_type
= AUTH_TYPE_NTLM
;
445 tmp
= sipmsg_find_auth_header(msg
, "Negotiate");
446 if (tmp
&& http_conn
->auth
&& http_conn
->auth
->use_negotiate
) {
448 auth_type
= AUTH_TYPE_NEGOTIATE
;
449 auth_name
= "Negotiate";
454 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
457 "NTLM and Negotiate authentications are"
458 #else /* !HAVE_LIBKRB5 */
459 "NTLM authentication is"
460 #endif /* HAVE_LIBKRB5 */
462 "NTLM authentication is"
468 if (!http_conn
->sec_ctx
) {
470 sip_sec_create_context(auth_type
,
473 http_conn
->auth
&& http_conn
->auth
->domain
? http_conn
->auth
->domain
: "",
474 http_conn
->auth
? http_conn
->auth
->user
: NULL
,
475 http_conn
->auth
? http_conn
->auth
->password
: NULL
);
478 if (http_conn
->sec_ctx
) {
479 char **parts
= g_strsplit(ptmp
, " ", 0);
480 char *spn
= g_strdup_printf("HTTP/%s", http_conn
->host
);
481 ret
= sip_sec_init_context_step(http_conn
->sec_ctx
,
484 &output_toked_base64
,
491 if (http_conn
->callback
) {
492 (*http_conn
->callback
)(HTTP_CONN_ERROR_FATAL
, NULL
, NULL
, http_conn
, http_conn
->data
);
494 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
495 http_conn_set_close(http_conn
);
499 authorization
= g_strdup_printf("%s %s", auth_name
, output_toked_base64
? output_toked_base64
: "");
500 g_free(output_toked_base64
);
502 http_conn_send0(http_conn
, authorization
);
503 g_free(authorization
);
507 const char *set_cookie_hdr
;
508 const char *content_type
= sipmsg_find_header(msg
, "Content-Type");
509 http_conn
->retries
= 0;
512 * Set-Cookie: RMID=732423sdfs73242; expires=Fri, 31-Dec-2010 23:59:59 GMT; path=/; domain=.example.net
514 if (http_conn
->http_session
&& (set_cookie_hdr
= sipmsg_find_header(msg
, "Set-Cookie"))) {
519 g_free(http_conn
->http_session
->cookie
);
520 http_conn
->http_session
->cookie
= NULL
;
522 parts
= g_strsplit(set_cookie_hdr
, ";", 0);
523 for (i
= 0; parts
[i
]; i
++) {
524 if (!strstr(parts
[i
], "path=") &&
525 !strstr(parts
[i
], "domain=") &&
526 !strstr(parts
[i
], "expires=") &&
527 !strstr(parts
[i
], "secure"))
529 tmp
= http_conn
->http_session
->cookie
;
530 http_conn
->http_session
->cookie
= !tmp
?
532 g_strconcat(http_conn
->http_session
->cookie
, ";", parts
[i
], NULL
);
537 SIPE_DEBUG_INFO("http_conn_process_input_message: Set cookie: %s",
538 http_conn
->http_session
->cookie
? http_conn
->http_session
->cookie
: "");
541 if (http_conn
->callback
) {
542 (*http_conn
->callback
)(msg
->response
, msg
->body
, content_type
, http_conn
, http_conn
->data
);
547 static void http_conn_input(struct sipe_transport_connection
*conn
)
549 HttpConn
*http_conn
= HTTP_CONN
;
550 char *cur
= conn
->buffer
;
552 /* according to the RFC remove CRLF at the beginning */
553 while (*cur
== '\r' || *cur
== '\n') {
556 if (cur
!= conn
->buffer
)
557 sipe_utils_shrink_buffer(conn
, cur
);
559 while ((cur
= strstr(conn
->buffer
, "\r\n\r\n")) != NULL
) {
563 time_t currtime
= time(NULL
);
566 SIPE_DEBUG_INFO("received - %s******\n%s\n******", ctime(&currtime
), tmp
= fix_newlines(conn
->buffer
));
569 msg
= sipmsg_parse_header(conn
->buffer
);
572 remainder
= conn
->buffer_used
- (cur
- conn
->buffer
);
573 if (msg
&& remainder
>= (guint
) msg
->bodylen
) {
574 char *dummy
= g_malloc(msg
->bodylen
+ 1);
575 memcpy(dummy
, cur
, msg
->bodylen
);
576 dummy
[msg
->bodylen
] = '\0';
579 sipe_utils_shrink_buffer(conn
, cur
);
582 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", remainder
, msg
->bodylen
, (int)strlen(conn
->buffer
));
589 SIPE_DEBUG_INFO("body:\n%s", msg
->body
);
592 /* important to set before callback call */
593 if (sipe_strcase_equal(sipmsg_find_header(msg
, "Connection"), "close")) {
594 http_conn
->closed
= TRUE
;
597 http_conn_process_input_message(http_conn
, msg
);
602 if (http_conn
->closed
) {
603 http_conn_close(http_conn
->do_close
, "Server closed connection");
604 } else if (http_conn
->do_close
) {
605 http_conn_close(http_conn
->do_close
, "User initiated");