mingw: fixed Windows build
[siplcs.git] / src / core / http-conn.c
blob11e09461da09b692e13d14d484b62ccc58178f22
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>
36 #include "debug.h"
37 #include "sipe.h"
38 #include "sipe-utils.h"
40 #include "http-conn.h"
42 /**
43 * HTTP POST headers
44 * @param url (%s) Ex.: /EWS/Exchange.asmx
45 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
46 * @param content_length (%d) length of body part
47 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
49 #define HTTP_CONN_POST_HEADER \
50 "POST %s HTTP/1.1\r\n"\
51 "Host: %s\r\n"\
52 "User-Agent: Sipe/" SIPE_VERSION "\r\n"\
53 "Content-Length: %d\r\n"\
54 "Content-Type: %s\r\n"
57 struct http_conn_struct {
58 PurpleAccount *account;
59 char *conn_type;
60 char *host;
61 int port;
62 char *url;
63 char *body;
64 char *content_type;
65 HttpConnAuth *auth;
66 HttpConnCallback callback;
67 void *data;
69 /* SSL connection */
70 PurpleSslConnection *gsc;
71 int fd;
72 int listenport;
73 time_t last_keepalive;
74 struct sip_connection *conn;
75 SipSecContext sec_ctx;
76 int retries;
78 HttpConn* do_close;
81 static HttpConn*
82 http_conn_clone(HttpConn* http_conn)
84 HttpConn *res = g_new0(HttpConn, 1);
86 res->account = http_conn->account;
87 res->conn_type = g_strdup(http_conn->conn_type);
88 res->host = g_strdup(http_conn->host);
89 res->port = http_conn->port;
90 res->url = g_strdup(http_conn->url);
91 res->body = g_strdup(http_conn->body);
92 res->content_type = g_strdup(http_conn->content_type);
93 res->auth = http_conn->auth;
94 res->callback = http_conn->callback;
95 res->data = http_conn->data;
97 /* SSL connection */
98 res->gsc = http_conn->gsc;
99 res->fd = http_conn->fd;
100 res->listenport = http_conn->listenport;
101 res->last_keepalive = http_conn->last_keepalive;
102 res->conn = http_conn->conn;
103 res->sec_ctx = http_conn->sec_ctx;
104 res->retries = http_conn->retries;
106 res->do_close = NULL;
108 return res;
111 static void
112 http_conn_free(HttpConn* http_conn)
114 if (!http_conn) return;
116 g_free(http_conn->conn_type);
117 g_free(http_conn->host);
118 g_free(http_conn->url);
119 g_free(http_conn->body);
120 g_free(http_conn->content_type);
122 if (http_conn->sec_ctx) {
123 sip_sec_destroy_context(http_conn->sec_ctx);
126 g_free(http_conn);
129 void
130 http_conn_auth_free(struct http_conn_auth* auth)
132 g_free(auth->domain);
133 g_free(auth->user);
134 g_free(auth->password);
135 g_free(auth);
138 void
139 http_conn_set_close(HttpConn* http_conn)
141 http_conn->do_close = http_conn;
144 static void
145 http_conn_invalidate_ssl_connection(HttpConn *http_conn);
147 static void
148 http_conn_close(HttpConn *http_conn, const char *message)
150 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message ? message : "");
152 http_conn_invalidate_ssl_connection(http_conn);
153 http_conn_free(http_conn);
157 * Extracts host, port and relative url
158 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
160 * Allocates memory, must be g_free'd.
162 static void
163 http_conn_parse_url(const char *url,
164 char **host,
165 int *port,
166 char **rel_url)
168 char **parts = g_strsplit(url, "://", 2);
169 char *no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
170 int port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
171 char *tmp;
172 char *host_port;
174 g_strfreev(parts);
175 tmp = strstr(no_proto, "/");
176 if (tmp && rel_url) *rel_url = g_strdup(tmp);
177 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
178 g_free(no_proto);
180 parts = g_strsplit(host_port, ":", 2);
181 if (host) *host = g_strdup(parts[0]);
182 if (port) *port = parts[1] ? atoi(parts[1]) : port_tmp;
183 g_strfreev(parts);
185 g_free(host_port);
188 static void
189 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
190 PurpleSslErrorType error,
191 gpointer data)
193 HttpConn *http_conn = data;
194 const char *message = NULL;
196 http_conn->gsc = NULL;
198 switch(error) {
199 case PURPLE_SSL_CONNECT_FAILED:
200 message = "Connection failed";
201 break;
202 case PURPLE_SSL_HANDSHAKE_FAILED:
203 message = "SSL handshake failed";
204 break;
205 case PURPLE_SSL_CERTIFICATE_INVALID:
206 message = "SSL certificate invalid";
207 break;
210 if (http_conn->callback) {
211 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
213 http_conn_close(http_conn, message);
216 static void
217 http_conn_connection_remove(struct sip_connection *conn)
219 if (conn) {
220 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
221 g_free(conn->inbuf);
222 g_free(conn);
226 static void
227 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
229 if (http_conn) {
230 PurpleSslConnection *gsc = http_conn->gsc;
232 /* Invalidate this connection. Next send will open a new one */
233 if (gsc) {
234 struct sip_connection *conn = http_conn->conn;
236 http_conn_connection_remove(conn);
237 http_conn->conn = NULL;
238 purple_ssl_close(gsc);
240 http_conn->gsc = NULL;
241 http_conn->fd = -1;
245 static void
246 http_conn_process_input(HttpConn *http_conn);
248 static void
249 http_conn_input_cb_ssl(gpointer data,
250 PurpleSslConnection *gsc,
251 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
253 HttpConn *http_conn = data;
254 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
255 int readlen, len;
256 gboolean firstread = TRUE;
258 if (conn == NULL) {
259 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
260 return;
263 /* Read all available data from the SSL connection */
264 do {
265 /* Increase input buffer size as needed */
266 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
267 conn->inbuflen += SIMPLE_BUF_INC;
268 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
269 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
272 /* Try to read as much as there is space left in the buffer */
273 readlen = conn->inbuflen - conn->inbufused - 1;
274 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
276 if (len < 0 && errno == EAGAIN) {
277 /* Try again later */
278 return;
279 } else if (len < 0) {
280 if (http_conn->callback) {
281 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
283 http_conn_close(http_conn, "SSL read error");
284 return;
285 } else if (firstread && (len == 0)) {
286 if (http_conn->callback) {
287 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
289 http_conn_close(http_conn, "Server has disconnected");
290 return;
293 conn->inbufused += len;
294 firstread = FALSE;
296 /* Equivalence indicates that there is possibly more data to read */
297 } while (len == readlen);
299 conn->inbuf[conn->inbufused] = '\0';
300 http_conn_process_input(http_conn);
302 static void
303 http_conn_post0(HttpConn *http_conn,
304 const char *authorization);
306 static void
307 http_conn_input0_cb_ssl(gpointer data,
308 PurpleSslConnection *gsc,
309 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
311 HttpConn *http_conn = data;
313 http_conn->fd = gsc->fd;
314 http_conn->gsc = gsc;
315 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
316 //http_conn->connecting = FALSE;
317 http_conn->last_keepalive = time(NULL);
319 http_conn->conn = g_new0(struct sip_connection, 1);
321 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
323 http_conn_post0(http_conn, NULL);
326 HttpConn *
327 http_conn_create(PurpleAccount *account,
328 const char *conn_type,
329 const char *full_url,
330 const char *body,
331 const char *content_type,
332 HttpConnAuth *auth,
333 HttpConnCallback callback,
334 void *data)
336 HttpConn *http_conn;
338 if (!full_url || (strlen(full_url) == 0)) {
339 purple_debug_info("sipe-http", "no URL supplied!\n");
340 return NULL;
342 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
343 !purple_ssl_is_supported())
345 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");
346 return NULL;
349 http_conn = g_new0(HttpConn, 1);
350 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
352 http_conn->account = account;
353 http_conn->conn_type = g_strdup(conn_type);
354 http_conn->body = g_strdup(body);
355 http_conn->content_type = g_strdup(content_type);
356 http_conn->auth = auth;
357 http_conn->callback = callback;
358 http_conn->data = data;
360 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
361 http_conn->host,
362 http_conn->port,
363 http_conn_input0_cb_ssl,
364 http_conn_ssl_connect_failure,
365 http_conn);
367 return http_conn;
370 /* Data part */
371 static void
372 http_conn_process_input_message(HttpConn *http_conn,
373 struct sipmsg *msg);
375 static void
376 http_conn_process_input(HttpConn *http_conn)
378 char *cur;
379 char *dummy;
380 char *tmp;
381 struct sipmsg *msg;
382 int restlen;
383 struct sip_connection *conn = http_conn->conn;
385 cur = conn->inbuf;
387 /* according to the RFC remove CRLF at the beginning */
388 while (*cur == '\r' || *cur == '\n') {
389 cur++;
391 if (cur != conn->inbuf) {
392 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
393 conn->inbufused = strlen(conn->inbuf);
396 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
397 time_t currtime = time(NULL);
398 cur += 2;
399 cur[0] = '\0';
400 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
401 g_free(tmp);
403 msg = sipmsg_parse_header(conn->inbuf);
404 cur[0] = '\r';
405 cur += 2;
406 restlen = conn->inbufused - (cur - conn->inbuf);
407 if (msg && restlen >= msg->bodylen) {
408 dummy = g_malloc(msg->bodylen + 1);
409 memcpy(dummy, cur, msg->bodylen);
410 dummy[msg->bodylen] = '\0';
411 msg->body = dummy;
412 cur += msg->bodylen;
413 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
414 conn->inbufused = strlen(conn->inbuf);
415 } else {
416 if (msg){
417 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
418 sipmsg_free(msg);
420 return;
423 if (msg->body) {
424 purple_debug_info("sipe-http", "body:\n%s\n", msg->body);
427 http_conn_process_input_message(http_conn, msg);
429 sipmsg_free(msg);
432 if (http_conn->do_close) {
433 http_conn_close(http_conn->do_close, "User initiated");
437 static void
438 http_conn_sendout_pkt(HttpConn *http_conn,
439 const char *buf)
441 time_t currtime = time(NULL);
442 int writelen = strlen(buf);
443 char *tmp;
444 int ret = 0;
446 purple_debug(PURPLE_DEBUG_MISC, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(buf));
447 g_free(tmp);
449 if (http_conn->fd < 0) {
450 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
451 return;
454 if (http_conn->gsc) {
455 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
458 if (ret < 0 && errno == EAGAIN)
459 ret = 0;
460 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
461 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
462 return;
465 if (ret < writelen) {
466 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
470 static void
471 http_conn_post0(HttpConn *http_conn,
472 const char *authorization)
474 GString *outstr = g_string_new("");
476 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
477 http_conn->url,
478 http_conn->host,
479 http_conn->body ? (int)strlen(http_conn->body) : 0,
480 http_conn->content_type ? http_conn->content_type : "text/plain");
481 if (authorization) {
482 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
484 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
486 http_conn_sendout_pkt(http_conn, outstr->str);
487 g_string_free(outstr, TRUE);
490 void
491 http_conn_post( HttpConn *http_conn,
492 const char *full_url,
493 const char *body,
494 const char *content_type,
495 HttpConnCallback callback,
496 void *data)
498 if (!http_conn) {
499 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
500 return;
503 g_free(http_conn->url);
504 g_free(http_conn->body);
505 g_free(http_conn->content_type);
506 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
507 http_conn->body = g_strdup(body);
508 http_conn->content_type = g_strdup(content_type);
509 http_conn->callback = callback;
510 http_conn->data = data;
512 http_conn_post0(http_conn, NULL);
515 static void
516 http_conn_process_input_message(HttpConn *http_conn,
517 struct sipmsg *msg)
519 /* Redirect */
520 if (msg->response == 300 ||
521 msg->response == 301 ||
522 msg->response == 302 ||
523 msg->response == 307)
525 const char *location = sipmsg_find_header(msg, "Location");
527 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location ? location : "");
529 http_conn->do_close = http_conn_clone(http_conn);
530 http_conn->sec_ctx = NULL;
532 g_free(http_conn->host);
533 g_free(http_conn->url);
534 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
536 http_conn->gsc = purple_ssl_connect(http_conn->account,
537 http_conn->host,
538 http_conn->port,
539 http_conn_input0_cb_ssl,
540 http_conn_ssl_connect_failure,
541 http_conn);
544 /* Authentication required */
545 else if (msg->response == 401) {
546 char *ptmp;
547 #ifdef _WIN32
548 #ifdef USE_KERBEROS
549 char *tmp;
550 #endif
551 #endif
552 SipSecAuthType auth_type;
553 const char *auth_name;
554 char *authorization;
555 char *output_toked_base64;
556 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
557 long ret = -1;
559 http_conn->retries++;
560 if (http_conn->retries > 2) {
561 if (http_conn->callback) {
562 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
564 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
565 http_conn_set_close(http_conn);
566 return;
569 ptmp = sipmsg_find_auth_header(msg, "NTLM");
570 auth_type = AUTH_TYPE_NTLM;
571 auth_name = "NTLM";
572 #ifdef _WIN32
573 #ifdef USE_KERBEROS
574 tmp = sipmsg_find_auth_header(msg, "Negotiate");
575 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
576 ptmp = tmp;
577 auth_type = AUTH_TYPE_NEGOTIATE;
578 auth_name = "Negotiate";
580 #endif
581 #endif
582 if (!ptmp) {
583 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
584 #ifdef _WIN32
585 #ifdef USE_KERBEROS
586 "NTLM and Negotiate authentications are"
587 #else //USE_KERBEROS
588 "NTLM authentication is"
589 #endif //USE_KERBEROS
590 #else //_WIN32
591 "NTLM authentication is"
592 #endif //_WIN32
597 if (!http_conn->sec_ctx) {
598 http_conn->sec_ctx =
599 sip_sec_create_context(auth_type,
600 use_sso,
602 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
603 http_conn->auth ? http_conn->auth->user : NULL,
604 http_conn->auth ? http_conn->auth->password : NULL);
607 if (http_conn->sec_ctx) {
608 char **parts = g_strsplit(ptmp, " ", 0);
609 char *spn = g_strdup_printf("HTTP/%s", http_conn->host);
610 ret = sip_sec_init_context_step(http_conn->sec_ctx,
611 spn,
612 parts[1],
613 &output_toked_base64,
614 NULL);
615 g_free(spn);
616 g_strfreev(parts);
619 if (ret < 0) {
620 if (http_conn->callback) {
621 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
623 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
624 http_conn_set_close(http_conn);
625 return;
628 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
629 g_free(output_toked_base64);
631 http_conn_post0(http_conn, authorization);
632 g_free(authorization);
634 /* Other response */
635 else {
636 http_conn->retries = 0;
638 if (http_conn->callback) {
639 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
647 Local Variables:
648 mode: c
649 c-file-style: "bsd"
650 indent-tabs-mode: t
651 tab-width: 8
652 End: