core cleanup: rename core & backend API headers
[siplcs.git] / src / core / http-conn.c
blob62fe96eb1b157ab3761e306e647025ee443759f3
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 "sipe-common.h"
46 #include "sipmsg.h"
47 #include "sip-sec.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 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", 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 = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
180 int port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
181 char *tmp;
182 char *host_port;
184 g_strfreev(parts);
185 tmp = strstr(no_proto, "/");
186 if (tmp && rel_url) *rel_url = g_strdup(tmp);
187 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
188 g_free(no_proto);
190 parts = g_strsplit(host_port, ":", 2);
191 if (host) *host = g_strdup(parts[0]);
192 if (port) *port = parts[1] ? atoi(parts[1]) : port_tmp;
193 g_strfreev(parts);
195 g_free(host_port);
198 static void
199 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
200 PurpleSslErrorType error,
201 gpointer data)
203 HttpConn *http_conn = data;
204 const char *message = NULL;
206 http_conn->gsc = NULL;
208 switch(error) {
209 case PURPLE_SSL_CONNECT_FAILED:
210 message = "Connection failed";
211 break;
212 case PURPLE_SSL_HANDSHAKE_FAILED:
213 message = "SSL handshake failed";
214 break;
215 case PURPLE_SSL_CERTIFICATE_INVALID:
216 message = "SSL certificate invalid";
217 break;
220 if (http_conn->callback) {
221 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
223 http_conn_close(http_conn, message);
226 static void
227 http_conn_connection_remove(struct sip_connection *conn)
229 if (conn) {
230 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
231 g_free(conn->inbuf);
232 g_free(conn);
236 static void
237 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
239 if (http_conn) {
240 PurpleSslConnection *gsc = http_conn->gsc;
242 /* Invalidate this connection. Next send will open a new one */
243 if (gsc) {
244 struct sip_connection *conn = http_conn->conn;
246 http_conn_connection_remove(conn);
247 http_conn->conn = NULL;
248 purple_ssl_close(gsc);
250 http_conn->gsc = NULL;
251 http_conn->fd = -1;
255 static void
256 http_conn_process_input(HttpConn *http_conn);
258 static void
259 http_conn_input_cb_ssl(gpointer data,
260 PurpleSslConnection *gsc,
261 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
263 HttpConn *http_conn = data;
264 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
265 int readlen, len;
266 gboolean firstread = TRUE;
268 if (conn == NULL) {
269 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
270 return;
273 /* Read all available data from the SSL connection */
274 do {
275 /* Increase input buffer size as needed */
276 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
277 conn->inbuflen += SIMPLE_BUF_INC;
278 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
279 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
282 /* Try to read as much as there is space left in the buffer */
283 readlen = conn->inbuflen - conn->inbufused - 1;
284 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
286 if (len < 0 && errno == EAGAIN) {
287 /* Try again later */
288 return;
289 } else if (len < 0) {
290 if (http_conn->callback) {
291 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
293 http_conn_close(http_conn, "SSL read error");
294 return;
295 } else if (firstread && (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, "Server has disconnected");
300 return;
303 conn->inbufused += len;
304 firstread = FALSE;
306 /* Equivalence indicates that there is possibly more data to read */
307 } while (len == readlen);
309 conn->inbuf[conn->inbufused] = '\0';
310 http_conn_process_input(http_conn);
312 static void
313 http_conn_post0(HttpConn *http_conn,
314 const char *authorization);
316 static void
317 http_conn_input0_cb_ssl(gpointer data,
318 PurpleSslConnection *gsc,
319 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
321 HttpConn *http_conn = data;
323 http_conn->fd = gsc->fd;
324 http_conn->gsc = gsc;
325 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
326 //http_conn->connecting = FALSE;
327 http_conn->last_keepalive = time(NULL);
329 http_conn->conn = g_new0(struct sip_connection, 1);
331 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
333 http_conn_post0(http_conn, NULL);
336 HttpConn *
337 http_conn_create(PurpleAccount *account,
338 const char *conn_type,
339 const char *full_url,
340 const char *body,
341 const char *content_type,
342 HttpConnAuth *auth,
343 HttpConnCallback callback,
344 void *data)
346 HttpConn *http_conn;
348 if (!full_url || (strlen(full_url) == 0)) {
349 purple_debug_info("sipe-http", "no URL supplied!\n");
350 return NULL;
352 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
353 !purple_ssl_is_supported())
355 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");
356 return NULL;
359 http_conn = g_new0(HttpConn, 1);
360 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
362 http_conn->account = account;
363 http_conn->conn_type = g_strdup(conn_type);
364 http_conn->body = g_strdup(body);
365 http_conn->content_type = g_strdup(content_type);
366 http_conn->auth = auth;
367 http_conn->callback = callback;
368 http_conn->data = data;
370 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
371 http_conn->host,
372 http_conn->port,
373 http_conn_input0_cb_ssl,
374 http_conn_ssl_connect_failure,
375 http_conn);
377 return http_conn;
380 /* Data part */
381 static void
382 http_conn_process_input_message(HttpConn *http_conn,
383 struct sipmsg *msg);
385 static void
386 http_conn_process_input(HttpConn *http_conn)
388 char *cur;
389 char *dummy;
390 char *tmp;
391 struct sipmsg *msg;
392 int restlen;
393 struct sip_connection *conn = http_conn->conn;
395 cur = conn->inbuf;
397 /* according to the RFC remove CRLF at the beginning */
398 while (*cur == '\r' || *cur == '\n') {
399 cur++;
401 if (cur != conn->inbuf) {
402 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
403 conn->inbufused = strlen(conn->inbuf);
406 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
407 time_t currtime = time(NULL);
408 cur += 2;
409 cur[0] = '\0';
410 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
411 g_free(tmp);
413 msg = sipmsg_parse_header(conn->inbuf);
414 cur[0] = '\r';
415 cur += 2;
416 restlen = conn->inbufused - (cur - conn->inbuf);
417 if (msg && restlen >= msg->bodylen) {
418 dummy = g_malloc(msg->bodylen + 1);
419 memcpy(dummy, cur, msg->bodylen);
420 dummy[msg->bodylen] = '\0';
421 msg->body = dummy;
422 cur += msg->bodylen;
423 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
424 conn->inbufused = strlen(conn->inbuf);
425 } else {
426 if (msg){
427 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
428 sipmsg_free(msg);
430 return;
433 if (msg->body) {
434 purple_debug_info("sipe-http", "body:\n%s\n", msg->body);
437 http_conn_process_input_message(http_conn, msg);
439 sipmsg_free(msg);
442 if (http_conn->do_close) {
443 http_conn_close(http_conn->do_close, "User initiated");
447 static void
448 http_conn_sendout_pkt(HttpConn *http_conn,
449 const char *buf)
451 time_t currtime = time(NULL);
452 int writelen = strlen(buf);
453 char *tmp;
454 int ret = 0;
456 purple_debug(PURPLE_DEBUG_MISC, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(buf));
457 g_free(tmp);
459 if (http_conn->fd < 0) {
460 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
461 return;
464 if (http_conn->gsc) {
465 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
468 if (ret < 0 && errno == EAGAIN)
469 ret = 0;
470 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
471 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
472 return;
475 if (ret < writelen) {
476 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
480 static void
481 http_conn_post0(HttpConn *http_conn,
482 const char *authorization)
484 GString *outstr = g_string_new("");
486 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
487 http_conn->url,
488 http_conn->host,
489 http_conn->body ? (int)strlen(http_conn->body) : 0,
490 http_conn->content_type ? http_conn->content_type : "text/plain");
491 if (authorization) {
492 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
494 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
496 http_conn_sendout_pkt(http_conn, outstr->str);
497 g_string_free(outstr, TRUE);
500 void
501 http_conn_post( HttpConn *http_conn,
502 const char *full_url,
503 const char *body,
504 const char *content_type,
505 HttpConnCallback callback,
506 void *data)
508 if (!http_conn) {
509 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
510 return;
513 g_free(http_conn->url);
514 g_free(http_conn->body);
515 g_free(http_conn->content_type);
516 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
517 http_conn->body = g_strdup(body);
518 http_conn->content_type = g_strdup(content_type);
519 http_conn->callback = callback;
520 http_conn->data = data;
522 http_conn_post0(http_conn, NULL);
525 static void
526 http_conn_process_input_message(HttpConn *http_conn,
527 struct sipmsg *msg)
529 /* Redirect */
530 if (msg->response == 300 ||
531 msg->response == 301 ||
532 msg->response == 302 ||
533 msg->response == 307)
535 const char *location = sipmsg_find_header(msg, "Location");
537 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location ? location : "");
539 http_conn->do_close = http_conn_clone(http_conn);
540 http_conn->sec_ctx = NULL;
542 g_free(http_conn->host);
543 g_free(http_conn->url);
544 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
546 http_conn->gsc = purple_ssl_connect(http_conn->account,
547 http_conn->host,
548 http_conn->port,
549 http_conn_input0_cb_ssl,
550 http_conn_ssl_connect_failure,
551 http_conn);
554 /* Authentication required */
555 else if (msg->response == 401) {
556 char *ptmp;
557 #ifdef _WIN32
558 #ifdef HAVE_KERBEROS
559 char *tmp;
560 #endif
561 #endif
562 SipSecAuthType auth_type;
563 const char *auth_name;
564 char *authorization;
565 char *output_toked_base64;
566 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
567 long ret = -1;
569 http_conn->retries++;
570 if (http_conn->retries > 2) {
571 if (http_conn->callback) {
572 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
574 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
575 http_conn_set_close(http_conn);
576 return;
579 ptmp = sipmsg_find_auth_header(msg, "NTLM");
580 auth_type = AUTH_TYPE_NTLM;
581 auth_name = "NTLM";
582 #ifdef _WIN32
583 #ifdef HAVE_KERBEROS
584 tmp = sipmsg_find_auth_header(msg, "Negotiate");
585 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
586 ptmp = tmp;
587 auth_type = AUTH_TYPE_NEGOTIATE;
588 auth_name = "Negotiate";
590 #endif
591 #endif
592 if (!ptmp) {
593 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
594 #ifdef _WIN32
595 #ifdef HAVE_KERBEROS
596 "NTLM and Negotiate authentications are"
597 #else /* !HAVE_KERBEROS */
598 "NTLM authentication is"
599 #endif /* HAVE_KERBEROS */
600 #else /* !_WIN32 */
601 "NTLM authentication is"
602 #endif /* _WIN32 */
607 if (!http_conn->sec_ctx) {
608 http_conn->sec_ctx =
609 sip_sec_create_context(auth_type,
610 use_sso,
612 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
613 http_conn->auth ? http_conn->auth->user : NULL,
614 http_conn->auth ? http_conn->auth->password : NULL);
617 if (http_conn->sec_ctx) {
618 char **parts = g_strsplit(ptmp, " ", 0);
619 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
620 ret = sip_sec_init_context_step(http_conn->sec_ctx,
621 spn,
622 parts[1],
623 &output_toked_base64,
624 NULL);
625 g_free(spn);
626 g_strfreev(parts);
629 if (ret < 0) {
630 if (http_conn->callback) {
631 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
633 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
634 http_conn_set_close(http_conn);
635 return;
638 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
639 g_free(output_toked_base64);
641 http_conn_post0(http_conn, authorization);
642 g_free(authorization);
644 /* Other response */
645 else {
646 http_conn->retries = 0;
648 if (http_conn->callback) {
649 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
657 Local Variables:
658 mode: c
659 c-file-style: "bsd"
660 indent-tabs-mode: t
661 tab-width: 8
662 End: