interface cleanup: flatten interface dependencies
[siplcs.git] / src / core / http-conn.c
blob1a767086a4562140d12a663261c1944db72c3dbd
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 "blist.h"
41 #include "circbuffer.h"
42 #include "connection.h"
43 #include "debug.h"
44 #include "dnsquery.h"
45 #include "dnssrv.h"
46 #include "eventloop.h"
47 #include "network.h"
48 #include "plugin.h"
49 #include "sslconn.h"
50 #include "xmlnode.h"
52 #include "sipmsg.h"
53 #include "sip-sec.h"
54 #include "sipe-utils.h"
55 #include "http-conn.h"
56 #include "sipe.h"
58 /**
59 * HTTP POST headers
60 * @param url (%s) Ex.: /EWS/Exchange.asmx
61 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
62 * @param content_length (%d) length of body part
63 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
65 #define HTTP_CONN_POST_HEADER \
66 "POST %s HTTP/1.1\r\n"\
67 "Host: %s\r\n"\
68 "User-Agent: Sipe/" PACKAGE_VERSION "\r\n"\
69 "Content-Length: %d\r\n"\
70 "Content-Type: %s\r\n"
73 struct http_conn_struct {
74 PurpleAccount *account;
75 char *conn_type;
76 char *host;
77 int port;
78 char *url;
79 char *body;
80 char *content_type;
81 HttpConnAuth *auth;
82 HttpConnCallback callback;
83 void *data;
85 /* SSL connection */
86 PurpleSslConnection *gsc;
87 int fd;
88 int listenport;
89 time_t last_keepalive;
90 struct sip_connection *conn;
91 SipSecContext sec_ctx;
92 int retries;
94 HttpConn* do_close;
97 static HttpConn*
98 http_conn_clone(HttpConn* http_conn)
100 HttpConn *res = g_new0(HttpConn, 1);
102 res->account = http_conn->account;
103 res->conn_type = g_strdup(http_conn->conn_type);
104 res->host = g_strdup(http_conn->host);
105 res->port = http_conn->port;
106 res->url = g_strdup(http_conn->url);
107 res->body = g_strdup(http_conn->body);
108 res->content_type = g_strdup(http_conn->content_type);
109 res->auth = http_conn->auth;
110 res->callback = http_conn->callback;
111 res->data = http_conn->data;
113 /* SSL connection */
114 res->gsc = http_conn->gsc;
115 res->fd = http_conn->fd;
116 res->listenport = http_conn->listenport;
117 res->last_keepalive = http_conn->last_keepalive;
118 res->conn = http_conn->conn;
119 res->sec_ctx = http_conn->sec_ctx;
120 res->retries = http_conn->retries;
122 res->do_close = NULL;
124 return res;
127 static void
128 http_conn_free(HttpConn* http_conn)
130 if (!http_conn) return;
132 g_free(http_conn->conn_type);
133 g_free(http_conn->host);
134 g_free(http_conn->url);
135 g_free(http_conn->body);
136 g_free(http_conn->content_type);
138 if (http_conn->sec_ctx) {
139 sip_sec_destroy_context(http_conn->sec_ctx);
142 g_free(http_conn);
145 void
146 http_conn_auth_free(struct http_conn_auth* auth)
148 g_free(auth->domain);
149 g_free(auth->user);
150 g_free(auth->password);
151 g_free(auth);
154 void
155 http_conn_set_close(HttpConn* http_conn)
157 http_conn->do_close = http_conn;
160 static void
161 http_conn_invalidate_ssl_connection(HttpConn *http_conn);
163 static void
164 http_conn_close(HttpConn *http_conn, const char *message)
166 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message ? message : "");
168 http_conn_invalidate_ssl_connection(http_conn);
169 http_conn_free(http_conn);
173 * Extracts host, port and relative url
174 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
176 * Allocates memory, must be g_free'd.
178 static void
179 http_conn_parse_url(const char *url,
180 char **host,
181 int *port,
182 char **rel_url)
184 char **parts = g_strsplit(url, "://", 2);
185 char *no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
186 int port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
187 char *tmp;
188 char *host_port;
190 g_strfreev(parts);
191 tmp = strstr(no_proto, "/");
192 if (tmp && rel_url) *rel_url = g_strdup(tmp);
193 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
194 g_free(no_proto);
196 parts = g_strsplit(host_port, ":", 2);
197 if (host) *host = g_strdup(parts[0]);
198 if (port) *port = parts[1] ? atoi(parts[1]) : port_tmp;
199 g_strfreev(parts);
201 g_free(host_port);
204 static void
205 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
206 PurpleSslErrorType error,
207 gpointer data)
209 HttpConn *http_conn = data;
210 const char *message = NULL;
212 http_conn->gsc = NULL;
214 switch(error) {
215 case PURPLE_SSL_CONNECT_FAILED:
216 message = "Connection failed";
217 break;
218 case PURPLE_SSL_HANDSHAKE_FAILED:
219 message = "SSL handshake failed";
220 break;
221 case PURPLE_SSL_CERTIFICATE_INVALID:
222 message = "SSL certificate invalid";
223 break;
226 if (http_conn->callback) {
227 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
229 http_conn_close(http_conn, message);
232 static void
233 http_conn_connection_remove(struct sip_connection *conn)
235 if (conn) {
236 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
237 g_free(conn->inbuf);
238 g_free(conn);
242 static void
243 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
245 if (http_conn) {
246 PurpleSslConnection *gsc = http_conn->gsc;
248 /* Invalidate this connection. Next send will open a new one */
249 if (gsc) {
250 struct sip_connection *conn = http_conn->conn;
252 http_conn_connection_remove(conn);
253 http_conn->conn = NULL;
254 purple_ssl_close(gsc);
256 http_conn->gsc = NULL;
257 http_conn->fd = -1;
261 static void
262 http_conn_process_input(HttpConn *http_conn);
264 static void
265 http_conn_input_cb_ssl(gpointer data,
266 PurpleSslConnection *gsc,
267 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
269 HttpConn *http_conn = data;
270 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
271 int readlen, len;
272 gboolean firstread = TRUE;
274 if (conn == NULL) {
275 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
276 return;
279 /* Read all available data from the SSL connection */
280 do {
281 /* Increase input buffer size as needed */
282 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
283 conn->inbuflen += SIMPLE_BUF_INC;
284 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
285 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
288 /* Try to read as much as there is space left in the buffer */
289 readlen = conn->inbuflen - conn->inbufused - 1;
290 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
292 if (len < 0 && errno == EAGAIN) {
293 /* Try again later */
294 return;
295 } else if (len < 0) {
296 if (http_conn->callback) {
297 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
299 http_conn_close(http_conn, "SSL read error");
300 return;
301 } else if (firstread && (len == 0)) {
302 if (http_conn->callback) {
303 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
305 http_conn_close(http_conn, "Server has disconnected");
306 return;
309 conn->inbufused += len;
310 firstread = FALSE;
312 /* Equivalence indicates that there is possibly more data to read */
313 } while (len == readlen);
315 conn->inbuf[conn->inbufused] = '\0';
316 http_conn_process_input(http_conn);
318 static void
319 http_conn_post0(HttpConn *http_conn,
320 const char *authorization);
322 static void
323 http_conn_input0_cb_ssl(gpointer data,
324 PurpleSslConnection *gsc,
325 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
327 HttpConn *http_conn = data;
329 http_conn->fd = gsc->fd;
330 http_conn->gsc = gsc;
331 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
332 //http_conn->connecting = FALSE;
333 http_conn->last_keepalive = time(NULL);
335 http_conn->conn = g_new0(struct sip_connection, 1);
337 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
339 http_conn_post0(http_conn, NULL);
342 HttpConn *
343 http_conn_create(PurpleAccount *account,
344 const char *conn_type,
345 const char *full_url,
346 const char *body,
347 const char *content_type,
348 HttpConnAuth *auth,
349 HttpConnCallback callback,
350 void *data)
352 HttpConn *http_conn;
354 if (!full_url || (strlen(full_url) == 0)) {
355 purple_debug_info("sipe-http", "no URL supplied!\n");
356 return NULL;
358 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
359 !purple_ssl_is_supported())
361 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");
362 return NULL;
365 http_conn = g_new0(HttpConn, 1);
366 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
368 http_conn->account = account;
369 http_conn->conn_type = g_strdup(conn_type);
370 http_conn->body = g_strdup(body);
371 http_conn->content_type = g_strdup(content_type);
372 http_conn->auth = auth;
373 http_conn->callback = callback;
374 http_conn->data = data;
376 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
377 http_conn->host,
378 http_conn->port,
379 http_conn_input0_cb_ssl,
380 http_conn_ssl_connect_failure,
381 http_conn);
383 return http_conn;
386 /* Data part */
387 static void
388 http_conn_process_input_message(HttpConn *http_conn,
389 struct sipmsg *msg);
391 static void
392 http_conn_process_input(HttpConn *http_conn)
394 char *cur;
395 char *dummy;
396 char *tmp;
397 struct sipmsg *msg;
398 int restlen;
399 struct sip_connection *conn = http_conn->conn;
401 cur = conn->inbuf;
403 /* according to the RFC remove CRLF at the beginning */
404 while (*cur == '\r' || *cur == '\n') {
405 cur++;
407 if (cur != conn->inbuf) {
408 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
409 conn->inbufused = strlen(conn->inbuf);
412 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
413 time_t currtime = time(NULL);
414 cur += 2;
415 cur[0] = '\0';
416 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
417 g_free(tmp);
419 msg = sipmsg_parse_header(conn->inbuf);
420 cur[0] = '\r';
421 cur += 2;
422 restlen = conn->inbufused - (cur - conn->inbuf);
423 if (msg && restlen >= msg->bodylen) {
424 dummy = g_malloc(msg->bodylen + 1);
425 memcpy(dummy, cur, msg->bodylen);
426 dummy[msg->bodylen] = '\0';
427 msg->body = dummy;
428 cur += msg->bodylen;
429 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
430 conn->inbufused = strlen(conn->inbuf);
431 } else {
432 if (msg){
433 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
434 sipmsg_free(msg);
436 return;
439 if (msg->body) {
440 purple_debug_info("sipe-http", "body:\n%s\n", msg->body);
443 http_conn_process_input_message(http_conn, msg);
445 sipmsg_free(msg);
448 if (http_conn->do_close) {
449 http_conn_close(http_conn->do_close, "User initiated");
453 static void
454 http_conn_sendout_pkt(HttpConn *http_conn,
455 const char *buf)
457 time_t currtime = time(NULL);
458 int writelen = strlen(buf);
459 char *tmp;
460 int ret = 0;
462 purple_debug(PURPLE_DEBUG_MISC, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(buf));
463 g_free(tmp);
465 if (http_conn->fd < 0) {
466 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
467 return;
470 if (http_conn->gsc) {
471 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
474 if (ret < 0 && errno == EAGAIN)
475 ret = 0;
476 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
477 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
478 return;
481 if (ret < writelen) {
482 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
486 static void
487 http_conn_post0(HttpConn *http_conn,
488 const char *authorization)
490 GString *outstr = g_string_new("");
492 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
493 http_conn->url,
494 http_conn->host,
495 http_conn->body ? (int)strlen(http_conn->body) : 0,
496 http_conn->content_type ? http_conn->content_type : "text/plain");
497 if (authorization) {
498 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
500 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
502 http_conn_sendout_pkt(http_conn, outstr->str);
503 g_string_free(outstr, TRUE);
506 void
507 http_conn_post( HttpConn *http_conn,
508 const char *full_url,
509 const char *body,
510 const char *content_type,
511 HttpConnCallback callback,
512 void *data)
514 if (!http_conn) {
515 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
516 return;
519 g_free(http_conn->url);
520 g_free(http_conn->body);
521 g_free(http_conn->content_type);
522 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
523 http_conn->body = g_strdup(body);
524 http_conn->content_type = g_strdup(content_type);
525 http_conn->callback = callback;
526 http_conn->data = data;
528 http_conn_post0(http_conn, NULL);
531 static void
532 http_conn_process_input_message(HttpConn *http_conn,
533 struct sipmsg *msg)
535 /* Redirect */
536 if (msg->response == 300 ||
537 msg->response == 301 ||
538 msg->response == 302 ||
539 msg->response == 307)
541 const char *location = sipmsg_find_header(msg, "Location");
543 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location ? location : "");
545 http_conn->do_close = http_conn_clone(http_conn);
546 http_conn->sec_ctx = NULL;
548 g_free(http_conn->host);
549 g_free(http_conn->url);
550 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
552 http_conn->gsc = purple_ssl_connect(http_conn->account,
553 http_conn->host,
554 http_conn->port,
555 http_conn_input0_cb_ssl,
556 http_conn_ssl_connect_failure,
557 http_conn);
560 /* Authentication required */
561 else if (msg->response == 401) {
562 char *ptmp;
563 #ifdef _WIN32
564 #ifdef HAVE_KERBEROS
565 char *tmp;
566 #endif
567 #endif
568 SipSecAuthType auth_type;
569 const char *auth_name;
570 char *authorization;
571 char *output_toked_base64;
572 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
573 long ret = -1;
575 http_conn->retries++;
576 if (http_conn->retries > 2) {
577 if (http_conn->callback) {
578 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
580 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
581 http_conn_set_close(http_conn);
582 return;
585 ptmp = sipmsg_find_auth_header(msg, "NTLM");
586 auth_type = AUTH_TYPE_NTLM;
587 auth_name = "NTLM";
588 #ifdef _WIN32
589 #ifdef HAVE_KERBEROS
590 tmp = sipmsg_find_auth_header(msg, "Negotiate");
591 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
592 ptmp = tmp;
593 auth_type = AUTH_TYPE_NEGOTIATE;
594 auth_name = "Negotiate";
596 #endif
597 #endif
598 if (!ptmp) {
599 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
600 #ifdef _WIN32
601 #ifdef HAVE_KERBEROS
602 "NTLM and Negotiate authentications are"
603 #else /* !HAVE_KERBEROS */
604 "NTLM authentication is"
605 #endif /* HAVE_KERBEROS */
606 #else /* !_WIN32 */
607 "NTLM authentication is"
608 #endif /* _WIN32 */
613 if (!http_conn->sec_ctx) {
614 http_conn->sec_ctx =
615 sip_sec_create_context(auth_type,
616 use_sso,
618 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
619 http_conn->auth ? http_conn->auth->user : NULL,
620 http_conn->auth ? http_conn->auth->password : NULL);
623 if (http_conn->sec_ctx) {
624 char **parts = g_strsplit(ptmp, " ", 0);
625 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
626 ret = sip_sec_init_context_step(http_conn->sec_ctx,
627 spn,
628 parts[1],
629 &output_toked_base64,
630 NULL);
631 g_free(spn);
632 g_strfreev(parts);
635 if (ret < 0) {
636 if (http_conn->callback) {
637 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
639 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
640 http_conn_set_close(http_conn);
641 return;
644 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
645 g_free(output_toked_base64);
647 http_conn_post0(http_conn, authorization);
648 g_free(authorization);
650 /* Other response */
651 else {
652 http_conn->retries = 0;
654 if (http_conn->callback) {
655 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
663 Local Variables:
664 mode: c
665 c-file-style: "bsd"
666 indent-tabs-mode: t
667 tab-width: 8
668 End: