Mercurial > pidgin
comparison libgaim/protocols/oscar/peer.c @ 14192:60b1bc8dbf37
[gaim-migrate @ 16863]
Renamed 'core' to 'libgaim'
committer: Tailor Script <tailor@pidgin.im>
author | Evan Schoenberg <evan.s@dreskin.net> |
---|---|
date | Sat, 19 Aug 2006 01:50:10 +0000 |
parents | |
children | baff095b146c |
comparison
equal
deleted
inserted
replaced
14191:009db0b357b5 | 14192:60b1bc8dbf37 |
---|---|
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 /*******************************************************************/ |