1 /* purple
2 *
3 * Purple is the legal property of its developers, whose names are too numerous
4 * to list here. Please refer to the COPYRIGHT file distributed with this
5 * source distribution.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
20 */
21
22 #include "purple-socket.h"
23
24 #ifndef _WIN32
25 #include <errno.h>
26 #include <unistd.h>
27 #endif
28
29 #include "internal.h"
30
31 #include "debug.h"
32 #include "proxy.h"
33 #include "sslconn.h"
34
35 typedef enum {
36 PURPLE_SOCKET_STATE_DISCONNECTED = 0,
37 PURPLE_SOCKET_STATE_CONNECTING,
38 PURPLE_SOCKET_STATE_CONNECTED,
39 PURPLE_SOCKET_STATE_ERROR
40 } PurpleSocketState;
41
42 struct _PurpleSocket
43 {
44 PurpleConnection *gc;
45 gchar *host;
46 int port;
47 gboolean is_tls;
48 GHashTable *data;
49
50 PurpleSocketState state;
51
52 PurpleSslConnection *tls_connection;
53 PurpleProxyConnectData *raw_connection;
54 int fd;
55 guint inpa;
56
57 PurpleSocketConnectCb cb;
58 gpointer cb_data;
59 };
60
61 static GHashTable *handles = NULL;
62
63 static void
handle_add(PurpleSocket * ps)64 handle_add(PurpleSocket *ps)
65 {
66 PurpleConnection *gc = ps->gc;
67 GSList *l;
68
69 l = g_hash_table_lookup(handles, gc);
70 l = g_slist_prepend(l, ps);
71 g_hash_table_insert(handles, gc, l);
72 }
73
74 static void
handle_remove(PurpleSocket * ps)75 handle_remove(PurpleSocket *ps)
76 {
77 PurpleConnection *gc = ps->gc;
78 GSList *l;
79
80 l = g_hash_table_lookup(handles, gc);
81 if (l != NULL) {
82 l = g_slist_remove(l, ps);
83 g_hash_table_insert(handles, gc, l);
84 }
85 }
86
87 void
_purple_socket_init(void)88 _purple_socket_init(void)
89 {
90 handles = g_hash_table_new(g_direct_hash, g_direct_equal);
91 }
92
93 void
_purple_socket_uninit(void)94 _purple_socket_uninit(void)
95 {
96 g_hash_table_destroy(handles);
97 handles = NULL;
98 }
99
100 PurpleSocket *
purple_socket_new(PurpleConnection * gc)101 purple_socket_new(PurpleConnection *gc)
102 {
103 PurpleSocket *ps = g_new0(PurpleSocket, 1);
104
105 ps->gc = gc;
106 ps->fd = -1;
107 ps->port = -1;
108 ps->data = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
109
110 handle_add(ps);
111
112 return ps;
113 }
114
115 PurpleConnection *
purple_socket_get_connection(PurpleSocket * ps)116 purple_socket_get_connection(PurpleSocket *ps)
117 {
118 g_return_val_if_fail(ps != NULL, NULL);
119
120 return ps->gc;
121 }
122
123 static gboolean
purple_socket_check_state(PurpleSocket * ps,PurpleSocketState wanted_state)124 purple_socket_check_state(PurpleSocket *ps, PurpleSocketState wanted_state)
125 {
126 g_return_val_if_fail(ps != NULL, FALSE);
127
128 if (ps->state == wanted_state)
129 return TRUE;
130
131 purple_debug_error("socket", "invalid state: %d (should be: %d)",
132 ps->state, wanted_state);
133 ps->state = PURPLE_SOCKET_STATE_ERROR;
134 return FALSE;
135 }
136
137 void
purple_socket_set_tls(PurpleSocket * ps,gboolean is_tls)138 purple_socket_set_tls(PurpleSocket *ps, gboolean is_tls)
139 {
140 g_return_if_fail(ps != NULL);
141
142 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
143 return;
144
145 ps->is_tls = is_tls;
146 }
147
148 void
purple_socket_set_host(PurpleSocket * ps,const gchar * host)149 purple_socket_set_host(PurpleSocket *ps, const gchar *host)
150 {
151 g_return_if_fail(ps != NULL);
152
153 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
154 return;
155
156 g_free(ps->host);
157 ps->host = g_strdup(host);
158 }
159
160 void
purple_socket_set_port(PurpleSocket * ps,int port)161 purple_socket_set_port(PurpleSocket *ps, int port)
162 {
163 g_return_if_fail(ps != NULL);
164 g_return_if_fail(port >= 0);
165 g_return_if_fail(port <= 65535);
166
167 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
168 return;
169
170 ps->port = port;
171 }
172
173 static void
_purple_socket_connected_raw(gpointer _ps,gint fd,const gchar * error_message)174 _purple_socket_connected_raw(gpointer _ps, gint fd, const gchar *error_message)
175 {
176 PurpleSocket *ps = _ps;
177
178 ps->raw_connection = NULL;
179
180 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) {
181 if (fd > 0)
182 close(fd);
183 ps->cb(ps, _("Invalid socket state"), ps->cb_data);
184 return;
185 }
186
187 if (fd <= 0 || error_message != NULL) {
188 if (error_message == NULL)
189 error_message = _("Unknown error");
190 ps->fd = -1;
191 ps->state = PURPLE_SOCKET_STATE_ERROR;
192 ps->cb(ps, error_message, ps->cb_data);
193 return;
194 }
195
196 ps->state = PURPLE_SOCKET_STATE_CONNECTED;
197 ps->fd = fd;
198 ps->cb(ps, NULL, ps->cb_data);
199 }
200
201 static void
_purple_socket_connected_tls(gpointer _ps,PurpleSslConnection * tls_connection,PurpleInputCondition cond)202 _purple_socket_connected_tls(gpointer _ps, PurpleSslConnection *tls_connection,
203 PurpleInputCondition cond)
204 {
205 PurpleSocket *ps = _ps;
206
207 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTING)) {
208 purple_ssl_close(tls_connection);
209 ps->tls_connection = NULL;
210 ps->cb(ps, _("Invalid socket state"), ps->cb_data);
211 return;
212 }
213
214 if (ps->tls_connection->fd <= 0) {
215 ps->state = PURPLE_SOCKET_STATE_ERROR;
216 purple_ssl_close(tls_connection);
217 ps->tls_connection = NULL;
218 ps->cb(ps, _("Invalid file descriptor"), ps->cb_data);
219 return;
220 }
221
222 ps->state = PURPLE_SOCKET_STATE_CONNECTED;
223 ps->fd = ps->tls_connection->fd;
224 ps->cb(ps, NULL, ps->cb_data);
225 }
226
227 static void
_purple_socket_connected_tls_error(PurpleSslConnection * ssl_connection,PurpleSslErrorType error,gpointer _ps)228 _purple_socket_connected_tls_error(PurpleSslConnection *ssl_connection,
229 PurpleSslErrorType error, gpointer _ps)
230 {
231 PurpleSocket *ps = _ps;
232
233 ps->state = PURPLE_SOCKET_STATE_ERROR;
234 ps->tls_connection = NULL;
235 ps->cb(ps, purple_ssl_strerror(error), ps->cb_data);
236 }
237
238 gboolean
purple_socket_connect(PurpleSocket * ps,PurpleSocketConnectCb cb,gpointer user_data)239 purple_socket_connect(PurpleSocket *ps, PurpleSocketConnectCb cb,
240 gpointer user_data)
241 {
242 PurpleAccount *account = NULL;
243
244 g_return_val_if_fail(ps != NULL, FALSE);
245
246 if (ps->gc && purple_connection_is_disconnecting(ps->gc)) {
247 purple_debug_error("socket", "connection is being destroyed");
248 ps->state = PURPLE_SOCKET_STATE_ERROR;
249 return FALSE;
250 }
251
252 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_DISCONNECTED))
253 return FALSE;
254 ps->state = PURPLE_SOCKET_STATE_CONNECTING;
255
256 if (ps->host == NULL || ps->port < 0) {
257 purple_debug_error("socket", "Host or port is not specified");
258 ps->state = PURPLE_SOCKET_STATE_ERROR;
259 return FALSE;
260 }
261
262 if (ps->gc != NULL)
263 account = purple_connection_get_account(ps->gc);
264
265 ps->cb = cb;
266 ps->cb_data = user_data;
267
268 if (ps->is_tls) {
269 ps->tls_connection = purple_ssl_connect(account, ps->host,
270 ps->port, _purple_socket_connected_tls,
271 _purple_socket_connected_tls_error, ps);
272 } else {
273 ps->raw_connection = purple_proxy_connect(ps->gc, account,
274 ps->host, ps->port, _purple_socket_connected_raw, ps);
275 }
276
277 if (ps->tls_connection == NULL &&
278 ps->raw_connection == NULL)
279 {
280 ps->state = PURPLE_SOCKET_STATE_ERROR;
281 return FALSE;
282 }
283
284 return TRUE;
285 }
286
287 gssize
purple_socket_read(PurpleSocket * ps,guchar * buf,size_t len)288 purple_socket_read(PurpleSocket *ps, guchar *buf, size_t len)
289 {
290 g_return_val_if_fail(ps != NULL, -1);
291 g_return_val_if_fail(buf != NULL, -1);
292
293 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
294 return -1;
295
296 if (ps->is_tls)
297 return purple_ssl_read(ps->tls_connection, buf, len);
298 else
299 return read(ps->fd, buf, len);
300 }
301
302 gssize
purple_socket_write(PurpleSocket * ps,const guchar * buf,size_t len)303 purple_socket_write(PurpleSocket *ps, const guchar *buf, size_t len)
304 {
305 g_return_val_if_fail(ps != NULL, -1);
306 g_return_val_if_fail(buf != NULL, -1);
307
308 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
309 return -1;
310
311 if (ps->is_tls)
312 return purple_ssl_write(ps->tls_connection, buf, len);
313 else
314 return write(ps->fd, buf, len);
315 }
316
317 void
purple_socket_watch(PurpleSocket * ps,PurpleInputCondition cond,PurpleInputFunction func,gpointer user_data)318 purple_socket_watch(PurpleSocket *ps, PurpleInputCondition cond,
319 PurpleInputFunction func, gpointer user_data)
320 {
321 g_return_if_fail(ps != NULL);
322
323 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
324 return;
325
326 if (ps->inpa > 0)
327 purple_input_remove(ps->inpa);
328 ps->inpa = 0;
329
330 g_return_if_fail(ps->fd > 0);
331
332 if (func != NULL)
333 ps->inpa = purple_input_add(ps->fd, cond, func, user_data);
334 }
335
336 int
purple_socket_get_fd(PurpleSocket * ps)337 purple_socket_get_fd(PurpleSocket *ps)
338 {
339 g_return_val_if_fail(ps != NULL, -1);
340
341 if (!purple_socket_check_state(ps, PURPLE_SOCKET_STATE_CONNECTED))
342 return -1;
343
344 g_return_val_if_fail(ps->fd > 0, -1);
345
346 return ps->fd;
347 }
348
349 void
purple_socket_set_data(PurpleSocket * ps,const gchar * key,gpointer data)350 purple_socket_set_data(PurpleSocket *ps, const gchar *key, gpointer data)
351 {
352 g_return_if_fail(ps != NULL);
353 g_return_if_fail(key != NULL);
354
355 if (data == NULL)
356 g_hash_table_remove(ps->data, key);
357 else
358 g_hash_table_insert(ps->data, g_strdup(key), data);
359 }
360
361 gpointer
purple_socket_get_data(PurpleSocket * ps,const gchar * key)362 purple_socket_get_data(PurpleSocket *ps, const gchar *key)
363 {
364 g_return_val_if_fail(ps != NULL, NULL);
365 g_return_val_if_fail(key != NULL, NULL);
366
367 return g_hash_table_lookup(ps->data, key);
368 }
369
370 static void
purple_socket_cancel(PurpleSocket * ps)371 purple_socket_cancel(PurpleSocket *ps)
372 {
373 if (ps->inpa > 0)
374 purple_input_remove(ps->inpa);
375 ps->inpa = 0;
376
377 if (ps->tls_connection != NULL) {
378 purple_ssl_close(ps->tls_connection);
379 ps->fd = -1;
380 }
381 ps->tls_connection = NULL;
382
383 if (ps->raw_connection != NULL)
384 purple_proxy_connect_cancel(ps->raw_connection);
385 ps->raw_connection = NULL;
386
387 if (ps->fd > 0)
388 close(ps->fd);
389 ps->fd = 0;
390 }
391
392 void
purple_socket_destroy(PurpleSocket * ps)393 purple_socket_destroy(PurpleSocket *ps)
394 {
395 if (ps == NULL)
396 return;
397
398 handle_remove(ps);
399
400 purple_socket_cancel(ps);
401
402 g_free(ps->host);
403 g_hash_table_destroy(ps->data);
404 g_free(ps);
405 }
406
407 void
_purple_socket_cancel_with_connection(PurpleConnection * gc)408 _purple_socket_cancel_with_connection(PurpleConnection *gc)
409 {
410 GSList *it;
411
412 it = g_hash_table_lookup(handles, gc);
413 for (; it; it = g_slist_next(it)) {
414 PurpleSocket *ps = it->data;
415 purple_socket_cancel(ps);
416 }
417 }
418