core cleanup: refactor SIP transport API to be the same as for HTTP
[siplcs.git] / src / purple / purple-transport.c
blob262bf46e6d0b06656d364b71c12b80ea3ab541d0
1 /**
2 * @file purple-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <errno.h>
28 #include <string.h>
29 #include <time.h>
30 #include <unistd.h>
32 #include <glib.h>
34 #include "sipe-common.h"
36 #include "circbuffer.h"
37 #include "connection.h"
38 #include "network.h"
39 #include "sslconn.h"
41 #include "purple-private.h"
43 #include "sipe-backend.h"
44 #include "sipe-core.h"
45 #include "sipe-nls.h"
47 struct sipe_transport_purple {
48 /* public part shared with core */
49 struct sipe_transport_connection public;
51 /* purple private part */
52 PurpleConnection *gc;
53 PurpleSslConnection *gsc;
54 PurpleCircBuffer *transmit_buffer;
55 guint transmit_handler;
56 guint receive_handler;
57 int socket;
59 #define PURPLE_TRANSPORT ((struct sipe_transport_purple *) conn)
60 #define SIPE_TRANSPORT_CONNECTION ((struct sipe_transport_connection *) transport)
62 #define BUFFER_SIZE_INCREMENT 4096
65 /*****************************************************************************
67 * SIP transport handling
69 *****************************************************************************/
70 static void transport_sip_input_common(struct sipe_transport_purple *transport)
72 struct sipe_transport_connection *conn = SIPE_TRANSPORT_CONNECTION;
73 gssize readlen, len;
74 gboolean firstread = TRUE;
76 /* Read all available data from the connection */
77 do {
78 /* Increase input buffer size as needed */
79 if (conn->buffer_length < conn->buffer_used + BUFFER_SIZE_INCREMENT) {
80 conn->buffer_length += BUFFER_SIZE_INCREMENT;
81 conn->buffer = g_realloc(conn->buffer, conn->buffer_length);
82 SIPE_DEBUG_INFO("transport_sip_input_common: new buffer length %" G_GSIZE_FORMAT,
83 conn->buffer_length);
86 /* Try to read as much as there is space left in the buffer */
87 /* minus 1 for the string terminator */
88 readlen = conn->buffer_length - conn->buffer_used - 1;
89 len = transport->gsc ?
90 (gssize) purple_ssl_read(transport->gsc,
91 conn->buffer + conn->buffer_used,
92 readlen) :
93 read(transport->socket,
94 conn->buffer + conn->buffer_used,
95 readlen);
97 if (len < 0 && errno == EAGAIN) {
98 /* Try again later */
99 return;
100 } else if (len < 0) {
101 SIPE_DEBUG_ERROR_NOFORMAT("Read error");
102 purple_connection_error_reason(transport->gc,
103 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
104 _("Read error"));
105 sipe_backend_transport_sip_disconnect(conn);
106 return;
107 } else if (firstread && (len == 0)) {
108 SIPE_DEBUG_ERROR_NOFORMAT("Server has disconnected");
109 purple_connection_error_reason(transport->gc,
110 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
111 _("Server has disconnected"));
112 sipe_backend_transport_sip_disconnect(conn);
113 return;
116 conn->buffer_used += len;
117 firstread = FALSE;
119 /* Equivalence indicates that there is possibly more data to read */
120 } while (len == readlen);
122 conn->buffer[conn->buffer_used] = '\0';
123 sipe_core_transport_sip_message(conn);
126 static void transport_sip_input_ssl_cb(gpointer data,
127 PurpleSslConnection *gsc,
128 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
130 struct sipe_transport_purple *transport = data;
132 /* NOTE: This check *IS* necessary */
133 if (!PURPLE_CONNECTION_IS_VALID(transport->gc)) {
134 purple_ssl_close(gsc);
135 return;
137 transport_sip_input_common(transport);
140 static void transport_sip_input_tcp_cb(gpointer data,
141 gint source,
142 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
144 struct sipe_transport_purple *transport = data;
146 /* NOTE: This check *IS* necessary */
147 if (!PURPLE_CONNECTION_IS_VALID(transport->gc)) {
148 close(source);
149 return;
151 transport_sip_input_common(transport);
154 static void transport_sip_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
155 PurpleSslErrorType error,
156 gpointer data)
158 struct sipe_transport_purple *transport = data;
160 /* If the connection is already disconnected
161 then we don't need to do anything else */
162 if (!PURPLE_CONNECTION_IS_VALID(transport->gc))
163 return;
165 sipe_core_transport_sip_ssl_connect_failure(data);
167 transport->socket = -1;
168 transport->gsc = NULL;
170 purple_connection_ssl_error(transport->gc, error);
173 static void transport_sip_connected_common(struct sipe_transport_purple *transport,
174 PurpleSslConnection *gsc,
175 int fd)
177 PurpleConnection *gc = transport->gc;
178 struct sipe_backend_private *purple_private;
180 if (!PURPLE_CONNECTION_IS_VALID(gc))
182 if (gsc) {
183 purple_ssl_close(gsc);
184 } else if (fd >= 0) {
185 close(fd);
187 return;
190 if (fd < 0) {
191 purple_connection_error_reason(gc,
192 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
193 _("Could not connect"));
194 return;
197 transport->socket = fd;
198 transport->public.client_port = purple_network_get_port_from_fd(fd);
199 purple_private = PURPLE_GC_TO_SIPE_CORE_PUBLIC->backend_private;
200 purple_private->last_keepalive = time(NULL);
202 if (gsc) {
203 transport->gsc = gsc;
204 purple_ssl_input_add(gsc, transport_sip_input_ssl_cb, transport);
205 } else {
206 transport->receive_handler = purple_input_add(fd,
207 PURPLE_INPUT_READ,
208 transport_sip_input_tcp_cb,
209 transport);
212 sipe_core_transport_sip_connected(SIPE_TRANSPORT_CONNECTION);
216 static void transport_sip_connected_ssl_cb(gpointer data,
217 PurpleSslConnection *gsc,
218 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
220 transport_sip_connected_common(data, gsc, gsc ? gsc->fd : -1);
223 static void transport_sip_connected_tcp_cb(gpointer data,
224 gint source,
225 SIPE_UNUSED_PARAMETER const gchar *error_message)
227 transport_sip_connected_common(data, NULL, source);
230 struct sipe_transport_connection *sipe_backend_transport_sip_connect(struct sipe_core_public *sipe_public,
231 guint type,
232 const gchar *server_name,
233 guint server_port)
235 struct sipe_backend_private *purple_private = sipe_public->backend_private;
236 PurpleConnection *gc = purple_private->gc;
237 PurpleAccount *account = purple_connection_get_account(gc);
238 struct sipe_transport_purple *transport = g_new0(struct sipe_transport_purple, 1);
240 SIPE_DEBUG_INFO("sipe_backend_transport_sip_connect - hostname: %s port: %d",
241 server_name, server_port);
243 transport->public.type = type;
244 transport->gc = gc;
245 transport->transmit_buffer = purple_circ_buffer_new(0);
247 if (type == SIPE_TRANSPORT_TLS) {
248 /* SSL case */
249 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
251 if (purple_ssl_connect(account,
252 server_name,
253 server_port,
254 transport_sip_connected_ssl_cb,
255 transport_sip_ssl_connect_failure,
256 transport) == NULL) {
257 purple_connection_error_reason(gc,
258 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
259 _("Could not create SSL context"));
260 return(NULL);
262 } else {
263 /* TCP case */
264 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
266 if (purple_proxy_connect(gc, account,
267 server_name,
268 server_port,
269 transport_sip_connected_tcp_cb,
270 transport) == NULL) {
271 purple_connection_error_reason(gc,
272 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
273 _("Could not create socket"));
274 return(NULL);
278 return(SIPE_TRANSPORT_CONNECTION);
281 void sipe_backend_transport_sip_disconnect(struct sipe_transport_connection *conn)
283 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
285 if (!transport) return;
287 if (transport->gsc) {
288 purple_ssl_close(transport->gsc);
289 } else if (transport->socket > 0) {
290 close(transport->socket);
293 if (transport->transmit_handler)
294 purple_input_remove(transport->transmit_handler);
295 if (transport->receive_handler)
296 purple_input_remove(transport->receive_handler);
298 if (transport->transmit_buffer)
299 purple_circ_buffer_destroy(transport->transmit_buffer);
300 g_free(transport->public.buffer);
302 g_free(transport);
304 ((struct sipe_core_public *)transport->gc->proto_data)->transport = NULL;
307 static void transport_sip_canwrite_cb(gpointer data,
308 SIPE_UNUSED_PARAMETER gint source,
309 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
311 struct sipe_transport_purple *transport = data;
312 gsize max_write;
314 max_write = purple_circ_buffer_get_max_read(transport->transmit_buffer);
315 if (max_write > 0) {
316 gssize written = transport->gsc ?
317 (gssize) purple_ssl_write(transport->gsc,
318 transport->transmit_buffer->outptr,
319 max_write) :
320 write(transport->socket,
321 transport->transmit_buffer->outptr,
322 max_write);
324 if (written < 0 && errno == EAGAIN) {
325 return;
326 } else if (written <= 0) {
327 purple_connection_error_reason(transport->gc,
328 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
329 _("Write error"));
330 return;
333 purple_circ_buffer_mark_read(transport->transmit_buffer,
334 written);
336 } else {
337 /* buffer is empty -> stop sending */
338 purple_input_remove(transport->transmit_handler);
339 transport->transmit_handler = 0;
343 void sipe_backend_transport_sip_message(struct sipe_transport_connection *conn,
344 const gchar *buffer)
346 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
347 time_t currtime = time(NULL);
348 char *tmp;
350 SIPE_DEBUG_INFO("sending - %s######\n%s######",
351 ctime(&currtime), tmp = fix_newlines(buffer));
352 g_free(tmp);
354 /* add packet to circular buffer */
355 purple_circ_buffer_append(transport->transmit_buffer,
356 buffer, strlen(buffer));
358 /* initiate transmission */
359 if (!transport->transmit_handler) {
360 transport->transmit_handler = purple_input_add(transport->socket,
361 PURPLE_INPUT_WRITE,
362 transport_sip_canwrite_cb,
363 transport);
367 /*****************************************************************************
369 * SIP transport handling
371 *****************************************************************************/
372 static void transport_http_input_common(struct sipe_transport_purple *transport)
374 struct sipe_transport_connection *conn = SIPE_TRANSPORT_CONNECTION;
375 gssize readlen, len;
376 gboolean firstread = TRUE;
378 /* Read all available data from the connection */
379 do {
380 /* Increase input buffer size as needed */
381 if (conn->buffer_length < conn->buffer_used + BUFFER_SIZE_INCREMENT) {
382 conn->buffer_length += BUFFER_SIZE_INCREMENT;
383 conn->buffer = g_realloc(conn->buffer, conn->buffer_length);
384 SIPE_DEBUG_INFO("transport_sip_input_common: new buffer length %" G_GSIZE_FORMAT,
385 conn->buffer_length);
388 /* Try to read as much as there is space left in the buffer */
389 /* minus 1 for the string terminator */
390 readlen = conn->buffer_length - conn->buffer_used - 1;
391 len = transport->gsc ?
392 (gssize) purple_ssl_read(transport->gsc,
393 conn->buffer + conn->buffer_used,
394 readlen) :
395 read(transport->socket,
396 conn->buffer + conn->buffer_used,
397 readlen);
399 if (len < 0 && errno == EAGAIN) {
400 /* Try again later */
401 return;
402 } else if (len < 0) {
403 sipe_core_transport_http_input_error(conn,
404 _("Read error"));
405 return;
406 } else if (firstread && (len == 0)) {
407 sipe_core_transport_http_input_error(conn,
408 _("Server has disconnected"));
409 return;
412 conn->buffer_used += len;
413 firstread = FALSE;
415 /* Equivalence indicates that there is possibly more data to read */
416 } while (len == readlen);
418 conn->buffer[conn->buffer_used] = '\0';
419 sipe_core_transport_http_message(conn);
422 static void transport_http_input_ssl_cb(gpointer data,
423 SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
424 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
426 transport_http_input_common(data);
429 static void transport_http_input_tcp_cb(gpointer data,
430 SIPE_UNUSED_PARAMETER gint source,
431 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
433 transport_http_input_common(data);
436 static void transport_http_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
437 PurpleSslErrorType error,
438 gpointer data)
440 struct sipe_transport_purple *transport = data;
441 const gchar *message = NULL;
443 switch(error) {
444 case PURPLE_SSL_CONNECT_FAILED:
445 message = "Connection failed";
446 break;
447 case PURPLE_SSL_HANDSHAKE_FAILED:
448 message = "SSL handshake failed";
449 break;
450 case PURPLE_SSL_CERTIFICATE_INVALID:
451 message = "SSL certificate invalid";
452 break;
455 sipe_core_transport_http_ssl_connect_failure(data, message);
457 transport->socket = -1;
458 transport->gsc = NULL;
461 static void transport_http_connected_common(struct sipe_transport_purple *transport,
462 PurpleSslConnection *gsc,
463 int fd)
465 if (fd < 0) return;
467 transport->socket = fd;
468 transport->public.client_port = purple_network_get_port_from_fd(fd);
470 if (gsc) {
471 transport->gsc = gsc;
472 purple_ssl_input_add(gsc, transport_http_input_ssl_cb, transport);
473 } else {
474 transport->receive_handler = purple_input_add(fd,
475 PURPLE_INPUT_READ,
476 transport_http_input_tcp_cb,
477 transport);
480 sipe_core_transport_http_connected(SIPE_TRANSPORT_CONNECTION);
484 static void transport_http_connected_ssl_cb(gpointer data,
485 PurpleSslConnection *gsc,
486 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
488 transport_http_connected_common(data, gsc, gsc ? gsc->fd : -1);
491 static void transport_http_connected_tcp_cb(gpointer data,
492 gint source,
493 SIPE_UNUSED_PARAMETER const gchar *error_message)
495 transport_http_connected_common(data, NULL, source);
499 struct sipe_transport_connection *sipe_backend_transport_http_connect(struct sipe_core_public *sipe_public,
500 guint type,
501 const gchar *server_name,
502 guint server_port)
504 struct sipe_backend_private *purple_private = sipe_public->backend_private;
505 PurpleConnection *gc = purple_private->gc;
506 PurpleAccount *account = purple_connection_get_account(gc);
507 struct sipe_transport_purple *transport = g_new0(struct sipe_transport_purple, 1);
509 SIPE_DEBUG_INFO("sipe_backend_transport_http_connect - hostname: %s port: %d",
510 server_name, server_port);
512 transport->public.type = type;
513 transport->transmit_buffer = purple_circ_buffer_new(0);
515 if (type == SIPE_TRANSPORT_TLS) {
516 /* SSL case */
517 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
519 if (purple_ssl_connect(account,
520 server_name,
521 server_port,
522 transport_http_connected_ssl_cb,
523 transport_http_ssl_connect_failure,
524 transport) == NULL) {
525 g_free(transport);
526 return(NULL);
528 } else {
529 /* TCP case */
530 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
532 if (purple_proxy_connect(gc, account,
533 server_name,
534 server_port,
535 transport_http_connected_tcp_cb,
536 transport) == NULL) {
537 g_free(transport);
538 return(NULL);
542 return(SIPE_TRANSPORT_CONNECTION);
545 void sipe_backend_transport_http_disconnect(struct sipe_transport_connection *conn)
547 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
549 if (!transport) return;
551 if (transport->gsc) {
552 purple_ssl_close(transport->gsc);
553 } else if (transport->socket > 0) {
554 close(transport->socket);
557 if (transport->transmit_handler)
558 purple_input_remove(transport->transmit_handler);
559 if (transport->receive_handler)
560 purple_input_remove(transport->receive_handler);
562 if (transport->transmit_buffer)
563 purple_circ_buffer_destroy(transport->transmit_buffer);
564 g_free(transport->public.buffer);
566 g_free(transport);
569 static void transport_http_canwrite_cb(gpointer data,
570 SIPE_UNUSED_PARAMETER gint source,
571 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
573 struct sipe_transport_purple *transport = data;
574 gsize max_write;
576 max_write = purple_circ_buffer_get_max_read(transport->transmit_buffer);
577 if (max_write > 0) {
578 gssize written = transport->gsc ?
579 (gssize) purple_ssl_write(transport->gsc,
580 transport->transmit_buffer->outptr,
581 max_write) :
582 write(transport->socket,
583 transport->transmit_buffer->outptr,
584 max_write);
586 if (written < 0 && errno == EAGAIN) {
587 return;
588 } else if (written <= 0) {
589 SIPE_DEBUG_INFO_NOFORMAT("transport_http_canwrite_cb: written <= 0, exiting");
590 return;
593 purple_circ_buffer_mark_read(transport->transmit_buffer,
594 written);
596 } else {
597 /* buffer is empty -> stop sending */
598 purple_input_remove(transport->transmit_handler);
599 transport->transmit_handler = 0;
603 void sipe_backend_transport_http_message(struct sipe_transport_connection *conn,
604 const gchar *buffer)
606 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
607 time_t currtime = time(NULL);
608 char *tmp;
610 SIPE_DEBUG_INFO("sending - %s######\n%s######",
611 ctime(&currtime), tmp = fix_newlines(buffer));
612 g_free(tmp);
614 /* add packet to circular buffer */
615 purple_circ_buffer_append(transport->transmit_buffer,
616 buffer, strlen(buffer));
618 /* initiate transmission */
619 if (!transport->transmit_handler) {
620 transport->transmit_handler = purple_input_add(transport->socket,
621 PURPLE_INPUT_WRITE,
622 transport_http_canwrite_cb,
623 transport);
628 Local Variables:
629 mode: c
630 c-file-style: "bsd"
631 indent-tabs-mode: t
632 tab-width: 8
633 End: