core cleanup: 6 more modules are purple free
[siplcs.git] / src / core / http-conn.c
blobd4876d4949db23e1567750da8cac0703719e3e49
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 "debug.h"
41 #include "eventloop.h"
42 #include "network.h"
43 #include "sslconn.h"
45 #include "sipmsg.h"
46 #include "sip-sec.h"
47 #include "sipe-utils.h"
48 #include "http-conn.h"
49 #include "sipe.h"
51 /**
52 * HTTP POST headers
53 * @param url (%s) Ex.: /EWS/Exchange.asmx
54 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
55 * @param content_length (%d) length of body part
56 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
58 #define HTTP_CONN_POST_HEADER \
59 "POST %s HTTP/1.1\r\n"\
60 "Host: %s\r\n"\
61 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"\
62 "Content-Length: %d\r\n"\
63 "Content-Type: %s\r\n"
66 struct http_conn_struct {
67 PurpleAccount *account;
68 char *conn_type;
69 char *host;
70 int port;
71 char *url;
72 char *body;
73 char *content_type;
74 HttpConnAuth *auth;
75 HttpConnCallback callback;
76 void *data;
78 /* SSL connection */
79 PurpleSslConnection *gsc;
80 int fd;
81 int listenport;
82 time_t last_keepalive;
83 struct sip_connection *conn;
84 SipSecContext sec_ctx;
85 int retries;
87 HttpConn* do_close;
90 static HttpConn*
91 http_conn_clone(HttpConn* http_conn)
93 HttpConn *res = g_new0(HttpConn, 1);
95 res->account = http_conn->account;
96 res->conn_type = g_strdup(http_conn->conn_type);
97 res->host = g_strdup(http_conn->host);
98 res->port = http_conn->port;
99 res->url = g_strdup(http_conn->url);
100 res->body = g_strdup(http_conn->body);
101 res->content_type = g_strdup(http_conn->content_type);
102 res->auth = http_conn->auth;
103 res->callback = http_conn->callback;
104 res->data = http_conn->data;
106 /* SSL connection */
107 res->gsc = http_conn->gsc;
108 res->fd = http_conn->fd;
109 res->listenport = http_conn->listenport;
110 res->last_keepalive = http_conn->last_keepalive;
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;
117 return res;
120 static void
121 http_conn_free(HttpConn* http_conn)
123 if (!http_conn) return;
125 g_free(http_conn->conn_type);
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);
135 g_free(http_conn);
138 void
139 http_conn_auth_free(struct http_conn_auth* auth)
141 g_free(auth->domain);
142 g_free(auth->user);
143 g_free(auth->password);
144 g_free(auth);
147 void
148 http_conn_set_close(HttpConn* http_conn)
150 http_conn->do_close = http_conn;
153 static void
154 http_conn_invalidate_ssl_connection(HttpConn *http_conn);
156 static void
157 http_conn_close(HttpConn *http_conn, const char *message)
159 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message ? message : "");
161 http_conn_invalidate_ssl_connection(http_conn);
162 http_conn_free(http_conn);
166 * Extracts host, port and relative url
167 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
169 * Allocates memory, must be g_free'd.
171 static void
172 http_conn_parse_url(const char *url,
173 char **host,
174 int *port,
175 char **rel_url)
177 char **parts = g_strsplit(url, "://", 2);
178 char *no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
179 int port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
180 char *tmp;
181 char *host_port;
183 g_strfreev(parts);
184 tmp = strstr(no_proto, "/");
185 if (tmp && rel_url) *rel_url = g_strdup(tmp);
186 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
187 g_free(no_proto);
189 parts = g_strsplit(host_port, ":", 2);
190 if (host) *host = g_strdup(parts[0]);
191 if (port) *port = parts[1] ? atoi(parts[1]) : port_tmp;
192 g_strfreev(parts);
194 g_free(host_port);
197 static void
198 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
199 PurpleSslErrorType error,
200 gpointer data)
202 HttpConn *http_conn = data;
203 const char *message = NULL;
205 http_conn->gsc = NULL;
207 switch(error) {
208 case PURPLE_SSL_CONNECT_FAILED:
209 message = "Connection failed";
210 break;
211 case PURPLE_SSL_HANDSHAKE_FAILED:
212 message = "SSL handshake failed";
213 break;
214 case PURPLE_SSL_CERTIFICATE_INVALID:
215 message = "SSL certificate invalid";
216 break;
219 if (http_conn->callback) {
220 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
222 http_conn_close(http_conn, message);
225 static void
226 http_conn_connection_remove(struct sip_connection *conn)
228 if (conn) {
229 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
230 g_free(conn->inbuf);
231 g_free(conn);
235 static void
236 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
238 if (http_conn) {
239 PurpleSslConnection *gsc = http_conn->gsc;
241 /* Invalidate this connection. Next send will open a new one */
242 if (gsc) {
243 struct sip_connection *conn = http_conn->conn;
245 http_conn_connection_remove(conn);
246 http_conn->conn = NULL;
247 purple_ssl_close(gsc);
249 http_conn->gsc = NULL;
250 http_conn->fd = -1;
254 static void
255 http_conn_process_input(HttpConn *http_conn);
257 static void
258 http_conn_input_cb_ssl(gpointer data,
259 PurpleSslConnection *gsc,
260 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
262 HttpConn *http_conn = data;
263 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
264 int readlen, len;
265 gboolean firstread = TRUE;
267 if (conn == NULL) {
268 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
269 return;
272 /* Read all available data from the SSL connection */
273 do {
274 /* Increase input buffer size as needed */
275 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
276 conn->inbuflen += SIMPLE_BUF_INC;
277 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
278 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
281 /* Try to read as much as there is space left in the buffer */
282 readlen = conn->inbuflen - conn->inbufused - 1;
283 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
285 if (len < 0 && errno == EAGAIN) {
286 /* Try again later */
287 return;
288 } else if (len < 0) {
289 if (http_conn->callback) {
290 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
292 http_conn_close(http_conn, "SSL read error");
293 return;
294 } else if (firstread && (len == 0)) {
295 if (http_conn->callback) {
296 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
298 http_conn_close(http_conn, "Server has disconnected");
299 return;
302 conn->inbufused += len;
303 firstread = FALSE;
305 /* Equivalence indicates that there is possibly more data to read */
306 } while (len == readlen);
308 conn->inbuf[conn->inbufused] = '\0';
309 http_conn_process_input(http_conn);
311 static void
312 http_conn_post0(HttpConn *http_conn,
313 const char *authorization);
315 static void
316 http_conn_input0_cb_ssl(gpointer data,
317 PurpleSslConnection *gsc,
318 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
320 HttpConn *http_conn = data;
322 http_conn->fd = gsc->fd;
323 http_conn->gsc = gsc;
324 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
325 //http_conn->connecting = FALSE;
326 http_conn->last_keepalive = time(NULL);
328 http_conn->conn = g_new0(struct sip_connection, 1);
330 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
332 http_conn_post0(http_conn, NULL);
335 HttpConn *
336 http_conn_create(PurpleAccount *account,
337 const char *conn_type,
338 const char *full_url,
339 const char *body,
340 const char *content_type,
341 HttpConnAuth *auth,
342 HttpConnCallback callback,
343 void *data)
345 HttpConn *http_conn;
347 if (!full_url || (strlen(full_url) == 0)) {
348 purple_debug_info("sipe-http", "no URL supplied!\n");
349 return NULL;
351 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
352 !purple_ssl_is_supported())
354 purple_debug_info("sipe-http", "SSL support is not installed. Either install SSL support or configure a different connection type in the account editor\n");
355 return NULL;
358 http_conn = g_new0(HttpConn, 1);
359 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
361 http_conn->account = account;
362 http_conn->conn_type = g_strdup(conn_type);
363 http_conn->body = g_strdup(body);
364 http_conn->content_type = g_strdup(content_type);
365 http_conn->auth = auth;
366 http_conn->callback = callback;
367 http_conn->data = data;
369 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
370 http_conn->host,
371 http_conn->port,
372 http_conn_input0_cb_ssl,
373 http_conn_ssl_connect_failure,
374 http_conn);
376 return http_conn;
379 /* Data part */
380 static void
381 http_conn_process_input_message(HttpConn *http_conn,
382 struct sipmsg *msg);
384 static void
385 http_conn_process_input(HttpConn *http_conn)
387 char *cur;
388 char *dummy;
389 char *tmp;
390 struct sipmsg *msg;
391 int restlen;
392 struct sip_connection *conn = http_conn->conn;
394 cur = conn->inbuf;
396 /* according to the RFC remove CRLF at the beginning */
397 while (*cur == '\r' || *cur == '\n') {
398 cur++;
400 if (cur != conn->inbuf) {
401 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
402 conn->inbufused = strlen(conn->inbuf);
405 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
406 time_t currtime = time(NULL);
407 cur += 2;
408 cur[0] = '\0';
409 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
410 g_free(tmp);
412 msg = sipmsg_parse_header(conn->inbuf);
413 cur[0] = '\r';
414 cur += 2;
415 restlen = conn->inbufused - (cur - conn->inbuf);
416 if (msg && restlen >= msg->bodylen) {
417 dummy = g_malloc(msg->bodylen + 1);
418 memcpy(dummy, cur, msg->bodylen);
419 dummy[msg->bodylen] = '\0';
420 msg->body = dummy;
421 cur += msg->bodylen;
422 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
423 conn->inbufused = strlen(conn->inbuf);
424 } else {
425 if (msg){
426 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
427 sipmsg_free(msg);
429 return;
432 if (msg->body) {
433 purple_debug_info("sipe-http", "body:\n%s\n", msg->body);
436 http_conn_process_input_message(http_conn, msg);
438 sipmsg_free(msg);
441 if (http_conn->do_close) {
442 http_conn_close(http_conn->do_close, "User initiated");
446 static void
447 http_conn_sendout_pkt(HttpConn *http_conn,
448 const char *buf)
450 time_t currtime = time(NULL);
451 int writelen = strlen(buf);
452 char *tmp;
453 int ret = 0;
455 purple_debug(PURPLE_DEBUG_MISC, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(buf));
456 g_free(tmp);
458 if (http_conn->fd < 0) {
459 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
460 return;
463 if (http_conn->gsc) {
464 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
467 if (ret < 0 && errno == EAGAIN)
468 ret = 0;
469 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
470 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
471 return;
474 if (ret < writelen) {
475 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
479 static void
480 http_conn_post0(HttpConn *http_conn,
481 const char *authorization)
483 GString *outstr = g_string_new("");
485 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
486 http_conn->url,
487 http_conn->host,
488 http_conn->body ? (int)strlen(http_conn->body) : 0,
489 http_conn->content_type ? http_conn->content_type : "text/plain");
490 if (authorization) {
491 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
493 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
495 http_conn_sendout_pkt(http_conn, outstr->str);
496 g_string_free(outstr, TRUE);
499 void
500 http_conn_post( HttpConn *http_conn,
501 const char *full_url,
502 const char *body,
503 const char *content_type,
504 HttpConnCallback callback,
505 void *data)
507 if (!http_conn) {
508 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
509 return;
512 g_free(http_conn->url);
513 g_free(http_conn->body);
514 g_free(http_conn->content_type);
515 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
516 http_conn->body = g_strdup(body);
517 http_conn->content_type = g_strdup(content_type);
518 http_conn->callback = callback;
519 http_conn->data = data;
521 http_conn_post0(http_conn, NULL);
524 static void
525 http_conn_process_input_message(HttpConn *http_conn,
526 struct sipmsg *msg)
528 /* Redirect */
529 if (msg->response == 300 ||
530 msg->response == 301 ||
531 msg->response == 302 ||
532 msg->response == 307)
534 const char *location = sipmsg_find_header(msg, "Location");
536 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location ? location : "");
538 http_conn->do_close = http_conn_clone(http_conn);
539 http_conn->sec_ctx = NULL;
541 g_free(http_conn->host);
542 g_free(http_conn->url);
543 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
545 http_conn->gsc = purple_ssl_connect(http_conn->account,
546 http_conn->host,
547 http_conn->port,
548 http_conn_input0_cb_ssl,
549 http_conn_ssl_connect_failure,
550 http_conn);
553 /* Authentication required */
554 else if (msg->response == 401) {
555 char *ptmp;
556 #ifdef _WIN32
557 #ifdef HAVE_KERBEROS
558 char *tmp;
559 #endif
560 #endif
561 SipSecAuthType auth_type;
562 const char *auth_name;
563 char *authorization;
564 char *output_toked_base64;
565 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
566 long ret = -1;
568 http_conn->retries++;
569 if (http_conn->retries > 2) {
570 if (http_conn->callback) {
571 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
573 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
574 http_conn_set_close(http_conn);
575 return;
578 ptmp = sipmsg_find_auth_header(msg, "NTLM");
579 auth_type = AUTH_TYPE_NTLM;
580 auth_name = "NTLM";
581 #ifdef _WIN32
582 #ifdef HAVE_KERBEROS
583 tmp = sipmsg_find_auth_header(msg, "Negotiate");
584 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
585 ptmp = tmp;
586 auth_type = AUTH_TYPE_NEGOTIATE;
587 auth_name = "Negotiate";
589 #endif
590 #endif
591 if (!ptmp) {
592 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
593 #ifdef _WIN32
594 #ifdef HAVE_KERBEROS
595 "NTLM and Negotiate authentications are"
596 #else /* !HAVE_KERBEROS */
597 "NTLM authentication is"
598 #endif /* HAVE_KERBEROS */
599 #else /* !_WIN32 */
600 "NTLM authentication is"
601 #endif /* _WIN32 */
606 if (!http_conn->sec_ctx) {
607 http_conn->sec_ctx =
608 sip_sec_create_context(auth_type,
609 use_sso,
611 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
612 http_conn->auth ? http_conn->auth->user : NULL,
613 http_conn->auth ? http_conn->auth->password : NULL);
616 if (http_conn->sec_ctx) {
617 char **parts = g_strsplit(ptmp, " ", 0);
618 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
619 ret = sip_sec_init_context_step(http_conn->sec_ctx,
620 spn,
621 parts[1],
622 &output_toked_base64,
623 NULL);
624 g_free(spn);
625 g_strfreev(parts);
628 if (ret < 0) {
629 if (http_conn->callback) {
630 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
632 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
633 http_conn_set_close(http_conn);
634 return;
637 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
638 g_free(output_toked_base64);
640 http_conn_post0(http_conn, authorization);
641 g_free(authorization);
643 /* Other response */
644 else {
645 http_conn->retries = 0;
647 if (http_conn->callback) {
648 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
656 Local Variables:
657 mode: c
658 c-file-style: "bsd"
659 indent-tabs-mode: t
660 tab-width: 8
661 End: