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