l10n: Updates to Portuguese (Brazilian) (pt_BR) translation
[siplcs.git] / src / core / http-conn.c
blob7a6af2df3510dee5eddd79106b8c513bc0f91eac
1 /**
2 * @file http-conn.c
4 * pidgin-sipe
6 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 /**
25 * Operates with HTTPS connection.
26 * Support Negotiate (Windows only) and NTLM authentications, redirect.
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
33 #include <string.h>
34 #include <errno.h>
35 #include <time.h>
37 #include <glib.h>
39 #include "account.h"
40 #include "eventloop.h"
41 #include "network.h"
42 #include "sslconn.h"
44 #include "sipe-common.h"
45 #include "sipmsg.h"
46 #include "sip-sec.h"
47 #include "sipe-backend.h"
48 #include "sipe-utils.h"
49 #include "http-conn.h"
50 #include "sipe.h"
52 /**
53 * HTTP POST headers
54 * @param url (%s) Ex.: /EWS/Exchange.asmx
55 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
56 * @param content_length (%d) length of body part
57 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
59 #define HTTP_CONN_POST_HEADER \
60 "POST %s HTTP/1.1\r\n"\
61 "Host: %s\r\n"\
62 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"\
63 "Content-Length: %d\r\n"\
64 "Content-Type: %s\r\n"
67 struct http_conn_struct {
68 PurpleAccount *account;
69 char *conn_type;
70 char *host;
71 int port;
72 char *url;
73 char *body;
74 char *content_type;
75 HttpConnAuth *auth;
76 HttpConnCallback callback;
77 void *data;
79 /* SSL connection */
80 PurpleSslConnection *gsc;
81 int fd;
82 int listenport;
83 time_t last_keepalive;
84 struct sip_connection *conn;
85 SipSecContext sec_ctx;
86 int retries;
88 HttpConn* do_close;
91 static HttpConn*
92 http_conn_clone(HttpConn* http_conn)
94 HttpConn *res = g_new0(HttpConn, 1);
96 res->account = http_conn->account;
97 res->conn_type = g_strdup(http_conn->conn_type);
98 res->host = g_strdup(http_conn->host);
99 res->port = http_conn->port;
100 res->url = g_strdup(http_conn->url);
101 res->body = g_strdup(http_conn->body);
102 res->content_type = g_strdup(http_conn->content_type);
103 res->auth = http_conn->auth;
104 res->callback = http_conn->callback;
105 res->data = http_conn->data;
107 /* SSL connection */
108 res->gsc = http_conn->gsc;
109 res->fd = http_conn->fd;
110 res->listenport = http_conn->listenport;
111 res->last_keepalive = http_conn->last_keepalive;
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;
118 return res;
121 static void
122 http_conn_free(HttpConn* http_conn)
124 if (!http_conn) return;
126 g_free(http_conn->conn_type);
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);
136 g_free(http_conn);
139 void
140 http_conn_auth_free(struct http_conn_auth* auth)
142 g_free(auth->domain);
143 g_free(auth->user);
144 g_free(auth->password);
145 g_free(auth);
148 void
149 http_conn_set_close(HttpConn* http_conn)
151 http_conn->do_close = http_conn;
154 static void
155 http_conn_invalidate_ssl_connection(HttpConn *http_conn);
157 static void
158 http_conn_close(HttpConn *http_conn, const char *message)
160 SIPE_DEBUG_INFO("http_conn_close: closing http connection: %s", message ? message : "");
162 http_conn_invalidate_ssl_connection(http_conn);
163 http_conn_free(http_conn);
167 * Extracts host, port and relative url
168 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
170 * Allocates memory, must be g_free'd.
172 static void
173 http_conn_parse_url(const char *url,
174 char **host,
175 int *port,
176 char **rel_url)
178 char **parts = g_strsplit(url, "://", 2);
179 char *no_proto;
180 int port_tmp;
181 char *tmp;
182 char *host_port;
184 if(!parts) {
185 return;
186 } else if(!parts[0]) {
187 g_strfreev(parts);
188 return;
191 no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
192 port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
193 g_strfreev(parts);
195 if(!no_proto) {
196 return;
199 tmp = strstr(no_proto, "/");
200 if (tmp && rel_url) *rel_url = g_strdup(tmp);
201 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
202 g_free(no_proto);
204 if(!host_port) {
205 return;
208 parts = g_strsplit(host_port, ":", 2);
210 if(parts) {
211 if (host) *host = g_strdup(parts[0]);
212 if(parts[0]) {
213 port_tmp = parts[1] ? atoi(parts[1]) : port_tmp;
215 if (port) *port = port_tmp;
216 g_strfreev(parts);
219 g_free(host_port);
222 static void
223 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
224 PurpleSslErrorType error,
225 gpointer data)
227 HttpConn *http_conn = data;
228 const char *message = NULL;
230 http_conn->gsc = NULL;
232 switch(error) {
233 case PURPLE_SSL_CONNECT_FAILED:
234 message = "Connection failed";
235 break;
236 case PURPLE_SSL_HANDSHAKE_FAILED:
237 message = "SSL handshake failed";
238 break;
239 case PURPLE_SSL_CERTIFICATE_INVALID:
240 message = "SSL certificate invalid";
241 break;
244 if (http_conn->callback) {
245 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
247 http_conn_close(http_conn, message);
250 static void
251 http_conn_connection_remove(struct sip_connection *conn)
253 if (conn) {
254 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
255 g_free(conn->inbuf);
256 g_free(conn);
260 static void
261 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
263 if (http_conn) {
264 PurpleSslConnection *gsc = http_conn->gsc;
266 /* Invalidate this connection. Next send will open a new one */
267 if (gsc) {
268 struct sip_connection *conn = http_conn->conn;
270 http_conn_connection_remove(conn);
271 http_conn->conn = NULL;
272 purple_ssl_close(gsc);
274 http_conn->gsc = NULL;
275 http_conn->fd = -1;
279 static void
280 http_conn_process_input(HttpConn *http_conn);
282 static void
283 http_conn_input_cb_ssl(gpointer data,
284 PurpleSslConnection *gsc,
285 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
287 HttpConn *http_conn = data;
288 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
289 int readlen, len;
290 gboolean firstread = TRUE;
292 if (conn == NULL) {
293 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
294 return;
297 /* Read all available data from the SSL connection */
298 do {
299 /* Increase input buffer size as needed */
300 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
301 conn->inbuflen += SIMPLE_BUF_INC;
302 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
303 SIPE_DEBUG_INFO("http_conn_input_cb_ssl: new input buffer length %d", conn->inbuflen);
306 /* Try to read as much as there is space left in the buffer */
307 readlen = conn->inbuflen - conn->inbufused - 1;
308 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
310 if (len < 0 && errno == EAGAIN) {
311 /* Try again later */
312 return;
313 } else if (len < 0) {
314 if (http_conn->callback) {
315 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
317 http_conn_close(http_conn, "SSL read error");
318 return;
319 } else if (firstread && (len == 0)) {
320 if (http_conn->callback) {
321 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
323 http_conn_close(http_conn, "Server has disconnected");
324 return;
327 conn->inbufused += len;
328 firstread = FALSE;
330 /* Equivalence indicates that there is possibly more data to read */
331 } while (len == readlen);
333 conn->inbuf[conn->inbufused] = '\0';
334 http_conn_process_input(http_conn);
336 static void
337 http_conn_post0(HttpConn *http_conn,
338 const char *authorization);
340 static void
341 http_conn_input0_cb_ssl(gpointer data,
342 PurpleSslConnection *gsc,
343 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
345 HttpConn *http_conn = data;
347 http_conn->fd = gsc->fd;
348 http_conn->gsc = gsc;
349 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
350 //http_conn->connecting = FALSE;
351 http_conn->last_keepalive = time(NULL);
353 http_conn->conn = g_new0(struct sip_connection, 1);
355 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
357 http_conn_post0(http_conn, NULL);
360 HttpConn *
361 http_conn_create(PurpleAccount *account,
362 const char *conn_type,
363 const char *full_url,
364 const char *body,
365 const char *content_type,
366 HttpConnAuth *auth,
367 HttpConnCallback callback,
368 void *data)
370 HttpConn *http_conn;
372 if (!full_url || (strlen(full_url) == 0)) {
373 SIPE_DEBUG_INFO_NOFORMAT("no URL supplied!");
374 return NULL;
376 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
377 !purple_ssl_is_supported())
379 SIPE_DEBUG_INFO_NOFORMAT("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor.");
380 return NULL;
383 http_conn = g_new0(HttpConn, 1);
384 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
386 http_conn->account = account;
387 http_conn->conn_type = g_strdup(conn_type);
388 http_conn->body = g_strdup(body);
389 http_conn->content_type = g_strdup(content_type);
390 http_conn->auth = auth;
391 http_conn->callback = callback;
392 http_conn->data = data;
394 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
395 http_conn->host,
396 http_conn->port,
397 http_conn_input0_cb_ssl,
398 http_conn_ssl_connect_failure,
399 http_conn);
401 return http_conn;
404 /* Data part */
405 static void
406 http_conn_process_input_message(HttpConn *http_conn,
407 struct sipmsg *msg);
409 static void
410 http_conn_process_input(HttpConn *http_conn)
412 char *cur;
413 char *dummy;
414 char *tmp;
415 struct sipmsg *msg;
416 int restlen;
417 struct sip_connection *conn = http_conn->conn;
419 cur = conn->inbuf;
421 /* according to the RFC remove CRLF at the beginning */
422 while (*cur == '\r' || *cur == '\n') {
423 cur++;
425 if (cur != conn->inbuf) {
426 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
427 conn->inbufused = strlen(conn->inbuf);
430 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
431 time_t currtime = time(NULL);
432 cur += 2;
433 cur[0] = '\0';
434 SIPE_DEBUG_INFO("received - %s******\n%s\n******", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
435 g_free(tmp);
437 msg = sipmsg_parse_header(conn->inbuf);
438 cur[0] = '\r';
439 cur += 2;
440 restlen = conn->inbufused - (cur - conn->inbuf);
441 if (msg && restlen >= msg->bodylen) {
442 dummy = g_malloc(msg->bodylen + 1);
443 memcpy(dummy, cur, msg->bodylen);
444 dummy[msg->bodylen] = '\0';
445 msg->body = dummy;
446 cur += msg->bodylen;
447 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
448 conn->inbufused = strlen(conn->inbuf);
449 } else {
450 if (msg){
451 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen, msg->bodylen, (int)strlen(conn->inbuf));
452 sipmsg_free(msg);
454 return;
457 if (msg->body) {
458 SIPE_DEBUG_INFO("body:\n%s", msg->body);
461 http_conn_process_input_message(http_conn, msg);
463 sipmsg_free(msg);
466 if (http_conn->do_close) {
467 http_conn_close(http_conn->do_close, "User initiated");
471 static void
472 http_conn_sendout_pkt(HttpConn *http_conn,
473 const char *buf)
475 time_t currtime = time(NULL);
476 int writelen = strlen(buf);
477 char *tmp;
478 int ret = 0;
480 SIPE_DEBUG_INFO("sending - %s******\n%s\n******", ctime(&currtime), tmp = fix_newlines(buf));
481 g_free(tmp);
483 if (http_conn->fd < 0) {
484 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: http_conn->fd < 0, exiting");
485 return;
488 if (http_conn->gsc) {
489 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
492 if (ret < 0 && errno == EAGAIN)
493 ret = 0;
494 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
495 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: ret <= 0, exiting");
496 return;
499 if (ret < writelen) {
500 SIPE_DEBUG_INFO_NOFORMAT("http_conn_sendout_pkt: ret < writelen, exiting");
504 static void
505 http_conn_post0(HttpConn *http_conn,
506 const char *authorization)
508 GString *outstr = g_string_new("");
510 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
511 http_conn->url,
512 http_conn->host,
513 http_conn->body ? (int)strlen(http_conn->body) : 0,
514 http_conn->content_type ? http_conn->content_type : "text/plain");
515 if (authorization) {
516 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
518 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
520 http_conn_sendout_pkt(http_conn, outstr->str);
521 g_string_free(outstr, TRUE);
524 void
525 http_conn_post( HttpConn *http_conn,
526 const char *full_url,
527 const char *body,
528 const char *content_type,
529 HttpConnCallback callback,
530 void *data)
532 if (!http_conn) {
533 SIPE_DEBUG_INFO_NOFORMAT("http_conn_post: NULL http_conn, exiting.");
534 return;
537 g_free(http_conn->url);
538 g_free(http_conn->body);
539 g_free(http_conn->content_type);
540 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
541 http_conn->body = g_strdup(body);
542 http_conn->content_type = g_strdup(content_type);
543 http_conn->callback = callback;
544 http_conn->data = data;
546 http_conn_post0(http_conn, NULL);
549 static void
550 http_conn_process_input_message(HttpConn *http_conn,
551 struct sipmsg *msg)
553 /* Redirect */
554 if (msg->response == 300 ||
555 msg->response == 301 ||
556 msg->response == 302 ||
557 msg->response == 307)
559 const char *location = sipmsg_find_header(msg, "Location");
561 SIPE_DEBUG_INFO("http_conn_process_input_message: Redirect to: %s", location ? location : "");
563 http_conn->do_close = http_conn_clone(http_conn);
564 http_conn->sec_ctx = NULL;
566 g_free(http_conn->host);
567 g_free(http_conn->url);
568 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
570 http_conn->gsc = purple_ssl_connect(http_conn->account,
571 http_conn->host,
572 http_conn->port,
573 http_conn_input0_cb_ssl,
574 http_conn_ssl_connect_failure,
575 http_conn);
578 /* Authentication required */
579 else if (msg->response == 401) {
580 char *ptmp;
581 #ifdef _WIN32
582 #ifdef HAVE_LIBKRB5
583 char *tmp;
584 #endif
585 #endif
586 SipSecAuthType auth_type;
587 const char *auth_name;
588 char *authorization;
589 char *output_toked_base64;
590 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
591 long ret = -1;
593 http_conn->retries++;
594 if (http_conn->retries > 2) {
595 if (http_conn->callback) {
596 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
598 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Authentication failed");
599 http_conn_set_close(http_conn);
600 return;
603 ptmp = sipmsg_find_auth_header(msg, "NTLM");
604 auth_type = AUTH_TYPE_NTLM;
605 auth_name = "NTLM";
606 #ifdef _WIN32
607 #ifdef HAVE_LIBKRB5
608 tmp = sipmsg_find_auth_header(msg, "Negotiate");
609 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
610 ptmp = tmp;
611 auth_type = AUTH_TYPE_NEGOTIATE;
612 auth_name = "Negotiate";
614 #endif
615 #endif
616 if (!ptmp) {
617 SIPE_DEBUG_INFO("http_conn_process_input_message: Only %s supported in the moment, exiting",
618 #ifdef _WIN32
619 #ifdef HAVE_LIBKRB5
620 "NTLM and Negotiate authentications are"
621 #else /* !HAVE_LIBKRB5 */
622 "NTLM authentication is"
623 #endif /* HAVE_LIBKRB5 */
624 #else /* !_WIN32 */
625 "NTLM authentication is"
626 #endif /* _WIN32 */
631 if (!http_conn->sec_ctx) {
632 http_conn->sec_ctx =
633 sip_sec_create_context(auth_type,
634 use_sso,
636 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
637 http_conn->auth ? http_conn->auth->user : NULL,
638 http_conn->auth ? http_conn->auth->password : NULL);
641 if (http_conn->sec_ctx) {
642 char **parts = g_strsplit(ptmp, " ", 0);
643 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
644 ret = sip_sec_init_context_step(http_conn->sec_ctx,
645 spn,
646 parts[1],
647 &output_toked_base64,
648 NULL);
649 g_free(spn);
650 g_strfreev(parts);
653 if (ret < 0) {
654 if (http_conn->callback) {
655 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
657 SIPE_DEBUG_INFO_NOFORMAT("http_conn_process_input_message: Failed to initialize security context");
658 http_conn_set_close(http_conn);
659 return;
662 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
663 g_free(output_toked_base64);
665 http_conn_post0(http_conn, authorization);
666 g_free(authorization);
668 /* Other response */
669 else {
670 http_conn->retries = 0;
672 if (http_conn->callback) {
673 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
681 Local Variables:
682 mode: c
683 c-file-style: "bsd"
684 indent-tabs-mode: t
685 tab-width: 8
686 End: