Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / purple / purple-transport.c
blob398b11a942f3133b593b0bdd856e8275cc3f11c7
1 /**
2 * @file purple-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2018 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 #ifdef HAVE_UNISTD_H
31 #include <unistd.h>
32 #endif
34 #include <glib.h>
36 #include "sipe-common.h"
38 #include "connection.h"
39 #include "eventloop.h"
40 #include "network.h"
41 #include "proxy.h"
42 #include "sslconn.h"
44 #include "version.h"
45 #if PURPLE_VERSION_CHECK(3,0,0)
46 #include "circularbuffer.h"
47 #else
48 #include "circbuffer.h"
49 #define PurpleCircularBuffer PurpleCircBuffer
50 #define purple_circular_buffer_append(b, s, n) purple_circ_buffer_append(b, s, n)
51 #define purple_circular_buffer_get_max_read(b) purple_circ_buffer_get_max_read(b)
52 #define purple_circular_buffer_get_output(b) b->outptr
53 #define purple_circular_buffer_mark_read(b, s) purple_circ_buffer_mark_read(b, s)
54 #define purple_circular_buffer_new(s) purple_circ_buffer_new(s)
55 #endif
57 #ifdef _WIN32
58 /* wrappers for write() & friends for socket handling */
59 #include "win32/win32dep.h"
60 #else
61 #include <sys/types.h>
62 #include <sys/socket.h>
63 #include <netinet/in.h>
64 #include <arpa/inet.h>
65 #endif
67 #include "purple-private.h"
69 #include "sipe-backend.h"
70 #include "sipe-core.h"
71 #include "sipe-nls.h"
73 struct sipe_transport_purple {
74 /* public part shared with core */
75 struct sipe_transport_connection public;
77 /* purple private part */
78 struct sipe_backend_private *purple_private;
79 transport_connected_cb *connected;
80 transport_input_cb *input;
81 transport_error_cb *error;
82 PurpleSslConnection *gsc;
83 PurpleProxyConnectData *proxy;
84 PurpleCircularBuffer *transmit_buffer;
85 guint transmit_handler;
86 guint receive_handler;
87 int socket;
89 gboolean is_valid;
91 gchar ip_address[INET6_ADDRSTRLEN]; /* OK for IPv4 too */
94 #define PURPLE_TRANSPORT ((struct sipe_transport_purple *) conn)
95 #define SIPE_TRANSPORT_CONNECTION ((struct sipe_transport_connection *) transport)
97 #define BUFFER_SIZE_INCREMENT 4096
98 #define FLUSH_MAX_RETRIES 5
102 /*****************************************************************************
104 * Common transport handling
106 *****************************************************************************/
107 static void transport_common_input(struct sipe_transport_purple *transport)
109 struct sipe_transport_connection *conn = SIPE_TRANSPORT_CONNECTION;
110 gssize readlen, len;
111 gboolean firstread = TRUE;
113 /* Read all available data from the connection */
114 do {
115 /* Increase input buffer size as needed */
116 if (conn->buffer_length < conn->buffer_used + BUFFER_SIZE_INCREMENT) {
117 conn->buffer_length += BUFFER_SIZE_INCREMENT;
118 conn->buffer = g_realloc(conn->buffer, conn->buffer_length);
119 SIPE_DEBUG_INFO("transport_input_common: new buffer length %" G_GSIZE_FORMAT,
120 conn->buffer_length);
123 /* Try to read as much as there is space left in the buffer */
124 /* minus 1 for the string terminator */
125 readlen = conn->buffer_length - conn->buffer_used - 1;
126 len = transport->gsc ?
127 (gssize) purple_ssl_read(transport->gsc,
128 conn->buffer + conn->buffer_used,
129 readlen) :
130 read(transport->socket,
131 conn->buffer + conn->buffer_used,
132 readlen);
134 if (len < 0 && errno == EAGAIN) {
136 * Work around rare SSL read deadlock situation
138 * When we went around the loop then the previous call
139 * to purple_ssl_read() filled the buffer exactly. If
140 * it also happened to read all pending bytes then it
141 * seems that the next call returns len < 0 with EAGAIN
142 * instead of the expected len == 0.
144 if (transport->gsc && !firstread) {
145 SIPE_DEBUG_INFO("transport_input_common: SSL read deadlock detected - assuming message is %" G_GSIZE_FORMAT " bytes long", conn->buffer_used);
146 break;
149 /* Try again later */
150 return;
151 } else if (len < 0) {
152 SIPE_DEBUG_ERROR("Read error: %s (%d)", strerror(errno), errno);
153 transport->error(SIPE_TRANSPORT_CONNECTION, _("Read error"));
154 return;
155 } else if (firstread && (len == 0)) {
156 SIPE_DEBUG_ERROR_NOFORMAT("Server has disconnected");
157 transport->error(SIPE_TRANSPORT_CONNECTION, _("Server has disconnected"));
158 return;
161 conn->buffer_used += len;
162 firstread = FALSE;
164 /* Equivalence indicates that there is possibly more data to read */
165 } while (len == readlen);
167 conn->buffer[conn->buffer_used] = '\0';
168 transport->input(conn);
171 static void transport_ssl_input(gpointer data,
172 SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
173 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
175 struct sipe_transport_purple *transport = data;
177 /* Ignore spurious "SSL input" events after disconnect */
178 if (transport->is_valid)
179 transport_common_input(transport);
182 static void transport_tcp_input(gpointer data,
183 SIPE_UNUSED_PARAMETER gint source,
184 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
186 struct sipe_transport_purple *transport = data;
188 /* Ignore spurious "TCP input" events after disconnect */
189 if (transport->is_valid)
190 transport_common_input(transport);
193 static void transport_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
194 PurpleSslErrorType error,
195 gpointer data)
197 struct sipe_transport_purple *transport = data;
199 /* Ignore spurious "SSL connect failure" events after disconnect */
200 if (transport->is_valid) {
201 transport->socket = -1;
202 transport->gsc = NULL;
203 transport->error(SIPE_TRANSPORT_CONNECTION,
204 purple_ssl_strerror(error));
205 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
209 static void transport_get_socket_info(struct sipe_transport_purple *transport)
212 * NOTE: getsockname() on Windows seems to be picky about the buffer
213 * location. Use an allocated buffer instead of one on the stack,
215 union socket_info {
216 struct sockaddr sa; /* to avoid casts */
217 struct sockaddr_in sa_in; /* IPv4 variant */
218 struct sockaddr_in6 sa_in6; /* IPv6 variant */
219 struct sockaddr_storage unused; /* for alignment */
220 } *si = g_new(union socket_info, 1);
221 socklen_t si_len = sizeof(*si);
222 const void *addr;
223 guint port;
226 * libpurple only returns IPv4 addresses
228 * purple_network_get_my_ip(transport->socket);
230 * libpurple returns port 0 on Windows for IPv6 sockets
232 * purple_network_get_port_from_fd(transport->socket);
234 * Replace them with our own code.
236 if (getsockname(transport->socket, &si->sa, &si_len) < 0) {
237 SIPE_DEBUG_ERROR("transport_get_socket_info: %s (%d)",
238 strerror(errno), errno);
240 /* make sure socket address family is initialized */
241 si->sa.sa_family = AF_UNSPEC;
244 switch (si->sa.sa_family) {
245 case AF_INET:
246 port = si->sa_in.sin_port;
247 addr = &si->sa_in.sin_addr;
248 break;
249 case AF_INET6:
250 port = si->sa_in6.sin6_port;
251 addr = &si->sa_in6.sin6_addr;
252 break;
253 default:
254 port = htons(0); /* error fallback */
255 addr = NULL;
256 break;
259 transport->public.client_port = ntohs(port);
260 if ((addr == NULL) ||
261 (inet_ntop(si->sa.sa_family, addr,
262 transport->ip_address,
263 sizeof(transport->ip_address)) == NULL)) {
264 /* error fallback */
265 strcpy(transport->ip_address, "0.0.0.0");
267 g_free(si);
269 SIPE_DEBUG_INFO("transport_get_socket_info: %s:%d(%p)",
270 transport->ip_address,
271 transport->public.client_port,
272 transport);
275 static void transport_common_connected(struct sipe_transport_purple *transport,
276 int fd)
278 /* Ignore spurious "connected" events after disconnect */
279 if (transport->is_valid) {
281 transport->proxy = NULL;
283 if (fd < 0) {
284 transport->error(SIPE_TRANSPORT_CONNECTION,
285 _("Could not connect"));
286 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
287 return;
290 transport->socket = fd;
291 transport_get_socket_info(transport);
293 if (transport->gsc) {
294 purple_ssl_input_add(transport->gsc, transport_ssl_input, transport);
295 } else {
296 transport->receive_handler = purple_input_add(fd,
297 PURPLE_INPUT_READ,
298 transport_tcp_input,
299 transport);
302 transport->connected(SIPE_TRANSPORT_CONNECTION);
306 static void transport_ssl_connected(gpointer data,
307 PurpleSslConnection *gsc,
308 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
310 transport_common_connected(data, gsc->fd);
313 static void transport_tcp_connected(gpointer data,
314 gint source,
315 SIPE_UNUSED_PARAMETER const gchar *error_message)
317 transport_common_connected(data, source);
320 struct sipe_transport_connection *
321 sipe_backend_transport_connect(struct sipe_core_public *sipe_public,
322 const sipe_connect_setup *setup)
324 struct sipe_transport_purple *transport = g_new0(struct sipe_transport_purple, 1);
325 struct sipe_backend_private *purple_private = sipe_public->backend_private;
326 PurpleConnection *gc = purple_private->gc;
327 PurpleAccount *account = purple_connection_get_account(gc);
329 SIPE_DEBUG_INFO("transport_connect - hostname: %s port: %d",
330 setup->server_name, setup->server_port);
332 transport->public.type = setup->type;
333 transport->public.user_data = setup->user_data;
334 transport->purple_private = purple_private;
335 transport->connected = setup->connected;
336 transport->input = setup->input;
337 transport->error = setup->error;
338 transport->transmit_buffer = purple_circular_buffer_new(0);
339 transport->is_valid = TRUE;
341 purple_private->transports = g_slist_prepend(purple_private->transports,
342 transport);
344 if (setup->type == SIPE_TRANSPORT_TLS) {
345 /* SSL case */
346 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
348 if ((transport->gsc = purple_ssl_connect(account,
349 setup->server_name,
350 setup->server_port,
351 transport_ssl_connected,
352 transport_ssl_connect_failure,
353 transport)) == NULL) {
354 setup->error(SIPE_TRANSPORT_CONNECTION,
355 _("Could not create SSL context"));
356 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
357 return(NULL);
359 } else if (setup->type == SIPE_TRANSPORT_TCP) {
360 /* TCP case */
361 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
364 * NOTE: during shutdown libpurple calls
366 * purple_proxy_connect_cancel_with_handle(gc);
368 * before our cleanup code. Therefore we can't use "gc" as
369 * handle. We are not using it for anything thus NULL is fine.
371 if ((transport->proxy = purple_proxy_connect(NULL, account,
372 setup->server_name,
373 setup->server_port,
374 transport_tcp_connected,
375 transport)) == NULL) {
376 setup->error(SIPE_TRANSPORT_CONNECTION,
377 _("Could not create socket"));
378 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
379 return(NULL);
381 } else {
382 setup->error(SIPE_TRANSPORT_CONNECTION,
383 "This should not happen...");
384 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
385 return(NULL);
388 return(SIPE_TRANSPORT_CONNECTION);
391 static gboolean transport_deferred_destroy(gpointer user_data)
394 * All pending events on transport have been processed.
395 * Now it is safe to destroy the data structure.
397 SIPE_DEBUG_INFO("transport_deferred_destroy: %p", user_data);
398 g_free(user_data);
399 return(FALSE);
402 void sipe_backend_transport_disconnect(struct sipe_transport_connection *conn)
404 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
405 struct sipe_backend_private *purple_private;
407 if (!transport || !transport->is_valid) return;
409 purple_private = transport->purple_private;
410 purple_private->transports = g_slist_remove(purple_private->transports,
411 transport);
413 if (transport->gsc) {
414 purple_ssl_close(transport->gsc);
415 } else if (transport->socket > 0) {
416 close(transport->socket);
419 if (transport->proxy)
420 purple_proxy_connect_cancel(transport->proxy);
422 if (transport->transmit_handler)
423 purple_input_remove(transport->transmit_handler);
424 if (transport->receive_handler)
425 purple_input_remove(transport->receive_handler);
427 if (transport->transmit_buffer)
428 #if PURPLE_VERSION_CHECK(3,0,0)
429 g_object_unref(transport->transmit_buffer);
430 #else
431 purple_circ_buffer_destroy(transport->transmit_buffer);
432 #endif
433 g_free(transport->public.buffer);
435 /* defer deletion of transport data structure to idle callback */
436 transport->is_valid = FALSE;
437 g_idle_add(transport_deferred_destroy, transport);
440 gchar *sipe_backend_transport_ip_address(struct sipe_transport_connection *conn)
442 return(g_strdup(PURPLE_TRANSPORT->ip_address));
445 void sipe_purple_transport_close_all(struct sipe_backend_private *purple_private)
447 GSList *entry;
448 SIPE_DEBUG_INFO_NOFORMAT("sipe_purple_transport_close_all: entered");
449 while ((entry = purple_private->transports) != NULL)
450 sipe_backend_transport_disconnect(entry->data);
453 /* returns a negative number on write error */
454 static gssize transport_write(struct sipe_transport_purple *transport)
456 gsize max_write;
458 max_write = purple_circular_buffer_get_max_read(transport->transmit_buffer);
459 if (max_write > 0) {
460 gssize written = transport->gsc ?
461 (gssize) purple_ssl_write(transport->gsc,
462 purple_circular_buffer_get_output(transport->transmit_buffer),
463 max_write) :
464 write(transport->socket,
465 purple_circular_buffer_get_output(transport->transmit_buffer),
466 max_write);
468 if (written <= 0) {
469 if (written == 0 || errno != EAGAIN) {
470 SIPE_DEBUG_ERROR("Write error: %s (%d)",
471 strerror(errno), errno);
472 transport->error(SIPE_TRANSPORT_CONNECTION,
473 _("Write error"));
475 } else {
476 purple_circular_buffer_mark_read(transport->transmit_buffer,
477 written);
480 return written;
481 } else {
482 /* buffer is empty -> stop sending */
483 purple_input_remove(transport->transmit_handler);
484 transport->transmit_handler = 0;
487 return 0;
490 static void transport_canwrite_cb(gpointer data,
491 SIPE_UNUSED_PARAMETER gint source,
492 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
494 struct sipe_transport_purple *transport = data;
496 /* Ignore spurious "can write" events after disconnect */
497 if (transport->is_valid)
498 transport_write(data);
501 void sipe_backend_transport_message(struct sipe_transport_connection *conn,
502 const gchar *buffer)
504 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
506 /* add packet to circular buffer */
507 purple_circular_buffer_append(transport->transmit_buffer,
508 buffer, strlen(buffer));
510 /* initiate transmission */
511 if (!transport->transmit_handler) {
512 transport->transmit_handler = purple_input_add(transport->socket,
513 PURPLE_INPUT_WRITE,
514 transport_canwrite_cb,
515 transport);
519 void sipe_backend_transport_flush(struct sipe_transport_connection *conn)
521 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
522 gssize written;
523 int retries = 0;
525 while ((written = transport_write(transport))) {
526 if (written < 0) {
527 if (errno == EAGAIN && retries++ < FLUSH_MAX_RETRIES) {
528 continue;
530 break;
533 retries = 0;
536 if (written != 0) {
537 /* We couldn't send the whole buffer. Transport is probably
538 * broken. */
539 SIPE_DEBUG_INFO("sipe_backend_transport_flush: leaving "
540 "%" G_GSSIZE_FORMAT " unsent bytes in buffer.",
541 purple_circular_buffer_get_max_read(
542 transport->transmit_buffer));
547 Local Variables:
548 mode: c
549 c-file-style: "bsd"
550 indent-tabs-mode: t
551 tab-width: 8
552 End: