| |
1 /* |
| |
2 * Gaim's oscar protocol plugin |
| |
3 * This file is the legal property of its developers. |
| |
4 * Please see the AUTHORS file distributed alongside this file. |
| |
5 * |
| |
6 * This library is free software; you can redistribute it and/or |
| |
7 * modify it under the terms of the GNU Lesser General Public |
| |
8 * License as published by the Free Software Foundation; either |
| |
9 * version 2 of the License, or (at your option) any later version. |
| |
10 * |
| |
11 * This library is distributed in the hope that it will be useful, |
| |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| |
14 * Lesser General Public License for more details. |
| |
15 * |
| |
16 * You should have received a copy of the GNU Lesser General Public |
| |
17 * License along with this library; if not, write to the Free Software |
| |
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| |
19 */ |
| |
20 |
| |
21 /* |
| |
22 * Functions dealing with peer connections. This includes the code |
| |
23 * used to establish a peer connection for both Oscar File transfer |
| |
24 * (OFT) and Oscar Direct Connect (ODC). (ODC is also referred to |
| |
25 * as DirectIM and IM Image.) |
| |
26 */ |
| |
27 |
| |
28 #ifdef HAVE_CONFIG_H |
| |
29 #include <config.h> |
| |
30 #endif |
| |
31 |
| |
32 /* From the oscar PRPL */ |
| |
33 #include "oscar.h" |
| |
34 #include "peer.h" |
| |
35 |
| |
36 /* From Gaim */ |
| |
37 #include "conversation.h" |
| |
38 #include "ft.h" |
| |
39 #include "network.h" |
| |
40 #include "notify.h" |
| |
41 #include "request.h" |
| |
42 #include "util.h" |
| |
43 |
| |
44 #ifndef _WIN32 |
| |
45 #include <stdio.h> |
| |
46 #include <netdb.h> |
| |
47 #include <sys/socket.h> |
| |
48 #include <netinet/in.h> |
| |
49 #include <arpa/inet.h> /* for inet_ntoa */ |
| |
50 #include <limits.h> /* for UINT_MAX */ |
| |
51 #endif |
| |
52 |
| |
53 #ifdef _WIN32 |
| |
54 #include "win32dep.h" |
| |
55 #endif |
| |
56 |
| |
57 /* |
| |
58 * I really want to switch all our networking code to using IPv6 only, |
| |
59 * but that really isn't a good idea at all. Evan S. of Adium says |
| |
60 * OS X sets all connections as "AF_INET6/PF_INET6," even if there is |
| |
61 * nothing inherently IPv6 about them. And I feel like Linux kernel |
| |
62 * 2.6.5 is doing the same thing. So we REALLY should accept |
| |
63 * connections if they're showing up as IPv6. Old OSes (Solaris?) |
| |
64 * that might not have full IPv6 support yet will fail if we try |
| |
65 * to use PF_INET6 but it isn't defined. --Mark Doliner |
| |
66 */ |
| |
67 #ifndef PF_INET6 |
| |
68 #define PF_INET6 PF_INET |
| |
69 #endif |
| |
70 |
| |
71 PeerConnection * |
| |
72 peer_connection_find_by_type(OscarData *od, const char *sn, OscarCapability type) |
| |
73 { |
| |
74 GList *cur; |
| |
75 PeerConnection *conn; |
| |
76 |
| |
77 for (cur = od->peer_connections; cur != NULL; cur = cur->next) |
| |
78 { |
| |
79 conn = cur->data; |
| |
80 if ((conn->type == type) && !aim_sncmp(conn->sn, sn)) |
| |
81 return conn; |
| |
82 } |
| |
83 |
| |
84 return NULL; |
| |
85 } |
| |
86 |
| |
87 /** |
| |
88 * @param cookie This must be exactly 8 characters. |
| |
89 */ |
| |
90 PeerConnection * |
| |
91 peer_connection_find_by_cookie(OscarData *od, const char *sn, const guchar *cookie) |
| |
92 { |
| |
93 GList *cur; |
| |
94 PeerConnection *conn; |
| |
95 |
| |
96 for (cur = od->peer_connections; cur != NULL; cur = cur->next) |
| |
97 { |
| |
98 conn = cur->data; |
| |
99 if (!memcmp(conn->cookie, cookie, 8) && !aim_sncmp(conn->sn, sn)) |
| |
100 return conn; |
| |
101 } |
| |
102 |
| |
103 return NULL; |
| |
104 } |
| |
105 |
| |
106 PeerConnection * |
| |
107 peer_connection_new(OscarData *od, OscarCapability type, const char *sn) |
| |
108 { |
| |
109 PeerConnection *conn; |
| |
110 GaimAccount *account; |
| |
111 |
| |
112 account = gaim_connection_get_account(od->gc); |
| |
113 |
| |
114 conn = g_new0(PeerConnection, 1); |
| |
115 conn->od = od; |
| |
116 conn->type = type; |
| |
117 conn->sn = g_strdup(sn); |
| |
118 conn->buffer_outgoing = gaim_circ_buffer_new(0); |
| |
119 conn->listenerfd = -1; |
| |
120 conn->fd = -1; |
| |
121 conn->lastactivity = time(NULL); |
| |
122 conn->use_proxy |= gaim_account_get_bool(account, "always_use_rv_proxy", FALSE); |
| |
123 |
| |
124 if (type == OSCAR_CAPABILITY_DIRECTIM) |
| |
125 memcpy(conn->magic, "ODC2", 4); |
| |
126 else if (type == OSCAR_CAPABILITY_SENDFILE) |
| |
127 memcpy(conn->magic, "OFT2", 4); |
| |
128 |
| |
129 od->peer_connections = g_list_prepend(od->peer_connections, conn); |
| |
130 |
| |
131 return conn; |
| |
132 } |
| |
133 |
| |
134 static void |
| |
135 peer_connection_close(PeerConnection *conn) |
| |
136 { |
| |
137 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
138 peer_odc_close(conn); |
| |
139 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) |
| |
140 peer_oft_close(conn); |
| |
141 |
| |
142 if (conn->connect_info != NULL) |
| |
143 { |
| |
144 gaim_proxy_connect_cancel(conn->connect_info); |
| |
145 conn->connect_info = NULL; |
| |
146 } |
| |
147 |
| |
148 if (conn->connect_timeout_timer != 0) |
| |
149 { |
| |
150 gaim_timeout_remove(conn->connect_timeout_timer); |
| |
151 conn->connect_timeout_timer = 0; |
| |
152 } |
| |
153 |
| |
154 if (conn->watcher_incoming != 0) |
| |
155 { |
| |
156 gaim_input_remove(conn->watcher_incoming); |
| |
157 conn->watcher_incoming = 0; |
| |
158 } |
| |
159 if (conn->watcher_outgoing != 0) |
| |
160 { |
| |
161 gaim_input_remove(conn->watcher_outgoing); |
| |
162 conn->watcher_outgoing = 0; |
| |
163 } |
| |
164 if (conn->listenerfd != -1) |
| |
165 { |
| |
166 close(conn->listenerfd); |
| |
167 conn->listenerfd = -1; |
| |
168 } |
| |
169 if (conn->fd != -1) |
| |
170 { |
| |
171 close(conn->fd); |
| |
172 conn->fd = -1; |
| |
173 } |
| |
174 |
| |
175 g_free(conn->buffer_incoming.data); |
| |
176 conn->buffer_incoming.data = NULL; |
| |
177 conn->buffer_incoming.len = 0; |
| |
178 conn->buffer_incoming.offset = 0; |
| |
179 |
| |
180 gaim_circ_buffer_destroy(conn->buffer_outgoing); |
| |
181 conn->buffer_outgoing = gaim_circ_buffer_new(0); |
| |
182 |
| |
183 conn->flags &= ~PEER_CONNECTION_FLAG_IS_INCOMING; |
| |
184 } |
| |
185 |
| |
186 static gboolean |
| |
187 peer_connection_destroy_cb(gpointer data) |
| |
188 { |
| |
189 PeerConnection *conn; |
| |
190 |
| |
191 conn = data; |
| |
192 |
| |
193 gaim_request_close_with_handle(conn); |
| |
194 |
| |
195 peer_connection_close(conn); |
| |
196 |
| |
197 if (conn->xfer != NULL) |
| |
198 { |
| |
199 GaimXferStatusType status; |
| |
200 conn->xfer->data = NULL; |
| |
201 status = gaim_xfer_get_status(conn->xfer); |
| |
202 if ((status != GAIM_XFER_STATUS_DONE) && |
| |
203 (status != GAIM_XFER_STATUS_CANCEL_LOCAL) && |
| |
204 (status != GAIM_XFER_STATUS_CANCEL_REMOTE)) |
| |
205 { |
| |
206 if ((conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED) || |
| |
207 (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_REFUSED)) |
| |
208 gaim_xfer_cancel_remote(conn->xfer); |
| |
209 else |
| |
210 gaim_xfer_cancel_local(conn->xfer); |
| |
211 } |
| |
212 gaim_xfer_unref(conn->xfer); |
| |
213 conn->xfer = NULL; |
| |
214 } |
| |
215 |
| |
216 g_free(conn->proxyip); |
| |
217 g_free(conn->clientip); |
| |
218 g_free(conn->verifiedip); |
| |
219 gaim_circ_buffer_destroy(conn->buffer_outgoing); |
| |
220 |
| |
221 conn->od->peer_connections = g_list_remove(conn->od->peer_connections, conn); |
| |
222 |
| |
223 g_free(conn); |
| |
224 |
| |
225 return FALSE; |
| |
226 } |
| |
227 |
| |
228 void |
| |
229 peer_connection_destroy(PeerConnection *conn, OscarDisconnectReason reason) |
| |
230 { |
| |
231 conn->disconnect_reason = reason; |
| |
232 if (conn->destroy_timeout != 0) |
| |
233 gaim_timeout_remove(conn->destroy_timeout); |
| |
234 peer_connection_destroy_cb(conn); |
| |
235 } |
| |
236 |
| |
237 void |
| |
238 peer_connection_schedule_destroy(PeerConnection *conn, OscarDisconnectReason reason) |
| |
239 { |
| |
240 if (conn->destroy_timeout != 0) |
| |
241 /* Already taken care of */ |
| |
242 return; |
| |
243 |
| |
244 gaim_debug_info("oscar", "Scheduling destruction of peer connection\n"); |
| |
245 conn->disconnect_reason = reason; |
| |
246 conn->destroy_timeout = gaim_timeout_add(0, peer_connection_destroy_cb, conn); |
| |
247 } |
| |
248 |
| |
249 /*******************************************************************/ |
| |
250 /* Begin code for receiving data on a peer connection */ |
| |
251 /*******************************************************************/ |
| |
252 |
| |
253 /** |
| |
254 * This should be used to read ODC and OFT framing info. It should |
| |
255 * NOT be used to read the payload sent across the connection (IMs, |
| |
256 * file data, etc), and it should NOT be used to read proxy negotiation |
| |
257 * headers. |
| |
258 * |
| |
259 * Unlike flap_connection_recv_cb(), this only reads one frame at a |
| |
260 * time. This is done so that the watcher can be changed during the |
| |
261 * handling of the frame. If the watcher is changed then this |
| |
262 * function will not read in any more data. This happens when |
| |
263 * reading the payload of a direct IM frame, or when we're |
| |
264 * receiving a file from the remote user. Once the data has been |
| |
265 * read, the watcher will be switched back to this function to |
| |
266 * continue reading the next frame. |
| |
267 */ |
| |
268 void |
| |
269 peer_connection_recv_cb(gpointer data, gint source, GaimInputCondition cond) |
| |
270 { |
| |
271 PeerConnection *conn; |
| |
272 ssize_t read; |
| |
273 guint8 header[6]; |
| |
274 |
| |
275 conn = data; |
| |
276 |
| |
277 /* Start reading a new ODC/OFT frame */ |
| |
278 if (conn->buffer_incoming.data == NULL) |
| |
279 { |
| |
280 /* Peek at the first 6 bytes to get the length */ |
| |
281 read = recv(conn->fd, &header, 6, MSG_PEEK); |
| |
282 |
| |
283 /* Check if the remote user closed the connection */ |
| |
284 if (read == 0) |
| |
285 { |
| |
286 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); |
| |
287 return; |
| |
288 } |
| |
289 |
| |
290 /* If there was an error then close the connection */ |
| |
291 if (read == -1) |
| |
292 { |
| |
293 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| |
294 /* No worries */ |
| |
295 return; |
| |
296 |
| |
297 peer_connection_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); |
| |
298 return; |
| |
299 } |
| |
300 |
| |
301 conn->lastactivity = time(NULL); |
| |
302 |
| |
303 /* If we don't even have the first 6 bytes then do nothing */ |
| |
304 if (read < 6) |
| |
305 return; |
| |
306 |
| |
307 /* Read the first 6 bytes (magic string and frame length) */ |
| |
308 read = recv(conn->fd, &header, 6, 0); |
| |
309 |
| |
310 /* All ODC/OFT frames must start with a magic string */ |
| |
311 if (memcmp(conn->magic, header, 4)) |
| |
312 { |
| |
313 gaim_debug_warning("oscar", "Expecting magic string to " |
| |
314 "be %c%c%c%c but received magic string %c%c%c%c. " |
| |
315 "Closing connection.\n", |
| |
316 conn->magic[0], conn->magic[1], conn->magic[2], |
| |
317 conn->magic[3], header[0], header[1], header[2], header[3]); |
| |
318 peer_connection_destroy(conn, OSCAR_DISCONNECT_INVALID_DATA); |
| |
319 return; |
| |
320 } |
| |
321 |
| |
322 /* Initialize a new temporary ByteStream for incoming data */ |
| |
323 conn->buffer_incoming.len = aimutil_get16(&header[4]) - 6; |
| |
324 conn->buffer_incoming.data = g_new(guint8, conn->buffer_incoming.len); |
| |
325 conn->buffer_incoming.offset = 0; |
| |
326 } |
| |
327 |
| |
328 /* Read data into the temporary buffer until it is complete */ |
| |
329 read = recv(conn->fd, |
| |
330 &conn->buffer_incoming.data[conn->buffer_incoming.offset], |
| |
331 conn->buffer_incoming.len - conn->buffer_incoming.offset, |
| |
332 0); |
| |
333 |
| |
334 /* Check if the remote user closed the connection */ |
| |
335 if (read == 0) |
| |
336 { |
| |
337 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); |
| |
338 return; |
| |
339 } |
| |
340 |
| |
341 if (read == -1) |
| |
342 { |
| |
343 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| |
344 /* No worries */ |
| |
345 return; |
| |
346 |
| |
347 peer_connection_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); |
| |
348 return; |
| |
349 } |
| |
350 |
| |
351 conn->lastactivity = time(NULL); |
| |
352 conn->buffer_incoming.offset += read; |
| |
353 if (conn->buffer_incoming.offset < conn->buffer_incoming.len) |
| |
354 /* Waiting for more data to arrive */ |
| |
355 return; |
| |
356 |
| |
357 /* We have a complete ODC/OFT frame! Handle it and continue reading */ |
| |
358 byte_stream_rewind(&conn->buffer_incoming); |
| |
359 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
360 { |
| |
361 peer_odc_recv_frame(conn, &conn->buffer_incoming); |
| |
362 } |
| |
363 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) |
| |
364 { |
| |
365 peer_oft_recv_frame(conn, &conn->buffer_incoming); |
| |
366 } |
| |
367 g_free(conn->buffer_incoming.data); |
| |
368 conn->buffer_incoming.data = NULL; |
| |
369 } |
| |
370 |
| |
371 /*******************************************************************/ |
| |
372 /* End code for receiving data on a peer connection */ |
| |
373 /*******************************************************************/ |
| |
374 |
| |
375 /*******************************************************************/ |
| |
376 /* Begin code for sending data on a peer connection */ |
| |
377 /*******************************************************************/ |
| |
378 |
| |
379 static void |
| |
380 send_cb(gpointer data, gint source, GaimInputCondition cond) |
| |
381 { |
| |
382 PeerConnection *conn; |
| |
383 gsize writelen; |
| |
384 ssize_t wrotelen; |
| |
385 |
| |
386 conn = data; |
| |
387 writelen = gaim_circ_buffer_get_max_read(conn->buffer_outgoing); |
| |
388 |
| |
389 if (writelen == 0) |
| |
390 { |
| |
391 gaim_input_remove(conn->watcher_outgoing); |
| |
392 conn->watcher_outgoing = 0; |
| |
393 return; |
| |
394 } |
| |
395 |
| |
396 wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); |
| |
397 if (wrotelen <= 0) |
| |
398 { |
| |
399 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| |
400 /* No worries */ |
| |
401 return; |
| |
402 |
| |
403 if (conn->ready) |
| |
404 peer_connection_schedule_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION); |
| |
405 else |
| |
406 { |
| |
407 /* |
| |
408 * This could happen when unable to send a negotiation |
| |
409 * frame to a peer proxy server. |
| |
410 */ |
| |
411 peer_connection_trynext(conn); |
| |
412 } |
| |
413 return; |
| |
414 } |
| |
415 |
| |
416 gaim_circ_buffer_mark_read(conn->buffer_outgoing, wrotelen); |
| |
417 conn->lastactivity = time(NULL); |
| |
418 } |
| |
419 |
| |
420 /** |
| |
421 * This should be called by OFT/ODC code to send a standard OFT or ODC |
| |
422 * frame across the peer connection along with some payload data. Or |
| |
423 * maybe a file. Anything, really. |
| |
424 */ |
| |
425 void |
| |
426 peer_connection_send(PeerConnection *conn, ByteStream *bs) |
| |
427 { |
| |
428 /* Add everything to our outgoing buffer */ |
| |
429 gaim_circ_buffer_append(conn->buffer_outgoing, bs->data, bs->len); |
| |
430 |
| |
431 /* If we haven't already started writing stuff, then start the cycle */ |
| |
432 if (conn->watcher_outgoing == 0) |
| |
433 { |
| |
434 conn->watcher_outgoing = gaim_input_add(conn->fd, |
| |
435 GAIM_INPUT_WRITE, send_cb, conn); |
| |
436 send_cb(conn, conn->fd, 0); |
| |
437 } |
| |
438 } |
| |
439 |
| |
440 /*******************************************************************/ |
| |
441 /* End code for sending data on a peer connection */ |
| |
442 /*******************************************************************/ |
| |
443 |
| |
444 /*******************************************************************/ |
| |
445 /* Begin code for establishing a peer connection */ |
| |
446 /*******************************************************************/ |
| |
447 |
| |
448 void |
| |
449 peer_connection_finalize_connection(PeerConnection *conn) |
| |
450 { |
| |
451 conn->watcher_incoming = gaim_input_add(conn->fd, |
| |
452 GAIM_INPUT_READ, peer_connection_recv_cb, conn); |
| |
453 |
| |
454 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
455 { |
| |
456 /* |
| |
457 * If we are connecting to them then send our cookie so they |
| |
458 * can verify who we are. Note: This doesn't seem to be |
| |
459 * necessary, but it also doesn't seem to hurt. |
| |
460 */ |
| |
461 if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) |
| |
462 peer_odc_send_cookie(conn); |
| |
463 } |
| |
464 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) |
| |
465 { |
| |
466 if (gaim_xfer_get_type(conn->xfer) == GAIM_XFER_SEND) |
| |
467 { |
| |
468 peer_oft_send_prompt(conn); |
| |
469 } |
| |
470 } |
| |
471 |
| |
472 /* |
| |
473 * Tell the remote user that we're connected (which may also imply |
| |
474 * that we've accepted their request). |
| |
475 */ |
| |
476 if (!(conn->flags & PEER_CONNECTION_FLAG_IS_INCOMING)) |
| |
477 aim_im_sendch2_connected(conn); |
| |
478 } |
| |
479 |
| |
480 /** |
| |
481 * We tried to make an outgoing connection to a remote user. It |
| |
482 * either connected or failed to connect. |
| |
483 */ |
| |
484 static void |
| |
485 peer_connection_established_cb(gpointer data, gint source, const gchar *error_message) |
| |
486 { |
| |
487 PeerConnection *conn; |
| |
488 |
| |
489 conn = data; |
| |
490 |
| |
491 conn->connect_info = NULL; |
| |
492 gaim_timeout_remove(conn->connect_timeout_timer); |
| |
493 conn->connect_timeout_timer = 0; |
| |
494 |
| |
495 if (source < 0) |
| |
496 { |
| |
497 peer_connection_trynext(conn); |
| |
498 return; |
| |
499 } |
| |
500 |
| |
501 conn->fd = source; |
| |
502 |
| |
503 peer_connection_finalize_connection(conn); |
| |
504 } |
| |
505 |
| |
506 /** |
| |
507 * This is the watcher callback for any listening socket that is |
| |
508 * waiting for a peer to connect. When a peer connects we set the |
| |
509 * input watcher to start reading data from the peer. |
| |
510 * |
| |
511 * To make sure that the connection is with the intended person and |
| |
512 * not with a malicious middle man, we don't send anything until we've |
| |
513 * received a peer frame from the remote user and have verified that |
| |
514 * the cookie in the peer frame matches the cookie that was exchanged |
| |
515 * in the channel 2 ICBM. |
| |
516 */ |
| |
517 void |
| |
518 peer_connection_listen_cb(gpointer data, gint source, GaimInputCondition cond) |
| |
519 { |
| |
520 PeerConnection *conn; |
| |
521 OscarData *od; |
| |
522 GaimConnection *gc; |
| |
523 struct sockaddr addr; |
| |
524 socklen_t addrlen = sizeof(addr); |
| |
525 |
| |
526 conn = data; |
| |
527 od = conn->od; |
| |
528 gc = od->gc; |
| |
529 |
| |
530 gaim_debug_info("oscar", "Accepting connection on listener socket.\n"); |
| |
531 |
| |
532 conn->fd = accept(conn->listenerfd, &addr, &addrlen); |
| |
533 if (conn->fd == -1) |
| |
534 { |
| |
535 if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) |
| |
536 /* No connection yet--no worries */ |
| |
537 /* TODO: Hmm, but they SHOULD be connected if we're here, right? */ |
| |
538 return; |
| |
539 |
| |
540 peer_connection_trynext(conn); |
| |
541 return; |
| |
542 } |
| |
543 |
| |
544 if ((addr.sa_family != PF_INET) && (addr.sa_family != PF_INET6)) |
| |
545 { |
| |
546 /* Invalid connection type?! Continue waiting. */ |
| |
547 close(conn->fd); |
| |
548 return; |
| |
549 } |
| |
550 |
| |
551 fcntl(conn->fd, F_SETFL, O_NONBLOCK); |
| |
552 gaim_input_remove(conn->watcher_incoming); |
| |
553 |
| |
554 peer_connection_finalize_connection(conn); |
| |
555 } |
| |
556 |
| |
557 /** |
| |
558 * We've just opened a listener socket, so we send the remote |
| |
559 * user an ICBM and ask them to connect to us. |
| |
560 */ |
| |
561 static void |
| |
562 peer_connection_establish_listener_cb(int listenerfd, gpointer data) |
| |
563 { |
| |
564 NewPeerConnectionData *new_conn_data; |
| |
565 PeerConnection *conn; |
| |
566 OscarData *od; |
| |
567 GaimConnection *gc; |
| |
568 GaimAccount *account; |
| |
569 GaimConversation *conv; |
| |
570 char *tmp; |
| |
571 FlapConnection *bos_conn; |
| |
572 const char *listener_ip; |
| |
573 unsigned short listener_port; |
| |
574 |
| |
575 new_conn_data = data; |
| |
576 gc = new_conn_data->gc; |
| |
577 conn = new_conn_data->conn; |
| |
578 g_free(new_conn_data); |
| |
579 |
| |
580 if (!GAIM_CONNECTION_IS_VALID(gc)) |
| |
581 { |
| |
582 if (listenerfd != -1) |
| |
583 close(listenerfd); |
| |
584 return; |
| |
585 } |
| |
586 |
| |
587 if (listenerfd == -1) |
| |
588 { |
| |
589 /* Could not open listener socket */ |
| |
590 peer_connection_trynext(conn); |
| |
591 return; |
| |
592 } |
| |
593 |
| |
594 od = conn->od; |
| |
595 account = gaim_connection_get_account(gc); |
| |
596 conn->listenerfd = listenerfd; |
| |
597 |
| |
598 /* Send the "please connect to me!" ICBM */ |
| |
599 bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM); |
| |
600 if (bos_conn == NULL) |
| |
601 { |
| |
602 /* Not good */ |
| |
603 peer_connection_trynext(conn); |
| |
604 return; |
| |
605 } |
| |
606 |
| |
607 listener_ip = gaim_network_get_my_ip(bos_conn->fd); |
| |
608 listener_port = gaim_network_get_port_from_fd(conn->listenerfd); |
| |
609 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
610 { |
| |
611 aim_im_sendch2_odc_requestdirect(od, |
| |
612 conn->cookie, conn->sn, gaim_network_ip_atoi(listener_ip), |
| |
613 listener_port, ++conn->lastrequestnumber); |
| |
614 |
| |
615 /* Print a message to a local conversation window */ |
| |
616 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); |
| |
617 tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for " |
| |
618 "Direct IM."), conn->sn, listener_ip, listener_port); |
| |
619 gaim_conversation_write(conv, NULL, tmp, GAIM_MESSAGE_SYSTEM, time(NULL)); |
| |
620 g_free(tmp); |
| |
621 } |
| |
622 else if (conn->type == OSCAR_CAPABILITY_SENDFILE) |
| |
623 { |
| |
624 aim_im_sendch2_sendfile_requestdirect(od, |
| |
625 conn->cookie, conn->sn, |
| |
626 gaim_network_ip_atoi(listener_ip), |
| |
627 listener_port, ++conn->lastrequestnumber, |
| |
628 (const gchar *)conn->xferdata.name, |
| |
629 conn->xferdata.size, conn->xferdata.totfiles); |
| |
630 } |
| |
631 } |
| |
632 |
| |
633 /** |
| |
634 * This is a callback function used when we're connecting to a peer |
| |
635 * using either the client IP or the verified IP and the connection |
| |
636 * took longer than 15 seconds to complete. We do this because |
| |
637 * waiting for the OS to time out the connection attempt is not |
| |
638 * practical--the default timeout on many OSes can be 3 minutes or |
| |
639 * more, and users are impatient. |
| |
640 * |
| |
641 * Worst case scenario: the user is connected to the Internet using |
| |
642 * a modem with severe lag. The peer connections fail and Gaim falls |
| |
643 * back to using a proxied connection. The lower bandwidth |
| |
644 * limitations imposed by the proxied connection won't matter because |
| |
645 * the user is using a modem. |
| |
646 * |
| |
647 * I suppose this line of thinking is discriminatory against people |
| |
648 * with very high lag but decent throughput who are transferring |
| |
649 * large files. But we don't care about those people. |
| |
650 */ |
| |
651 static gboolean |
| |
652 peer_connection_tooktoolong(gpointer data) |
| |
653 { |
| |
654 PeerConnection *conn; |
| |
655 |
| |
656 conn = data; |
| |
657 |
| |
658 gaim_debug_info("oscar", "Peer connection timed out after 15 seconds. " |
| |
659 "Trying next method...\n"); |
| |
660 |
| |
661 peer_connection_close(conn); |
| |
662 |
| |
663 peer_connection_trynext(conn); |
| |
664 |
| |
665 /* Cancel this timer. It'll be added again, if needed. */ |
| |
666 return FALSE; |
| |
667 } |
| |
668 |
| |
669 /** |
| |
670 * Try to establish the given PeerConnection using a defined |
| |
671 * sequence of steps. |
| |
672 */ |
| |
673 void |
| |
674 peer_connection_trynext(PeerConnection *conn) |
| |
675 { |
| |
676 GaimAccount *account; |
| |
677 |
| |
678 account = gaim_connection_get_account(conn->od->gc); |
| |
679 |
| |
680 /* |
| |
681 * Close any remnants of a previous failed connection attempt. |
| |
682 */ |
| |
683 peer_connection_close(conn); |
| |
684 |
| |
685 /* |
| |
686 * 1. Attempt to connect to the remote user using their verifiedip. |
| |
687 */ |
| |
688 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_VERIFIEDIP) && |
| |
689 (conn->verifiedip != NULL) && (conn->port != 0) && (!conn->use_proxy)) |
| |
690 { |
| |
691 conn->flags |= PEER_CONNECTION_FLAG_TRIED_VERIFIEDIP; |
| |
692 |
| |
693 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
694 { |
| |
695 gchar *tmp; |
| |
696 GaimConversation *conv; |
| |
697 tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."), |
| |
698 conn->verifiedip, conn->port); |
| |
699 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); |
| |
700 gaim_conversation_write(conv, NULL, tmp, |
| |
701 GAIM_MESSAGE_SYSTEM, time(NULL)); |
| |
702 g_free(tmp); |
| |
703 } |
| |
704 |
| |
705 conn->connect_info = gaim_proxy_connect(account, |
| |
706 conn->verifiedip, conn->port, |
| |
707 peer_connection_established_cb, conn); |
| |
708 if (conn->connect_info != NULL) |
| |
709 { |
| |
710 /* Connecting... */ |
| |
711 conn->connect_timeout_timer = gaim_timeout_add(15000, |
| |
712 peer_connection_tooktoolong, conn); |
| |
713 return; |
| |
714 } |
| |
715 } |
| |
716 |
| |
717 /* |
| |
718 * 2. Attempt to connect to the remote user using their clientip. |
| |
719 */ |
| |
720 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_CLIENTIP) && |
| |
721 (conn->clientip != NULL) && (conn->port != 0) && (!conn->use_proxy)) |
| |
722 { |
| |
723 conn->flags |= PEER_CONNECTION_FLAG_TRIED_CLIENTIP; |
| |
724 |
| |
725 if ((conn->verifiedip == NULL) || |
| |
726 strcmp(conn->verifiedip, conn->clientip)) |
| |
727 { |
| |
728 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
729 { |
| |
730 gchar *tmp; |
| |
731 GaimConversation *conv; |
| |
732 tmp = g_strdup_printf(_("Attempting to connect to %s:%hu."), |
| |
733 conn->clientip, conn->port); |
| |
734 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); |
| |
735 gaim_conversation_write(conv, NULL, tmp, |
| |
736 GAIM_MESSAGE_SYSTEM, time(NULL)); |
| |
737 g_free(tmp); |
| |
738 } |
| |
739 |
| |
740 conn->connect_info = gaim_proxy_connect(account, |
| |
741 conn->clientip, conn->port, |
| |
742 peer_connection_established_cb, conn); |
| |
743 if (conn->connect_info != NULL) |
| |
744 { |
| |
745 /* Connecting... */ |
| |
746 conn->connect_timeout_timer = gaim_timeout_add(15000, |
| |
747 peer_connection_tooktoolong, conn); |
| |
748 return; |
| |
749 } |
| |
750 } |
| |
751 } |
| |
752 |
| |
753 /* |
| |
754 * 3. Attempt to have the remote user connect to us (using both |
| |
755 * our verifiedip and our clientip). |
| |
756 */ |
| |
757 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_INCOMING) && |
| |
758 (!conn->use_proxy)) |
| |
759 { |
| |
760 NewPeerConnectionData *new_conn_data; |
| |
761 |
| |
762 new_conn_data = g_new(NewPeerConnectionData, 1); |
| |
763 new_conn_data->gc = conn->od->gc; |
| |
764 new_conn_data->conn = conn; |
| |
765 |
| |
766 conn->flags |= PEER_CONNECTION_FLAG_TRIED_INCOMING; |
| |
767 |
| |
768 /* |
| |
769 * Remote user is connecting to us, so we'll need to verify |
| |
770 * that the user who connected is our friend. |
| |
771 */ |
| |
772 conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; |
| |
773 |
| |
774 if (gaim_network_listen_range(5190, 5290, SOCK_STREAM, |
| |
775 peer_connection_establish_listener_cb, new_conn_data)) |
| |
776 { |
| |
777 /* Opening listener socket... */ |
| |
778 return; |
| |
779 } |
| |
780 |
| |
781 g_free(new_conn_data); |
| |
782 } |
| |
783 |
| |
784 /* |
| |
785 * 4. Attempt to have both users connect to an intermediate proxy |
| |
786 * server. |
| |
787 */ |
| |
788 if (!(conn->flags & PEER_CONNECTION_FLAG_TRIED_PROXY)) |
| |
789 { |
| |
790 conn->flags |= PEER_CONNECTION_FLAG_TRIED_PROXY; |
| |
791 |
| |
792 /* |
| |
793 * If we initiate the proxy connection, then the remote user |
| |
794 * could be anyone, so we need to verify that the user who |
| |
795 * connected is our friend. |
| |
796 */ |
| |
797 if (!conn->use_proxy) |
| |
798 conn->flags |= PEER_CONNECTION_FLAG_IS_INCOMING; |
| |
799 |
| |
800 if (conn->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
801 { |
| |
802 gchar *tmp; |
| |
803 GaimConversation *conv; |
| |
804 tmp = g_strdup_printf(_("Attempting to connect via proxy server.")); |
| |
805 conv = gaim_conversation_new(GAIM_CONV_TYPE_IM, account, conn->sn); |
| |
806 gaim_conversation_write(conv, NULL, tmp, |
| |
807 GAIM_MESSAGE_SYSTEM, time(NULL)); |
| |
808 g_free(tmp); |
| |
809 } |
| |
810 |
| |
811 conn->connect_info = gaim_proxy_connect(account, |
| |
812 (conn->proxyip != NULL) ? conn->proxyip : PEER_PROXY_SERVER, |
| |
813 PEER_PROXY_PORT, |
| |
814 peer_proxy_connection_established_cb, conn); |
| |
815 if (conn->connect_info != NULL) |
| |
816 { |
| |
817 /* Connecting... */ |
| |
818 return; |
| |
819 } |
| |
820 } |
| |
821 |
| |
822 /* Give up! */ |
| |
823 peer_connection_destroy(conn, OSCAR_DISCONNECT_COULD_NOT_CONNECT); |
| |
824 } |
| |
825 |
| |
826 /** |
| |
827 * Initiate a peer connection with someone. |
| |
828 */ |
| |
829 void |
| |
830 peer_connection_propose(OscarData *od, OscarCapability type, const char *sn) |
| |
831 { |
| |
832 PeerConnection *conn; |
| |
833 |
| |
834 if (type == OSCAR_CAPABILITY_DIRECTIM) |
| |
835 { |
| |
836 conn = peer_connection_find_by_type(od, sn, type); |
| |
837 if (conn != NULL) |
| |
838 { |
| |
839 if (conn->ready) |
| |
840 { |
| |
841 GaimAccount *account; |
| |
842 GaimConversation *conv; |
| |
843 |
| |
844 gaim_debug_info("oscar", "Already have a direct IM " |
| |
845 "session with %s.\n", sn); |
| |
846 account = gaim_connection_get_account(od->gc); |
| |
847 conv = gaim_find_conversation_with_account(GAIM_CONV_TYPE_IM, |
| |
848 sn, account); |
| |
849 if (conv != NULL) |
| |
850 gaim_conversation_present(conv); |
| |
851 return; |
| |
852 } |
| |
853 |
| |
854 /* Cancel the old connection and try again */ |
| |
855 peer_connection_destroy(conn, OSCAR_DISCONNECT_RETRYING); |
| |
856 } |
| |
857 } |
| |
858 |
| |
859 conn = peer_connection_new(od, type, sn); |
| |
860 conn->flags |= PEER_CONNECTION_FLAG_INITIATED_BY_ME; |
| |
861 conn->flags |= PEER_CONNECTION_FLAG_APPROVED; |
| |
862 aim_icbm_makecookie(conn->cookie); |
| |
863 |
| |
864 peer_connection_trynext(conn); |
| |
865 } |
| |
866 |
| |
867 /** |
| |
868 * Someone else wants to establish a peer connection with us, |
| |
869 * and we said yes. |
| |
870 */ |
| |
871 static void |
| |
872 peer_connection_got_proposition_yes_cb(gpointer data, gint id) |
| |
873 { |
| |
874 PeerConnection *conn; |
| |
875 |
| |
876 conn = data; |
| |
877 |
| |
878 conn->flags |= PEER_CONNECTION_FLAG_APPROVED; |
| |
879 peer_connection_trynext(conn); |
| |
880 } |
| |
881 |
| |
882 /** |
| |
883 * Someone else wants to establish a peer connection with us, |
| |
884 * and we said no. |
| |
885 * |
| |
886 * "Well, one time my friend asked me if I wanted to play the |
| |
887 * piccolo. But I said no." |
| |
888 */ |
| |
889 static void |
| |
890 peer_connection_got_proposition_no_cb(gpointer data, gint id) |
| |
891 { |
| |
892 PeerConnection *conn; |
| |
893 |
| |
894 conn = data; |
| |
895 |
| |
896 aim_im_denytransfer(conn->od, conn->sn, conn->cookie, |
| |
897 AIM_TRANSFER_DENY_DECLINE); |
| |
898 peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED); |
| |
899 } |
| |
900 |
| |
901 /** |
| |
902 * Someone else wants to establish a peer connection with us. |
| |
903 */ |
| |
904 void |
| |
905 peer_connection_got_proposition(OscarData *od, const gchar *sn, const gchar *message, IcbmArgsCh2 *args) |
| |
906 { |
| |
907 GaimConnection *gc; |
| |
908 GaimAccount *account; |
| |
909 PeerConnection *conn; |
| |
910 gchar *buf; |
| |
911 |
| |
912 gc = od->gc; |
| |
913 account = gaim_connection_get_account(gc); |
| |
914 |
| |
915 /* |
| |
916 * If we have a connection with this same cookie then they are |
| |
917 * probably just telling us they weren't able to connect to us |
| |
918 * and we should try connecting to them, instead. Or they want |
| |
919 * to go through a proxy. |
| |
920 */ |
| |
921 conn = peer_connection_find_by_cookie(od, sn, args->cookie); |
| |
922 if ((conn != NULL) && (conn->type == args->type)) |
| |
923 { |
| |
924 gaim_debug_info("oscar", "Remote user wants to try a " |
| |
925 "different connection method\n"); |
| |
926 g_free(conn->proxyip); |
| |
927 g_free(conn->clientip); |
| |
928 g_free(conn->verifiedip); |
| |
929 if (args->use_proxy) |
| |
930 conn->proxyip = g_strdup(args->proxyip); |
| |
931 else |
| |
932 conn->proxyip = NULL; |
| |
933 conn->verifiedip = g_strdup(args->verifiedip); |
| |
934 conn->clientip = g_strdup(args->clientip); |
| |
935 conn->port = args->port; |
| |
936 conn->use_proxy |= args->use_proxy; |
| |
937 conn->lastrequestnumber++; |
| |
938 peer_connection_trynext(conn); |
| |
939 return; |
| |
940 } |
| |
941 |
| |
942 /* If this is a direct IM, then close any existing session */ |
| |
943 if (args->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
944 { |
| |
945 conn = peer_connection_find_by_type(od, sn, args->type); |
| |
946 if (conn != NULL) |
| |
947 { |
| |
948 /* Close the old direct IM and start a new one */ |
| |
949 gaim_debug_info("oscar", "Received new direct IM request " |
| |
950 "from %s. Destroying old connection.\n", sn); |
| |
951 peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED); |
| |
952 } |
| |
953 } |
| |
954 |
| |
955 /* Check for proper arguments */ |
| |
956 if (args->type == OSCAR_CAPABILITY_SENDFILE) |
| |
957 { |
| |
958 if ((args->info.sendfile.filename == NULL) || |
| |
959 (args->info.sendfile.totsize == 0) || |
| |
960 (args->info.sendfile.totfiles == 0)) |
| |
961 { |
| |
962 gaim_debug_warning("oscar", |
| |
963 "%s tried to send you a file with incomplete " |
| |
964 "information.\n", sn); |
| |
965 return; |
| |
966 } |
| |
967 } |
| |
968 |
| |
969 conn = peer_connection_new(od, args->type, sn); |
| |
970 memcpy(conn->cookie, args->cookie, 8); |
| |
971 if (args->use_proxy) |
| |
972 conn->proxyip = g_strdup(args->proxyip); |
| |
973 conn->clientip = g_strdup(args->clientip); |
| |
974 conn->verifiedip = g_strdup(args->verifiedip); |
| |
975 conn->port = args->port; |
| |
976 conn->use_proxy |= args->use_proxy; |
| |
977 conn->lastrequestnumber++; |
| |
978 |
| |
979 if (args->type == OSCAR_CAPABILITY_DIRECTIM) |
| |
980 { |
| |
981 buf = g_strdup_printf(_("%s has just asked to directly connect to %s"), |
| |
982 sn, gaim_account_get_username(account)); |
| |
983 |
| |
984 gaim_request_action(conn, NULL, buf, |
| |
985 _("This requires a direct connection between " |
| |
986 "the two computers and is necessary for IM " |
| |
987 "Images. Because your IP address will be " |
| |
988 "revealed, this may be considered a privacy " |
| |
989 "risk."), |
| |
990 GAIM_DEFAULT_ACTION_NONE, conn, 2, |
| |
991 _("_Connect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), |
| |
992 _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb)); |
| |
993 } |
| |
994 else if (args->type == OSCAR_CAPABILITY_SENDFILE) |
| |
995 { |
| |
996 gchar *filename; |
| |
997 |
| |
998 conn->xfer = gaim_xfer_new(account, GAIM_XFER_RECEIVE, sn); |
| |
999 conn->xfer->data = conn; |
| |
1000 gaim_xfer_ref(conn->xfer); |
| |
1001 gaim_xfer_set_size(conn->xfer, args->info.sendfile.totsize); |
| |
1002 |
| |
1003 /* Set the file name */ |
| |
1004 if (g_utf8_validate(args->info.sendfile.filename, -1, NULL)) |
| |
1005 filename = g_strdup(args->info.sendfile.filename); |
| |
1006 else |
| |
1007 filename = gaim_utf8_salvage(args->info.sendfile.filename); |
| |
1008 |
| |
1009 if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR) |
| |
1010 { |
| |
1011 /* |
| |
1012 * If they are sending us a directory then the last character |
| |
1013 * of the file name will be an asterisk. We don't want to |
| |
1014 * save stuff to a directory named "*" so we remove the |
| |
1015 * asterisk from the file name. |
| |
1016 */ |
| |
1017 char *tmp = strrchr(filename, '\\'); |
| |
1018 if ((tmp != NULL) && (tmp[1] == '*')) |
| |
1019 tmp[0] = '\0'; |
| |
1020 } |
| |
1021 gaim_xfer_set_filename(conn->xfer, filename); |
| |
1022 g_free(filename); |
| |
1023 |
| |
1024 /* |
| |
1025 * Set the message (unless this is the dummy message from an |
| |
1026 * ICQ client or an empty message from an AIM client. |
| |
1027 * TODO: Maybe we should strip HTML and then see if strlen>0? |
| |
1028 */ |
| |
1029 if ((message != NULL) && |
| |
1030 (g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) && |
| |
1031 (g_ascii_strcasecmp(message, "<HTML>") != 0)) |
| |
1032 { |
| |
1033 gaim_xfer_set_message(conn->xfer, message); |
| |
1034 } |
| |
1035 |
| |
1036 /* Setup our I/O op functions */ |
| |
1037 gaim_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init); |
| |
1038 gaim_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end); |
| |
1039 gaim_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel); |
| |
1040 gaim_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel); |
| |
1041 gaim_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv); |
| |
1042 |
| |
1043 /* Now perform the request */ |
| |
1044 gaim_xfer_request(conn->xfer); |
| |
1045 } |
| |
1046 } |
| |
1047 |
| |
1048 /*******************************************************************/ |
| |
1049 /* End code for establishing a peer connection */ |
| |
1050 /*******************************************************************/ |