adium: clean up paths in xcconfigs
[siplcs.git] / src / purple / purple-transport.c
blob67d48c9ba0a417d71e1ddd579bec8db7bdcb2cee
1 /**
2 * @file purple-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2017 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/socket.h>
62 #include <arpa/inet.h>
63 #endif
65 #include "purple-private.h"
67 #include "sipe-backend.h"
68 #include "sipe-core.h"
69 #include "sipe-nls.h"
71 struct sipe_transport_purple {
72 /* public part shared with core */
73 struct sipe_transport_connection public;
75 /* purple private part */
76 struct sipe_backend_private *purple_private;
77 transport_connected_cb *connected;
78 transport_input_cb *input;
79 transport_error_cb *error;
80 PurpleSslConnection *gsc;
81 PurpleProxyConnectData *proxy;
82 PurpleCircularBuffer *transmit_buffer;
83 guint transmit_handler;
84 guint receive_handler;
85 int socket;
87 gboolean is_valid;
90 #define PURPLE_TRANSPORT ((struct sipe_transport_purple *) conn)
91 #define SIPE_TRANSPORT_CONNECTION ((struct sipe_transport_connection *) transport)
93 #define BUFFER_SIZE_INCREMENT 4096
94 #define FLUSH_MAX_RETRIES 5
98 /*****************************************************************************
100 * Common transport handling
102 *****************************************************************************/
103 static void transport_common_input(struct sipe_transport_purple *transport)
105 struct sipe_transport_connection *conn = SIPE_TRANSPORT_CONNECTION;
106 gssize readlen, len;
107 gboolean firstread = TRUE;
109 /* Read all available data from the connection */
110 do {
111 /* Increase input buffer size as needed */
112 if (conn->buffer_length < conn->buffer_used + BUFFER_SIZE_INCREMENT) {
113 conn->buffer_length += BUFFER_SIZE_INCREMENT;
114 conn->buffer = g_realloc(conn->buffer, conn->buffer_length);
115 SIPE_DEBUG_INFO("transport_input_common: new buffer length %" G_GSIZE_FORMAT,
116 conn->buffer_length);
119 /* Try to read as much as there is space left in the buffer */
120 /* minus 1 for the string terminator */
121 readlen = conn->buffer_length - conn->buffer_used - 1;
122 len = transport->gsc ?
123 (gssize) purple_ssl_read(transport->gsc,
124 conn->buffer + conn->buffer_used,
125 readlen) :
126 read(transport->socket,
127 conn->buffer + conn->buffer_used,
128 readlen);
130 if (len < 0 && errno == EAGAIN) {
132 * Work around rare SSL read deadlock situation
134 * When we went around the loop then the previous call
135 * to purple_ssl_read() filled the buffer exactly. If
136 * it also happened to read all pending bytes then it
137 * seems that the next call returns len < 0 with EAGAIN
138 * instead of the expected len == 0.
140 if (transport->gsc && !firstread) {
141 SIPE_DEBUG_INFO("transport_input_common: SSL read deadlock detected - assuming message is %" G_GSIZE_FORMAT " bytes long", conn->buffer_used);
142 break;
145 /* Try again later */
146 return;
147 } else if (len < 0) {
148 SIPE_DEBUG_ERROR("Read error: %s (%d)", strerror(errno), errno);
149 transport->error(SIPE_TRANSPORT_CONNECTION, _("Read error"));
150 return;
151 } else if (firstread && (len == 0)) {
152 SIPE_DEBUG_ERROR_NOFORMAT("Server has disconnected");
153 transport->error(SIPE_TRANSPORT_CONNECTION, _("Server has disconnected"));
154 return;
157 conn->buffer_used += len;
158 firstread = FALSE;
160 /* Equivalence indicates that there is possibly more data to read */
161 } while (len == readlen);
163 conn->buffer[conn->buffer_used] = '\0';
164 transport->input(conn);
167 static void transport_ssl_input(gpointer data,
168 SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
169 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
171 struct sipe_transport_purple *transport = data;
173 /* Ignore spurious "SSL input" events after disconnect */
174 if (transport->is_valid)
175 transport_common_input(transport);
178 static void transport_tcp_input(gpointer data,
179 SIPE_UNUSED_PARAMETER gint source,
180 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
182 struct sipe_transport_purple *transport = data;
184 /* Ignore spurious "TCP input" events after disconnect */
185 if (transport->is_valid)
186 transport_common_input(transport);
189 static void transport_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
190 PurpleSslErrorType error,
191 gpointer data)
193 struct sipe_transport_purple *transport = data;
195 /* Ignore spurious "SSL connect failure" events after disconnect */
196 if (transport->is_valid) {
197 transport->socket = -1;
198 transport->gsc = NULL;
199 transport->error(SIPE_TRANSPORT_CONNECTION,
200 purple_ssl_strerror(error));
201 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
205 static void transport_common_connected(struct sipe_transport_purple *transport,
206 int fd)
208 /* Ignore spurious "connected" events after disconnect */
209 if (transport->is_valid) {
211 transport->proxy = NULL;
213 if (fd < 0) {
214 transport->error(SIPE_TRANSPORT_CONNECTION,
215 _("Could not connect"));
216 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
217 return;
220 transport->socket = fd;
221 transport->public.client_port = purple_network_get_port_from_fd(fd);
223 if (transport->gsc) {
224 purple_ssl_input_add(transport->gsc, transport_ssl_input, transport);
225 } else {
226 transport->receive_handler = purple_input_add(fd,
227 PURPLE_INPUT_READ,
228 transport_tcp_input,
229 transport);
232 transport->connected(SIPE_TRANSPORT_CONNECTION);
236 static void transport_ssl_connected(gpointer data,
237 PurpleSslConnection *gsc,
238 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
240 transport_common_connected(data, gsc->fd);
243 static void transport_tcp_connected(gpointer data,
244 gint source,
245 SIPE_UNUSED_PARAMETER const gchar *error_message)
247 transport_common_connected(data, source);
250 struct sipe_transport_connection *
251 sipe_backend_transport_connect(struct sipe_core_public *sipe_public,
252 const sipe_connect_setup *setup)
254 struct sipe_transport_purple *transport = g_new0(struct sipe_transport_purple, 1);
255 struct sipe_backend_private *purple_private = sipe_public->backend_private;
256 PurpleConnection *gc = purple_private->gc;
257 PurpleAccount *account = purple_connection_get_account(gc);
259 SIPE_DEBUG_INFO("transport_connect - hostname: %s port: %d",
260 setup->server_name, setup->server_port);
262 transport->public.type = setup->type;
263 transport->public.user_data = setup->user_data;
264 transport->purple_private = purple_private;
265 transport->connected = setup->connected;
266 transport->input = setup->input;
267 transport->error = setup->error;
268 transport->transmit_buffer = purple_circular_buffer_new(0);
269 transport->is_valid = TRUE;
271 purple_private->transports = g_slist_prepend(purple_private->transports,
272 transport);
274 if (setup->type == SIPE_TRANSPORT_TLS) {
275 /* SSL case */
276 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
278 if ((transport->gsc = purple_ssl_connect(account,
279 setup->server_name,
280 setup->server_port,
281 transport_ssl_connected,
282 transport_ssl_connect_failure,
283 transport)) == NULL) {
284 setup->error(SIPE_TRANSPORT_CONNECTION,
285 _("Could not create SSL context"));
286 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
287 return(NULL);
289 } else if (setup->type == SIPE_TRANSPORT_TCP) {
290 /* TCP case */
291 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
294 * NOTE: during shutdown libpurple calls
296 * purple_proxy_connect_cancel_with_handle(gc);
298 * before our cleanup code. Therefore we can't use "gc" as
299 * handle. We are not using it for anything thus NULL is fine.
301 if ((transport->proxy = purple_proxy_connect(NULL, account,
302 setup->server_name,
303 setup->server_port,
304 transport_tcp_connected,
305 transport)) == NULL) {
306 setup->error(SIPE_TRANSPORT_CONNECTION,
307 _("Could not create socket"));
308 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
309 return(NULL);
311 } else {
312 setup->error(SIPE_TRANSPORT_CONNECTION,
313 "This should not happen...");
314 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
315 return(NULL);
318 return(SIPE_TRANSPORT_CONNECTION);
321 static gboolean transport_deferred_destroy(gpointer user_data)
324 * All pending events on transport have been processed.
325 * Now it is safe to destroy the data structure.
327 SIPE_DEBUG_INFO("transport_deferred_destroy: %p", user_data);
328 g_free(user_data);
329 return(FALSE);
332 void sipe_backend_transport_disconnect(struct sipe_transport_connection *conn)
334 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
335 struct sipe_backend_private *purple_private;
337 if (!transport || !transport->is_valid) return;
339 purple_private = transport->purple_private;
340 purple_private->transports = g_slist_remove(purple_private->transports,
341 transport);
343 if (transport->gsc) {
344 purple_ssl_close(transport->gsc);
345 } else if (transport->socket > 0) {
346 close(transport->socket);
349 if (transport->proxy)
350 purple_proxy_connect_cancel(transport->proxy);
352 if (transport->transmit_handler)
353 purple_input_remove(transport->transmit_handler);
354 if (transport->receive_handler)
355 purple_input_remove(transport->receive_handler);
357 if (transport->transmit_buffer)
358 #if PURPLE_VERSION_CHECK(3,0,0)
359 g_object_unref(transport->transmit_buffer);
360 #else
361 purple_circ_buffer_destroy(transport->transmit_buffer);
362 #endif
363 g_free(transport->public.buffer);
365 /* defer deletion of transport data structure to idle callback */
366 transport->is_valid = FALSE;
367 g_idle_add(transport_deferred_destroy, transport);
370 gchar *sipe_backend_transport_ip_address(struct sipe_transport_connection *conn)
373 * libpurple code only returns IPv4 addresses
375 * return(g_strdup(purple_network_get_my_ip(PURPLE_TRANSPORT->socket)));
377 * Use our own implementation instead. The user will no longer be able
378 * to override the local IP address via the libpurple settings.
380 struct sockaddr_storage addr;
381 socklen_t addrlen = sizeof(addr);
382 gchar buf[INET6_ADDRSTRLEN]; /* OK for IPv4 too */
383 const gchar *ipstr = "0.0.0.0"; /* default on error */
385 if ((getsockname(PURPLE_TRANSPORT->socket,
386 (struct sockaddr *) &addr,
387 &addrlen) == 0) &&
388 ((addr.ss_family == AF_INET) ||
389 (addr.ss_family == AF_INET6)) &&
390 (inet_ntop(addr.ss_family,
391 (addr.ss_family == AF_INET) ?
392 (void *) &(((struct sockaddr_in *) &addr)->sin_addr) :
393 (void *) &(((struct sockaddr_in6 *) &addr)->sin6_addr),
394 buf,
395 sizeof(buf)) != NULL)) {
396 ipstr = buf;
397 SIPE_DEBUG_INFO("sipe_backend_transport_ip_address: %s", ipstr);
400 return(g_strdup(ipstr));
403 void sipe_purple_transport_close_all(struct sipe_backend_private *purple_private)
405 GSList *entry;
406 SIPE_DEBUG_INFO_NOFORMAT("sipe_purple_transport_close_all: entered");
407 while ((entry = purple_private->transports) != NULL)
408 sipe_backend_transport_disconnect(entry->data);
411 /* returns a negative number on write error */
412 static gssize transport_write(struct sipe_transport_purple *transport)
414 gsize max_write;
416 max_write = purple_circular_buffer_get_max_read(transport->transmit_buffer);
417 if (max_write > 0) {
418 gssize written = transport->gsc ?
419 (gssize) purple_ssl_write(transport->gsc,
420 purple_circular_buffer_get_output(transport->transmit_buffer),
421 max_write) :
422 write(transport->socket,
423 purple_circular_buffer_get_output(transport->transmit_buffer),
424 max_write);
426 if (written <= 0) {
427 if (written == 0 || errno != EAGAIN) {
428 SIPE_DEBUG_ERROR("Write error: %s (%d)",
429 strerror(errno), errno);
430 transport->error(SIPE_TRANSPORT_CONNECTION,
431 _("Write error"));
433 } else {
434 purple_circular_buffer_mark_read(transport->transmit_buffer,
435 written);
438 return written;
439 } else {
440 /* buffer is empty -> stop sending */
441 purple_input_remove(transport->transmit_handler);
442 transport->transmit_handler = 0;
445 return 0;
448 static void transport_canwrite_cb(gpointer data,
449 SIPE_UNUSED_PARAMETER gint source,
450 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
452 struct sipe_transport_purple *transport = data;
454 /* Ignore spurious "can write" events after disconnect */
455 if (transport->is_valid)
456 transport_write(data);
459 void sipe_backend_transport_message(struct sipe_transport_connection *conn,
460 const gchar *buffer)
462 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
464 /* add packet to circular buffer */
465 purple_circular_buffer_append(transport->transmit_buffer,
466 buffer, strlen(buffer));
468 /* initiate transmission */
469 if (!transport->transmit_handler) {
470 transport->transmit_handler = purple_input_add(transport->socket,
471 PURPLE_INPUT_WRITE,
472 transport_canwrite_cb,
473 transport);
477 void sipe_backend_transport_flush(struct sipe_transport_connection *conn)
479 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
480 gssize written;
481 int retries = 0;
483 while ((written = transport_write(transport))) {
484 if (written < 0) {
485 if (errno == EAGAIN && retries++ < FLUSH_MAX_RETRIES) {
486 continue;
488 break;
491 retries = 0;
494 if (written != 0) {
495 /* We couldn't send the whole buffer. Transport is probably
496 * broken. */
497 SIPE_DEBUG_INFO("sipe_backend_transport_flush: leaving "
498 "%" G_GSSIZE_FORMAT " unsent bytes in buffer.",
499 purple_circular_buffer_get_max_read(
500 transport->transmit_buffer));
505 Local Variables:
506 mode: c
507 c-file-style: "bsd"
508 indent-tabs-mode: t
509 tab-width: 8
510 End: