2 * @file purple-transport.c
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
36 #include "sipe-common.h"
38 #include "connection.h"
39 #include "eventloop.h"
45 #if PURPLE_VERSION_CHECK(3,0,0)
46 #include "circularbuffer.h"
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)
58 /* wrappers for write() & friends for socket handling */
59 #include "win32/win32dep.h"
61 #include <sys/types.h>
62 #include <sys/socket.h>
63 #include <netinet/in.h>
64 #include <arpa/inet.h>
67 #include "purple-private.h"
69 #include "sipe-backend.h"
70 #include "sipe-core.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
;
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
;
111 gboolean firstread
= TRUE
;
113 /* Read all available data from the connection */
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
,
130 read(transport
->socket
,
131 conn
->buffer
+ conn
->buffer_used
,
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
);
149 /* Try again later */
151 } else if (len
< 0) {
152 SIPE_DEBUG_ERROR("Read error: %s (%d)", strerror(errno
), errno
);
153 transport
->error(SIPE_TRANSPORT_CONNECTION
, _("Read error"));
155 } else if (firstread
&& (len
== 0)) {
156 SIPE_DEBUG_ERROR_NOFORMAT("Server has disconnected");
157 transport
->error(SIPE_TRANSPORT_CONNECTION
, _("Server has disconnected"));
161 conn
->buffer_used
+= len
;
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
,
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,
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
);
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
) {
246 port
= si
->sa_in
.sin_port
;
247 addr
= &si
->sa_in
.sin_addr
;
250 port
= si
->sa_in6
.sin6_port
;
251 addr
= &si
->sa_in6
.sin6_addr
;
254 port
= htons(0); /* error fallback */
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
)) {
265 strcpy(transport
->ip_address
, "0.0.0.0");
269 SIPE_DEBUG_INFO("transport_get_socket_info: %s:%d(%p)",
270 transport
->ip_address
,
271 transport
->public.client_port
,
275 static void transport_common_connected(struct sipe_transport_purple
*transport
,
278 /* Ignore spurious "connected" events after disconnect */
279 if (transport
->is_valid
) {
281 transport
->proxy
= NULL
;
284 transport
->error(SIPE_TRANSPORT_CONNECTION
,
285 _("Could not connect"));
286 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION
);
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
);
296 transport
->receive_handler
= purple_input_add(fd
,
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
,
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
,
344 if (setup
->type
== SIPE_TRANSPORT_TLS
) {
346 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
348 if ((transport
->gsc
= purple_ssl_connect(account
,
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
);
359 } else if (setup
->type
== SIPE_TRANSPORT_TCP
) {
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
,
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
);
382 setup
->error(SIPE_TRANSPORT_CONNECTION
,
383 "This should not happen...");
384 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION
);
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
);
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
,
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
);
431 purple_circ_buffer_destroy(transport
->transmit_buffer
);
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
)
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
)
458 max_write
= purple_circular_buffer_get_max_read(transport
->transmit_buffer
);
460 gssize written
= transport
->gsc
?
461 (gssize
) purple_ssl_write(transport
->gsc
,
462 purple_circular_buffer_get_output(transport
->transmit_buffer
),
464 write(transport
->socket
,
465 purple_circular_buffer_get_output(transport
->transmit_buffer
),
469 if (written
== 0 || errno
!= EAGAIN
) {
470 SIPE_DEBUG_ERROR("Write error: %s (%d)",
471 strerror(errno
), errno
);
472 transport
->error(SIPE_TRANSPORT_CONNECTION
,
476 purple_circular_buffer_mark_read(transport
->transmit_buffer
,
482 /* buffer is empty -> stop sending */
483 purple_input_remove(transport
->transmit_handler
);
484 transport
->transmit_handler
= 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
,
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
,
514 transport_canwrite_cb
,
519 void sipe_backend_transport_flush(struct sipe_transport_connection
*conn
)
521 struct sipe_transport_purple
*transport
= PURPLE_TRANSPORT
;
525 while ((written
= transport_write(transport
))) {
527 if (errno
== EAGAIN
&& retries
++ < FLUSH_MAX_RETRIES
) {
537 /* We couldn't send the whole buffer. Transport is probably
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
));