buddy: add interface for number of buddies
[siplcs.git] / src / purple / purple-transport.c
blobc78938cb6ad212d083e12fd3ed130a919f32b5ee
1 /**
2 * @file purple-transport.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 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 "circbuffer.h"
39 #include "connection.h"
40 #include "eventloop.h"
41 #include "network.h"
42 #include "proxy.h"
43 #include "sslconn.h"
45 #ifdef _WIN32
46 /* wrappers for write() & friends for socket handling */
47 #include "win32/win32dep.h"
48 #endif
50 #include "purple-private.h"
52 #include "sipe-backend.h"
53 #include "sipe-core.h"
54 #include "sipe-nls.h"
56 struct sipe_transport_purple {
57 /* public part shared with core */
58 struct sipe_transport_connection public;
60 /* purple private part */
61 struct sipe_backend_private *purple_private;
62 transport_connected_cb *connected;
63 transport_input_cb *input;
64 transport_error_cb *error;
65 PurpleSslConnection *gsc;
66 PurpleProxyConnectData *proxy;
67 PurpleCircBuffer *transmit_buffer;
68 guint transmit_handler;
69 guint receive_handler;
70 int socket;
72 gboolean is_valid;
75 #define PURPLE_TRANSPORT ((struct sipe_transport_purple *) conn)
76 #define SIPE_TRANSPORT_CONNECTION ((struct sipe_transport_connection *) transport)
78 #define BUFFER_SIZE_INCREMENT 4096
82 /*****************************************************************************
84 * Common transport handling
86 *****************************************************************************/
87 static void transport_common_input(struct sipe_transport_purple *transport)
89 struct sipe_transport_connection *conn = SIPE_TRANSPORT_CONNECTION;
90 gssize readlen, len;
91 gboolean firstread = TRUE;
93 /* Read all available data from the connection */
94 do {
95 /* Increase input buffer size as needed */
96 if (conn->buffer_length < conn->buffer_used + BUFFER_SIZE_INCREMENT) {
97 conn->buffer_length += BUFFER_SIZE_INCREMENT;
98 conn->buffer = g_realloc(conn->buffer, conn->buffer_length);
99 SIPE_DEBUG_INFO("transport_input_common: new buffer length %" G_GSIZE_FORMAT,
100 conn->buffer_length);
103 /* Try to read as much as there is space left in the buffer */
104 /* minus 1 for the string terminator */
105 readlen = conn->buffer_length - conn->buffer_used - 1;
106 len = transport->gsc ?
107 (gssize) purple_ssl_read(transport->gsc,
108 conn->buffer + conn->buffer_used,
109 readlen) :
110 read(transport->socket,
111 conn->buffer + conn->buffer_used,
112 readlen);
114 if (len < 0 && errno == EAGAIN) {
115 /* Try again later */
116 return;
117 } else if (len < 0) {
118 SIPE_DEBUG_ERROR("Read error: %s (%d)", strerror(errno), errno);
119 transport->error(SIPE_TRANSPORT_CONNECTION, _("Read error"));
120 return;
121 } else if (firstread && (len == 0)) {
122 SIPE_DEBUG_ERROR_NOFORMAT("Server has disconnected");
123 transport->error(SIPE_TRANSPORT_CONNECTION, _("Server has disconnected"));
124 return;
127 conn->buffer_used += len;
128 firstread = FALSE;
130 /* Equivalence indicates that there is possibly more data to read */
131 } while (len == readlen);
133 conn->buffer[conn->buffer_used] = '\0';
134 transport->input(conn);
137 static void transport_ssl_input(gpointer data,
138 SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
139 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
141 struct sipe_transport_purple *transport = data;
143 /* Ignore spurious "SSL input" events after disconnect */
144 if (transport->is_valid)
145 transport_common_input(transport);
148 static void transport_tcp_input(gpointer data,
149 SIPE_UNUSED_PARAMETER gint source,
150 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
152 struct sipe_transport_purple *transport = data;
154 /* Ignore spurious "TCP input" events after disconnect */
155 if (transport->is_valid)
156 transport_common_input(transport);
159 static void transport_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
160 PurpleSslErrorType error,
161 gpointer data)
163 struct sipe_transport_purple *transport = data;
165 /* Ignore spurious "SSL connect failure" events after disconnect */
166 if (transport->is_valid) {
167 transport->socket = -1;
168 transport->gsc = NULL;
169 transport->error(SIPE_TRANSPORT_CONNECTION,
170 purple_ssl_strerror(error));
171 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
175 static void transport_common_connected(struct sipe_transport_purple *transport,
176 int fd)
178 /* Ignore spurious "connected" events after disconnect */
179 if (transport->is_valid) {
181 transport->proxy = NULL;
183 if (fd < 0) {
184 transport->error(SIPE_TRANSPORT_CONNECTION,
185 _("Could not connect"));
186 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
187 return;
190 transport->socket = fd;
191 transport->public.client_port = purple_network_get_port_from_fd(fd);
193 if (transport->gsc) {
194 purple_ssl_input_add(transport->gsc, transport_ssl_input, transport);
195 } else {
196 transport->receive_handler = purple_input_add(fd,
197 PURPLE_INPUT_READ,
198 transport_tcp_input,
199 transport);
202 transport->connected(SIPE_TRANSPORT_CONNECTION);
206 static void transport_ssl_connected(gpointer data,
207 PurpleSslConnection *gsc,
208 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
210 transport_common_connected(data, gsc->fd);
213 static void transport_tcp_connected(gpointer data,
214 gint source,
215 SIPE_UNUSED_PARAMETER const gchar *error_message)
217 transport_common_connected(data, source);
220 struct sipe_transport_connection *
221 sipe_backend_transport_connect(struct sipe_core_public *sipe_public,
222 const sipe_connect_setup *setup)
224 struct sipe_transport_purple *transport = g_new0(struct sipe_transport_purple, 1);
225 struct sipe_backend_private *purple_private = sipe_public->backend_private;
226 PurpleConnection *gc = purple_private->gc;
227 PurpleAccount *account = purple_connection_get_account(gc);
229 SIPE_DEBUG_INFO("transport_connect - hostname: %s port: %d",
230 setup->server_name, setup->server_port);
232 transport->public.type = setup->type;
233 transport->public.user_data = setup->user_data;
234 transport->purple_private = purple_private;
235 transport->connected = setup->connected;
236 transport->input = setup->input;
237 transport->error = setup->error;
238 transport->transmit_buffer = purple_circ_buffer_new(0);
239 transport->is_valid = TRUE;
241 purple_private->transports = g_slist_prepend(purple_private->transports,
242 transport);
244 if (setup->type == SIPE_TRANSPORT_TLS) {
245 /* SSL case */
246 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
248 if ((transport->gsc = purple_ssl_connect(account,
249 setup->server_name,
250 setup->server_port,
251 transport_ssl_connected,
252 transport_ssl_connect_failure,
253 transport)) == NULL) {
254 setup->error(SIPE_TRANSPORT_CONNECTION,
255 _("Could not create SSL context"));
256 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
257 return(NULL);
259 } else if (setup->type == SIPE_TRANSPORT_TCP) {
260 /* TCP case */
261 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
263 if ((transport->proxy = purple_proxy_connect(gc, account,
264 setup->server_name,
265 setup->server_port,
266 transport_tcp_connected,
267 transport)) == NULL) {
268 setup->error(SIPE_TRANSPORT_CONNECTION,
269 _("Could not create socket"));
270 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
271 return(NULL);
273 } else {
274 setup->error(SIPE_TRANSPORT_CONNECTION,
275 "This should not happen...");
276 sipe_backend_transport_disconnect(SIPE_TRANSPORT_CONNECTION);
277 return(NULL);
280 return(SIPE_TRANSPORT_CONNECTION);
283 static gboolean transport_deferred_destroy(gpointer user_data)
286 * All pending events on transport have been processed.
287 * Now it is safe to destroy the data structure.
289 SIPE_DEBUG_INFO("transport_deferred_destroy: %p", user_data);
290 g_free(user_data);
291 return(FALSE);
294 void sipe_backend_transport_disconnect(struct sipe_transport_connection *conn)
296 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
297 struct sipe_backend_private *purple_private;
299 if (!transport || !transport->is_valid) return;
301 purple_private = transport->purple_private;
302 purple_private->transports = g_slist_remove(purple_private->transports,
303 transport);
305 if (transport->gsc) {
306 purple_ssl_close(transport->gsc);
307 } else if (transport->socket > 0) {
308 close(transport->socket);
311 if (transport->proxy)
312 purple_proxy_connect_cancel(transport->proxy);
314 if (transport->transmit_handler)
315 purple_input_remove(transport->transmit_handler);
316 if (transport->receive_handler)
317 purple_input_remove(transport->receive_handler);
319 if (transport->transmit_buffer)
320 purple_circ_buffer_destroy(transport->transmit_buffer);
321 g_free(transport->public.buffer);
323 /* defer deletion of transport data structure to idle callback */
324 transport->is_valid = FALSE;
325 g_idle_add(transport_deferred_destroy, transport);
328 void sipe_purple_transport_close_all(struct sipe_backend_private *purple_private)
330 GSList *entry;
331 SIPE_DEBUG_INFO_NOFORMAT("sipe_purple_transport_close_all: entered");
332 while ((entry = purple_private->transports) != NULL)
333 sipe_backend_transport_disconnect(entry->data);
336 /* returns FALSE on write error */
337 static gboolean transport_write(struct sipe_transport_purple *transport)
339 gsize max_write;
341 max_write = purple_circ_buffer_get_max_read(transport->transmit_buffer);
342 if (max_write > 0) {
343 gssize written = transport->gsc ?
344 (gssize) purple_ssl_write(transport->gsc,
345 transport->transmit_buffer->outptr,
346 max_write) :
347 write(transport->socket,
348 transport->transmit_buffer->outptr,
349 max_write);
351 if (written < 0 && errno == EAGAIN) {
352 return TRUE;
353 } else if (written <= 0) {
354 SIPE_DEBUG_ERROR("Write error: %s (%d)", strerror(errno), errno);
355 transport->error(SIPE_TRANSPORT_CONNECTION,
356 _("Write error"));
357 return FALSE;
360 purple_circ_buffer_mark_read(transport->transmit_buffer,
361 written);
363 } else {
364 /* buffer is empty -> stop sending */
365 purple_input_remove(transport->transmit_handler);
366 transport->transmit_handler = 0;
369 return TRUE;
372 static void transport_canwrite_cb(gpointer data,
373 SIPE_UNUSED_PARAMETER gint source,
374 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
376 struct sipe_transport_purple *transport = data;
378 /* Ignore spurious "can write" events after disconnect */
379 if (transport->is_valid)
380 transport_write(data);
383 void sipe_backend_transport_message(struct sipe_transport_connection *conn,
384 const gchar *buffer)
386 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
388 /* add packet to circular buffer */
389 purple_circ_buffer_append(transport->transmit_buffer,
390 buffer, strlen(buffer));
392 /* initiate transmission */
393 if (!transport->transmit_handler) {
394 transport->transmit_handler = purple_input_add(transport->socket,
395 PURPLE_INPUT_WRITE,
396 transport_canwrite_cb,
397 transport);
401 void sipe_backend_transport_flush(struct sipe_transport_connection *conn)
403 struct sipe_transport_purple *transport = PURPLE_TRANSPORT;
405 while ( purple_circ_buffer_get_max_read(transport->transmit_buffer)
406 && transport_write(transport));
410 Local Variables:
411 mode: c
412 c-file-style: "bsd"
413 indent-tabs-mode: t
414 tab-width: 8
415 End: