Fix for #2949116: Pidgin 2.6.5 crashes with sipe 1.8.0
[siplcs.git] / src / core / http-conn.c
blob6576190066e950f92deb5b669229e72d7433ab2c
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 #include <string.h>
30 #include <errno.h>
32 #include "debug.h"
33 #include "sipe.h"
34 #include "sipe-utils.h"
36 #include "http-conn.h"
38 /**
39 * HTTP POST headers
40 * @param url (%s) Ex.: /EWS/Exchange.asmx
41 * @param host (%s) Ex.: cosmo-ocs-r2.cosmo.local
42 * @param content_length (%d) length of body part
43 * @param content_type (%s) Ex.: text/xml; charset=UTF-8
45 #define HTTP_CONN_POST_HEADER \
46 "POST %s HTTP/1.1\r\n"\
47 "Host: %s\r\n"\
48 "User-Agent: Sipe/" SIPE_VERSION "\r\n"\
49 "Content-Length: %d\r\n"\
50 "Content-Type: %s\r\n"
53 struct http_conn_struct {
54 PurpleAccount *account;
55 char *conn_type;
56 char *host;
57 int port;
58 char *url;
59 char *body;
60 char *content_type;
61 HttpConnAuth *auth;
62 HttpConnCallback callback;
63 void *data;
65 /* SSL connection */
66 PurpleSslConnection *gsc;
67 int fd;
68 int listenport;
69 time_t last_keepalive;
70 struct sip_connection *conn;
71 SipSecContext sec_ctx;
72 int retries;
74 HttpConn* do_close;
77 static HttpConn*
78 http_conn_clone(HttpConn* http_conn)
80 HttpConn *res = g_new0(HttpConn, 1);
82 res->account = http_conn->account;
83 res->conn_type = g_strdup(http_conn->conn_type);
84 res->host = g_strdup(http_conn->host);
85 res->port = http_conn->port;
86 res->url = g_strdup(http_conn->url);
87 res->body = g_strdup(http_conn->body);
88 res->content_type = g_strdup(http_conn->content_type);
89 res->auth = http_conn->auth;
90 res->callback = http_conn->callback;
91 res->data = http_conn->data;
93 /* SSL connection */
94 res->gsc = http_conn->gsc;
95 res->fd = http_conn->fd;
96 res->listenport = http_conn->listenport;
97 res->last_keepalive = http_conn->last_keepalive;
98 res->conn = http_conn->conn;
99 res->sec_ctx = http_conn->sec_ctx;
100 res->retries = http_conn->retries;
102 res->do_close = NULL;
104 return res;
107 static void
108 http_conn_free(HttpConn* http_conn)
110 if (!http_conn) return;
112 g_free(http_conn->conn_type);
113 g_free(http_conn->host);
114 g_free(http_conn->url);
115 g_free(http_conn->body);
116 g_free(http_conn->content_type);
118 if (http_conn->sec_ctx) {
119 sip_sec_destroy_context(http_conn->sec_ctx);
122 g_free(http_conn);
125 void
126 http_conn_auth_free(struct http_conn_auth* auth)
128 g_free(auth->domain);
129 g_free(auth->user);
130 g_free(auth->password);
131 g_free(auth);
134 void
135 http_conn_set_close(HttpConn* http_conn)
137 http_conn->do_close = http_conn;
140 static void
141 http_conn_invalidate_ssl_connection(HttpConn *http_conn);
143 static void
144 http_conn_close(HttpConn *http_conn, const char *message)
146 purple_debug_info("sipe-http", "http_conn_close: closing http connection: %s\n", message ? message : "");
148 http_conn_invalidate_ssl_connection(http_conn);
149 http_conn_free(http_conn);
153 * Extracts host, port and relative url
154 * Ex. url: https://machine.domain.Contoso.com/EWS/Exchange.asmx
156 * Allocates memory, must be g_free'd.
158 static void
159 http_conn_parse_url(const char *url,
160 char **host,
161 int *port,
162 char **rel_url)
164 char **parts = g_strsplit(url, "://", 2);
165 char *no_proto = parts[1] ? g_strdup(parts[1]) : g_strdup(parts[0]);
166 int port_tmp = sipe_strequal(parts[0], "https") ? 443 : 80;
167 char *tmp;
168 char *host_port;
170 g_strfreev(parts);
171 tmp = strstr(no_proto, "/");
172 if (tmp && rel_url) *rel_url = g_strdup(tmp);
173 host_port = tmp ? g_strndup(no_proto, tmp - no_proto) : g_strdup(no_proto);
174 g_free(no_proto);
176 parts = g_strsplit(host_port, ":", 2);
177 if (host) *host = g_strdup(parts[0]);
178 if (port) *port = parts[1] ? atoi(parts[1]) : port_tmp;
179 g_strfreev(parts);
181 g_free(host_port);
184 static void
185 http_conn_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
186 PurpleSslErrorType error,
187 gpointer data)
189 HttpConn *http_conn = data;
190 const char *message = NULL;
192 http_conn->gsc = NULL;
194 switch(error) {
195 case PURPLE_SSL_CONNECT_FAILED:
196 message = "Connection failed";
197 break;
198 case PURPLE_SSL_HANDSHAKE_FAILED:
199 message = "SSL handshake failed";
200 break;
201 case PURPLE_SSL_CERTIFICATE_INVALID:
202 message = "SSL certificate invalid";
203 break;
206 if (http_conn->callback) {
207 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
209 http_conn_close(http_conn, message);
212 static void
213 http_conn_connection_remove(struct sip_connection *conn)
215 if (conn) {
216 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
217 g_free(conn->inbuf);
218 g_free(conn);
222 static void
223 http_conn_invalidate_ssl_connection(HttpConn *http_conn)
225 if (http_conn) {
226 PurpleSslConnection *gsc = http_conn->gsc;
228 /* Invalidate this connection. Next send will open a new one */
229 if (gsc) {
230 struct sip_connection *conn = http_conn->conn;
232 http_conn_connection_remove(conn);
233 http_conn->conn = NULL;
234 purple_ssl_close(gsc);
236 http_conn->gsc = NULL;
237 http_conn->fd = -1;
241 static void
242 http_conn_process_input(HttpConn *http_conn);
244 static void
245 http_conn_input_cb_ssl(gpointer data,
246 PurpleSslConnection *gsc,
247 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
249 HttpConn *http_conn = data;
250 struct sip_connection *conn = http_conn ? http_conn->conn : NULL;
251 int readlen, len;
252 gboolean firstread = TRUE;
254 if (conn == NULL) {
255 purple_debug_error("sipe-http", "Connection not found; Please try to connect again.\n");
256 return;
259 /* Read all available data from the SSL connection */
260 do {
261 /* Increase input buffer size as needed */
262 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
263 conn->inbuflen += SIMPLE_BUF_INC;
264 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
265 purple_debug_info("sipe-http", "http_conn_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
268 /* Try to read as much as there is space left in the buffer */
269 readlen = conn->inbuflen - conn->inbufused - 1;
270 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
272 if (len < 0 && errno == EAGAIN) {
273 /* Try again later */
274 return;
275 } else if (len < 0) {
276 if (http_conn->callback) {
277 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
279 http_conn_close(http_conn, "SSL read error");
280 return;
281 } else if (firstread && (len == 0)) {
282 if (http_conn->callback) {
283 (*http_conn->callback)(HTTP_CONN_ERROR, NULL, http_conn, http_conn->data);
285 http_conn_close(http_conn, "Server has disconnected");
286 return;
289 conn->inbufused += len;
290 firstread = FALSE;
292 /* Equivalence indicates that there is possibly more data to read */
293 } while (len == readlen);
295 conn->inbuf[conn->inbufused] = '\0';
296 http_conn_process_input(http_conn);
298 static void
299 http_conn_post0(HttpConn *http_conn,
300 const char *authorization);
302 static void
303 http_conn_input0_cb_ssl(gpointer data,
304 PurpleSslConnection *gsc,
305 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
307 HttpConn *http_conn = data;
309 http_conn->fd = gsc->fd;
310 http_conn->gsc = gsc;
311 http_conn->listenport = purple_network_get_port_from_fd(gsc->fd);
312 //http_conn->connecting = FALSE;
313 http_conn->last_keepalive = time(NULL);
315 http_conn->conn = g_new0(struct sip_connection, 1);
317 purple_ssl_input_add(gsc, http_conn_input_cb_ssl, http_conn);
319 http_conn_post0(http_conn, NULL);
322 HttpConn *
323 http_conn_create(PurpleAccount *account,
324 const char *conn_type,
325 const char *full_url,
326 const char *body,
327 const char *content_type,
328 HttpConnAuth *auth,
329 HttpConnCallback callback,
330 void *data)
332 HttpConn *http_conn;
334 if (!full_url || (strlen(full_url) == 0)) {
335 purple_debug_info("sipe-http", "no URL supplied!\n");
336 return NULL;
338 if (sipe_strequal(conn_type, HTTP_CONN_SSL) &&
339 !purple_ssl_is_supported())
341 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");
342 return NULL;
345 http_conn = g_new0(HttpConn, 1);
346 http_conn_parse_url(full_url, &http_conn->host, &http_conn->port, &http_conn->url);
348 http_conn->account = account;
349 http_conn->conn_type = g_strdup(conn_type);
350 http_conn->body = g_strdup(body);
351 http_conn->content_type = g_strdup(content_type);
352 http_conn->auth = auth;
353 http_conn->callback = callback;
354 http_conn->data = data;
356 http_conn->gsc = purple_ssl_connect(http_conn->account, /* can we pass just NULL ? */
357 http_conn->host,
358 http_conn->port,
359 http_conn_input0_cb_ssl,
360 http_conn_ssl_connect_failure,
361 http_conn);
363 return http_conn;
366 /* Data part */
367 static void
368 http_conn_process_input_message(HttpConn *http_conn,
369 struct sipmsg *msg);
371 static void
372 http_conn_process_input(HttpConn *http_conn)
374 char *cur;
375 char *dummy;
376 char *tmp;
377 struct sipmsg *msg;
378 int restlen;
379 struct sip_connection *conn = http_conn->conn;
381 cur = conn->inbuf;
383 /* according to the RFC remove CRLF at the beginning */
384 while (*cur == '\r' || *cur == '\n') {
385 cur++;
387 if (cur != conn->inbuf) {
388 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
389 conn->inbufused = strlen(conn->inbuf);
392 while ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL) {
393 time_t currtime = time(NULL);
394 cur += 2;
395 cur[0] = '\0';
396 purple_debug_info("sipe-http", "received - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(conn->inbuf));
397 g_free(tmp);
399 msg = sipmsg_parse_header(conn->inbuf);
400 cur[0] = '\r';
401 cur += 2;
402 restlen = conn->inbufused - (cur - conn->inbuf);
403 if (msg && restlen >= msg->bodylen) {
404 dummy = g_malloc(msg->bodylen + 1);
405 memcpy(dummy, cur, msg->bodylen);
406 dummy[msg->bodylen] = '\0';
407 msg->body = dummy;
408 cur += msg->bodylen;
409 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
410 conn->inbufused = strlen(conn->inbuf);
411 } else {
412 if (msg){
413 purple_debug_info("sipe-http", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen, msg->bodylen, (int)strlen(conn->inbuf));
414 sipmsg_free(msg);
416 return;
419 if (msg->body) {
420 purple_debug_info("sipe-http", "body:\n%s\n", msg->body);
423 http_conn_process_input_message(http_conn, msg);
425 sipmsg_free(msg);
428 if (http_conn->do_close) {
429 http_conn_close(http_conn->do_close, "User initiated");
433 static void
434 http_conn_sendout_pkt(HttpConn *http_conn,
435 const char *buf)
437 time_t currtime = time(NULL);
438 int writelen = strlen(buf);
439 char *tmp;
440 int ret = 0;
442 purple_debug(PURPLE_DEBUG_MISC, "sipe-http", "sending - %s******\n%s\n******\n", ctime(&currtime), tmp = fix_newlines(buf));
443 g_free(tmp);
445 if (http_conn->fd < 0) {
446 purple_debug_info("sipe-http", "http_conn_sendout_pkt: http_conn->fd < 0, exiting\n");
447 return;
450 if (http_conn->gsc) {
451 ret = purple_ssl_write(http_conn->gsc, buf, writelen);
454 if (ret < 0 && errno == EAGAIN)
455 ret = 0;
456 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
457 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret <= 0, exiting\n");
458 return;
461 if (ret < writelen) {
462 purple_debug_info("sipe-http", "http_conn_sendout_pkt: ret < writelen, exiting\n");
466 static void
467 http_conn_post0(HttpConn *http_conn,
468 const char *authorization)
470 GString *outstr = g_string_new("");
472 g_string_append_printf(outstr, HTTP_CONN_POST_HEADER,
473 http_conn->url,
474 http_conn->host,
475 http_conn->body ? (int)strlen(http_conn->body) : 0,
476 http_conn->content_type ? http_conn->content_type : "text/plain");
477 if (authorization) {
478 g_string_append_printf(outstr, "Authorization: %s\r\n", authorization);
480 g_string_append_printf(outstr, "\r\n%s", http_conn->body ? http_conn->body : "");
482 http_conn_sendout_pkt(http_conn, outstr->str);
483 g_string_free(outstr, TRUE);
486 void
487 http_conn_post( HttpConn *http_conn,
488 const char *full_url,
489 const char *body,
490 const char *content_type,
491 HttpConnCallback callback,
492 void *data)
494 if (!http_conn) {
495 purple_debug_info("sipe-http", "http_conn_post: NULL http_conn, exiting.\n");
496 return;
499 g_free(http_conn->url);
500 g_free(http_conn->body);
501 g_free(http_conn->content_type);
502 http_conn_parse_url(full_url, NULL, NULL, &http_conn->url);
503 http_conn->body = g_strdup(body);
504 http_conn->content_type = g_strdup(content_type);
505 http_conn->callback = callback;
506 http_conn->data = data;
508 http_conn_post0(http_conn, NULL);
511 static void
512 http_conn_process_input_message(HttpConn *http_conn,
513 struct sipmsg *msg)
515 /* Redirect */
516 if (msg->response == 300 ||
517 msg->response == 301 ||
518 msg->response == 302 ||
519 msg->response == 307)
521 char *location = sipmsg_find_header(msg, "Location");
523 purple_debug_info("sipe-http", "http_conn_process_input_message: Redirect to: %s\n", location ? location : "");
525 http_conn->do_close = http_conn_clone(http_conn);
526 http_conn->sec_ctx = NULL;
528 g_free(http_conn->host);
529 g_free(http_conn->url);
530 http_conn_parse_url(location, &http_conn->host, &http_conn->port, &http_conn->url);
532 http_conn->gsc = purple_ssl_connect(http_conn->account,
533 http_conn->host,
534 http_conn->port,
535 http_conn_input0_cb_ssl,
536 http_conn_ssl_connect_failure,
537 http_conn);
540 /* Authentication required */
541 else if (msg->response == 401) {
542 char *ptmp;
543 #ifdef _WIN32
544 #ifdef USE_KERBEROS
545 char *tmp;
546 #endif
547 #endif
548 char **parts;
549 SipSecAuthType auth_type;
550 const char *auth_name;
551 char *authorization;
552 char *output_toked_base64;
553 char *spn;
554 int use_sso = !http_conn->auth || (http_conn->auth && !http_conn->auth->user);
555 long ret;
557 http_conn->retries++;
558 if (http_conn->retries > 2) {
559 if (http_conn->callback) {
560 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
562 purple_debug_info("sipe-http", "http_conn_process_input_message: Authentication failed\n");
563 http_conn_set_close(http_conn);
564 return;
567 ptmp = sipmsg_find_auth_header(msg, "NTLM");
568 auth_type = AUTH_TYPE_NTLM;
569 auth_name = "NTLM";
570 #ifdef _WIN32
571 #ifdef USE_KERBEROS
572 tmp = sipmsg_find_auth_header(msg, "Negotiate");
573 if (tmp && http_conn->auth && http_conn->auth->use_negotiate) {
574 ptmp = tmp;
575 auth_type = AUTH_TYPE_NEGOTIATE;
576 auth_name = "Negotiate";
578 #endif
579 #endif
580 if (!ptmp) {
581 purple_debug_info("sipe-http", "http_conn_process_input_message: Only %s supported in the moment, exiting\n",
582 #ifdef _WIN32
583 #ifdef USE_KERBEROS
584 "NTLM and Negotiate authentications are"
585 #else //USE_KERBEROS
586 "NTLM authentication is"
587 #endif //USE_KERBEROS
588 #else //_WIN32
589 "NTLM authentication is"
590 #endif //_WIN32
595 if (!http_conn->sec_ctx) {
596 sip_sec_create_context(&http_conn->sec_ctx,
597 auth_type,
598 use_sso,
600 http_conn->auth && http_conn->auth->domain ? http_conn->auth->domain : "",
601 http_conn->auth ? http_conn->auth->user : NULL,
602 http_conn->auth ? http_conn->auth->password : NULL);
605 parts = g_strsplit(ptmp, " ", 0);
606 spn = g_strdup_printf("HTTP/%s", http_conn->host);
607 ret = sip_sec_init_context_step(http_conn->sec_ctx,
608 spn,
609 parts[1],
610 &output_toked_base64,
611 NULL);
612 g_free(spn);
613 g_strfreev(parts);
615 if (ret < 0) {
616 if (http_conn->callback) {
617 (*http_conn->callback)(HTTP_CONN_ERROR_FATAL, NULL, http_conn, http_conn->data);
619 purple_debug_info("sipe-http", "http_conn_process_input_message: Failed to initialize security context\n");
620 http_conn_set_close(http_conn);
621 return;
624 authorization = g_strdup_printf("%s %s", auth_name, output_toked_base64 ? output_toked_base64 : "");
625 g_free(output_toked_base64);
627 http_conn_post0(http_conn, authorization);
628 g_free(authorization);
630 /* Other response */
631 else {
632 http_conn->retries = 0;
634 if (http_conn->callback) {
635 (*http_conn->callback)(msg->response, msg->body, http_conn, http_conn->data);
643 Local Variables:
644 mode: c
645 c-file-style: "bsd"
646 indent-tabs-mode: t
647 tab-width: 8
648 End: