1 /*
2 * This file handles incoming and outgoing file requests.
3 *
4 * climm Copyright (C) © 2001-2010 Rüdiger Kuhlmann
5 *
6 * climm is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 dated June, 1991.
9 *
10 * climm is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
13 * License for more details.
14 *
15 * In addition, as a special exception permission is granted to link the
16 * code of this release of climm with the OpenSSL project's "OpenSSL"
17 * library, and distribute the linked executables. You must obey the GNU
18 * General Public License in all respects for all of the code used other
19 * than "OpenSSL". If you modify this file, you may extend this exception
20 * to your version of the file, but you are not obligated to do so. If you
21 * do not wish to do so, delete this exception statement from your version
22 * of this file.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this package; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 * 02111-1307, USA.
28 *
29 * $Id: oscar_dc_file.c 2864 2010-03-16 22:48:25Z kuhlmann $
30 */
31
32 #include "climm.h"
33
34 #ifdef ENABLE_PEER2PEER
35
36 #include <unistd.h>
37 #include <assert.h>
38 #include <fcntl.h>
39 #include <errno.h>
40 #if HAVE_SYS_SOCKET_H
41 #include <sys/socket.h>
42 #endif
43 #if HAVE_NETINET_IN_H
44 #include <netinet/in.h>
45 #endif
46 #if HAVE_ARPA_INET_H
47 #include <arpa/inet.h>
48 #endif
49 #if HAVE_SYS_STAT_H
50 #include <sys/stat.h>
51 #endif
52
53 #include "preferences.h"
54 #include "connection.h"
55 #include "packet.h"
56 #include "util_ui.h"
57 #include "util_io.h"
58 #include "util_rl.h"
59 #include "util.h"
60 #include "im_response.h"
61 #include "oscar_dc.h"
62 #include "conv.h"
63 #include "oscar_base.h"
64 #include "oscar_dc_file.h"
65
66 #define FAIL(x) { err = x; break; }
67 #define PeerFileClose TCPClose
68
69 /*
70 * Create a new file listener unless one already exists.
71 */
PeerFileCreate(Server * serv)72 Connection *PeerFileCreate (Server *serv)
73 {
74 Connection *flist;
75
76 assert (serv);
77
78 if (!serv->oscar_dc || serv->oscar_dc->version < 6)
79 return NULL;
80
81 if ((flist = ServerFindChild (serv, NULL, TYPE_FILELISTEN)))
82 return flist;
83
84 flist = ServerChild (serv, NULL, TYPE_FILELISTEN);
85 if (!flist)
86 return NULL;
87
88 flist->oscar_dc_seq = -1;
89 flist->version = serv->oscar_dc->version;
90 flist->cont = serv->oscar_dc->cont;
91 flist->port = ServerPrefVal (serv, CO_OSCAR_DC_PORT);
92 flist->dispatch = &TCPDispatchMain;
93
94 if (prG->verbose)
95 rl_printf (i18n (2519, "Opening file listener connection at %slocalhost%s:%s%ld%s... "),
96 COLQUOTE, COLNONE, COLQUOTE, UD2UL (flist->port), COLNONE);
97
98 UtilIOListenTCP (flist);
99
100 return flist;
101 }
102
103 /*
104 * Handles a timeout.
105 */
106
PeerFileTO(Event * event)107 void PeerFileTO (Event *event)
108 {
109 QueueEnqueue (event);
110 QueueRelease (event);
111 event->callback = NULL;
112 }
113
114 /*
115 * Handles user reaction to incoming file request
116 */
PeerFileUser(UDWORD seq,Contact * cont,const char * reason,Server * serv)117 void PeerFileUser (UDWORD seq, Contact *cont, const char *reason, Server *serv)
118 {
119 Event *event;
120
121 if (!(event = QueueDequeue2 (serv->conn, QUEUE_USERFILEACK, seq, cont)))
122 {
123 rl_printf (i18n (2258, "No pending incoming file transfer request for %s with (sequence %ld) found.\n"),
124 cont ? cont->nick : "<?>", UD2UL (seq));
125 }
126 else
127 {
128 if (reason)
129 event->opt = OptSetVals (event->opt, CO_FILEACCEPT, 0, CO_REFUSE, reason, 0);
130 else
131 event->opt = OptSetVals (event->opt, CO_FILEACCEPT, 1, 0);
132 QueueEnqueue (event);
133 QueueRelease (event);
134 }
135 }
136
137 /*
138 * Handles an incoming file request.
139 */
PeerFileIncAccept(Connection * list,Event * event)140 UBYTE PeerFileIncAccept (Connection *list, Event *event)
141 {
142 Connection *flist, *fpeer;
143 Server *serv;
144 Contact *cont;
145 UDWORD opt_bytes, opt_acc;
146 const char *opt_files;
147
148 if (!OptGetVal (event->opt, CO_BYTES, &opt_bytes))
149 opt_bytes = 0;
150 if (!OptGetStr (event->opt, CO_MSGTEXT, &opt_files))
151 opt_files = "";
152
153 ASSERT_MSGLISTEN(list);
154
155 serv = list->serv;
156 cont = event->cont;
157 flist = PeerFileCreate (serv);
158
159 if ((event->wait && !OptGetVal (event->wait->opt, CO_FILEACCEPT, &opt_acc))
160 || !opt_acc || !cont || !flist
161 || !(fpeer = ServerChild (flist->serv, cont, TYPE_FILEDIRECT)))
162 {
163 const char *txt;
164 if (!OptGetStr (event->wait->opt, CO_REFUSE, &txt))
165 txt = "";
166 IMIntMsgFat (cont, NOW, ims_offline, INT_FILE_REJING, txt, opt_files, 0, 0);
167 return FALSE;
168 }
169 ASSERT_FILELISTEN (flist);
170 ASSERT_FILEDIRECT (fpeer);
171
172 fpeer->port = 0;
173 fpeer->ip = 0;
174 fpeer->connect = 0;
175 fpeer->version = serv->oscar_dc->version;
176 s_repl (&fpeer->server, NULL);
177 fpeer->oscar_file_len = opt_bytes;
178 fpeer->oscar_file_done = 0;
179
180 IMIntMsgFat (cont, NOW, ims_offline, INT_FILE_ACKING, "", opt_files, 0, opt_bytes);
181
182 return TRUE;
183 }
184
185 /*
186 * Checks the file request response.
187 */
PeerFileAccept(Connection * peer,UWORD ackstatus,UDWORD port)188 BOOL PeerFileAccept (Connection *peer, UWORD ackstatus, UDWORD port)
189 {
190 Connection *flist, *fpeer;
191
192 flist = PeerFileCreate (peer->serv);
193 fpeer = ServerFindChild (flist->serv, peer->cont, TYPE_FILEDIRECT);
194
195 if (!flist || !fpeer || !port || (ackstatus == TCP_ACK_REFUSE))
196 {
197 if (fpeer)
198 TCPClose (fpeer);
199
200 return 0;
201 }
202
203 ASSERT_MSGDIRECT(peer);
204 ASSERT_FILELISTEN(flist);
205 ASSERT_FILEDIRECT(fpeer);
206
207 fpeer->connect = 0;
208 fpeer->oscar_dc_seq = 0;
209 fpeer->port = port;
210 fpeer->ip = peer->ip;
211 s_repl (&fpeer->server, s_ip (fpeer->ip));
212
213 if (prG->verbose)
214 rl_printf (i18n (2520, "Opening file transfer connection to %s:%s%ld%s... \n"),
215 s_wordquote (fpeer->server), COLQUOTE, UD2UL (fpeer->port), COLNONE);
216
217 TCPDispatchConn (fpeer);
218
219 return 1;
220 }
221
222 /*
223 * Dispatches incoming packets on the file transfer connection.
224 */
PeerFileDispatch(Connection * fpeer)225 void PeerFileDispatch (Connection *fpeer)
226 {
227 Contact *cont;
228 Packet *pak;
229 int err = 0;
230
231 ASSERT_FILEDIRECT (fpeer);
232 assert (fpeer->cont);
233
234 cont = fpeer->cont;
235
236 if (fpeer->connect & CONNECT_SELECT_W && UtilIOSelectIs (fpeer->sok, WRITEFDS))
237 {
238 fpeer->connect &= ~CONNECT_SELECT_W;
239 if (fpeer->oscar_file->oscar_file_len)
240 ReadLinePromptUpdate (s_sprintf ("[%s%ld:%02d%%%s] %s%s",
241 COLCONTACT, UD2UL (fpeer->oscar_file->oscar_file_done), (int)((100.0 * fpeer->oscar_file->oscar_file_done) / fpeer->oscar_file->oscar_file_len),
242 COLNONE, COLSERVER, i18n (2467, "climm>")));
243 UtilIOSendTCP2 (fpeer, NULL);
244 QueueRetry (fpeer, QUEUE_PEER_FILE, fpeer->cont);
245 if (!UtilIOSelectIs (fpeer->sok, READFDS))
246 return;
247 }
248 if (!(pak = UtilIOReceiveTCP2 (fpeer)))
249 return;
250
251 if (prG->verbose & DEB_PACKTCP)
252 TCPPrint (pak, fpeer, FALSE);
253
254 switch (PacketRead1 (pak))
255 {
256 strc_t name, text;
257 UDWORD len, off, nr, speed;
258
259 case 0:
260 PacketRead4 (pak); /* EMPTY */
261 nr = PacketRead4 (pak); /* COUNT */
262 len = PacketRead4 (pak); /* BYTES */
263 speed= PacketRead4 (pak); /* SPEED */
264 name = PacketReadL2Str (pak, NULL); /* NICK */
265 PacketD (pak);
266
267 rl_log_for (cont->nick, COLCONTACT);
268 rl_printf (i18n (2161, "Receiving %ld files with total size %ld bytes at speed %lx from %s.\n"),
269 UD2UL (nr), UD2UL (len), UD2UL (speed), ConvFromCont (name, cont));
270
271 if (len != fpeer->oscar_file_len)
272 {
273 rl_printf ("FIXME: byte len different than in file request: requested %ld, sending %ld.\n",
274 UD2UL (fpeer->oscar_file_len), UD2UL (len));
275 fpeer->oscar_file_len = len;
276 }
277
278 pak = PeerPacketC (fpeer, 1);
279 PacketWrite4 (pak, 64);
280 PacketWriteLNTS (pak, cont->nick);
281 PeerPacketSend (fpeer, pak);
282 PacketD (pak);
283 return;
284
285 case 1:
286 speed = PacketRead4 (pak); /* SPEED */
287 name = PacketReadL2Str (pak, NULL); /* NICK */
288 PacketD (pak);
289
290 rl_log_for (cont->nick, COLCONTACT);
291 rl_printf (i18n (2170, "Sending file at speed %lx to %s.\n"), UD2UL (speed), s_wordquote (ConvFromCont (name, cont)));
292
293 fpeer->oscar_dc_seq = 1;
294 QueueRetry (fpeer, QUEUE_PEER_FILE, cont);
295 return;
296
297 case 2:
298 PacketRead1 (pak); /* EMPTY */
299 name = PacketReadL2Str (pak, NULL);
300 text = PacketReadL2Str (pak, NULL);
301 len = PacketRead4 (pak);
302 PacketRead4 (pak); /* EMPTY */
303 PacketRead4 (pak); /* SPEED */
304 off = 0;
305 PacketD (pak);
306
307 {
308 Connection *ffile = ServerChild (fpeer->serv, fpeer->cont, TYPE_FILE);
309 char buf[200], *p;
310 int pos = 0;
311 struct stat finfo;
312
313 assert (ffile);
314 pos = snprintf (buf, sizeof (buf), "%sfiles" _OS_PATHSEPSTR "%s" _OS_PATHSEPSTR,
315 PrefUserDir (prG), cont->screen);
316 snprintf (buf + pos, sizeof (buf) - pos, "%s", ConvFromCont (name, cont));
317 for (p = buf + pos; *p; p++)
318 if (*p == '/')
319 *p = '_';
320 finfo.st_size = 0;
321 if (!stat (buf, &finfo))
322 if ((UDWORD)finfo.st_size < len)
323 off = finfo.st_size;
324 fpeer->oscar_file = ffile;
325 ffile->sok = open (buf, O_CREAT | O_WRONLY | (off ? O_APPEND : O_TRUNC), 0660);
326 if (ffile->sok == -1)
327 {
328 int rc = errno;
329 if (rc == ENOENT)
330 {
331 mkdir (s_sprintf ("%sfiles", PrefUserDir (prG)), 0700);
332 mkdir (s_sprintf ("%sfiles" _OS_PATHSEPSTR "%s", PrefUserDir (prG), cont->screen), 0700);
333 ffile->sok = open (buf, O_CREAT | O_WRONLY | (off ? O_APPEND : O_TRUNC), 0660);
334 }
335 if (ffile->sok == -1)
336 {
337 rl_log_for (cont->nick, COLCONTACT);
338 rl_printf (i18n (2083, "Cannot open file %s: %s (%d).\n"),
339 buf, strerror (rc), rc);
340 ConnectionD (fpeer);
341 return;
342 }
343 }
344 ffile->connect = CONNECT_OK;
345 ffile->oscar_file_len = len;
346 ffile->oscar_file_done = off;
347
348 rl_log_for (cont->nick, COLCONTACT);
349 rl_printf (i18n (2162, "Receiving file %s (%s) with %ld bytes as %s.\n"),
350 name->txt, text->txt, UD2UL (len), buf);
351 }
352 pak = PeerPacketC (fpeer, 3);
353 PacketWrite4 (pak, off);
354 PacketWrite4 (pak, 0);
355 PacketWrite4 (pak, 64);
356 PacketWrite4 (pak, 1);
357 PeerPacketSend (fpeer, pak);
358 PacketD (pak);
359 return;
360
361 case 3:
362 off = PacketRead4 (pak);
363 PacketRead4 (pak); /* EMPTY */
364 PacketRead4 (pak); /* SPEED */
365 nr = PacketRead4 (pak); /* NR */
366 PacketD (pak);
367
368 rl_log_for (cont->nick, COLCONTACT);
369 rl_printf (i18n (2163, "Sending file %ld at offset %ld.\n"),
370 UD2UL (nr), UD2UL (off));
371
372 err = lseek (fpeer->oscar_file->sok, off, SEEK_SET);
373 if (err == -1)
374 {
375 err = errno;
376 rl_log_for (cont->nick, COLCONTACT);
377 rl_printf (i18n (2084, "Error while seeking to offset %ld: %s (%d).\n"),
378 UD2UL (off), strerror (err), err);
379 TCPClose (fpeer);
380 return;
381 }
382 fpeer->oscar_file->oscar_file_done = off;
383 fpeer->oscar_file->connect = CONNECT_OK;
384
385 QueueRetry (fpeer, QUEUE_PEER_FILE, cont);
386 return;
387
388 case 4:
389 rl_log_for (cont->nick, COLCONTACT);
390 rl_printf (i18n (2169, "File transfer aborted by peer (%d).\n"),
391 PacketRead1 (pak));
392 PacketD (pak);
393 PeerFileClose (fpeer);
394 return;
395
396 case 5:
397 rl_printf ("FIXME: Ignoring speed change to %d.\n",
398 PacketRead1 (pak));
399 PacketD (pak);
400 return;
401
402 case 6:
403 if (fpeer->oscar_file->oscar_file_done + pak->len - 1 > fpeer->oscar_file->oscar_file_len)
404 {
405 rl_log_for (cont->nick, COLCONTACT);
406 rl_printf (i18n (2165, "The peer sent more bytes (%ld) than the file length (%ld).\n"),
407 UD2UL (fpeer->oscar_file->oscar_file_done + pak->len - 1), UD2UL (fpeer->oscar_file->oscar_file_len));
408 PacketD (pak);
409 TCPClose (fpeer);
410 return;
411 }
412 if (pak->len <= 1)
413 {
414 PacketD (pak);
415 return;
416 }
417 len = write (fpeer->oscar_file->sok, pak->data + 1, pak->len - 1);
418 if (len + 1 != pak->len)
419 {
420 rl_log_for (cont->nick, COLCONTACT);
421 rl_printf (i18n (2575, "Error writing to file (%lu bytes written out of %u).\n"), UD2UL (len), pak->len - 1);
422 PacketD (pak);
423 TCPClose (fpeer);
424 return;
425 }
426 fpeer->oscar_file->oscar_file_done += len;
427 if (fpeer->oscar_file->oscar_file_len == fpeer->oscar_file->oscar_file_done)
428 {
429 ReadLinePromptReset ();
430 rl_log_for (cont->nick, COLCONTACT);
431 rl_print (i18n (2166, "Finished receiving file.\n"));
432 #if HAVE_FSYNC
433 fsync (fpeer->oscar_file->sok);
434 #endif
435 close (fpeer->oscar_file->sok);
436 fpeer->oscar_file->sok = -1;
437 fpeer->oscar_file->connect = CONNECT_OK;
438 }
439 else if (fpeer->oscar_file->oscar_file_len)
440 {
441 ReadLinePromptUpdate (s_sprintf ("[%s%ld %02d%%%s] %s%s",
442 COLINCOMING, UD2UL (fpeer->oscar_file->oscar_file_done), (int)((100.0 * fpeer->oscar_file->oscar_file_done) / fpeer->oscar_file->oscar_file_len),
443 COLNONE, COLSERVER, i18n (2467, "climm>")));
444 }
445 PacketD (pak);
446 return;
447 default:
448 rl_log_for (cont->nick, COLCONTACT);
449 rl_print (i18n (2167, "Error - unknown packet.\n"));
450 rl_print (s_dump (pak->data, pak->len));
451 PacketD (pak);
452 PeerFileClose (fpeer);
453 }
454 if ((prG->verbose & DEB_TCP) && err)
455 {
456 rl_printf ("%s %s: %d\n", s_now, i18n (2029, "Protocol error on peer-to-peer connection"), err);
457 PeerFileClose (fpeer);
458 }
459 }
460
PeerFileResend(Event * event)461 void PeerFileResend (Event *event)
462 {
463 Contact *cont;
464 Connection *fpeer = event->conn;
465 Packet *pak;
466 Event *event2;
467 int rc;
468 const char *opt_text;
469
470 if (!fpeer)
471 {
472 EventD (event);
473 return;
474 }
475
476 ASSERT_FILEDIRECT (fpeer);
477
478 cont = event->cont;
479 assert (cont);
480
481 if (!OptGetStr (event->opt, CO_FILENAME, &opt_text))
482 opt_text = "";
483
484 if (event->attempts >= MAX_RETRY_P2PFILE_ATTEMPTS || (!event->pak && !event->seq))
485 {
486 rl_log_for (cont->nick, COLCONTACT);
487 rl_printf (i18n (2168, "File transfer #%ld (%s) dropped after %ld attempts because of timeout.\n"),
488 UD2UL (event->seq), opt_text, UD2UL (event->attempts));
489 TCPClose (fpeer);
490 }
491 else if (!(fpeer->connect & CONNECT_MASK))
492 {
493 rl_log_for (cont->nick, COLCONTACT);
494 rl_printf (i18n (2072, "File transfer #%ld (%s) canceled because of closed connection.\n"),
495 UD2UL (event->seq), opt_text);
496 }
497 else if (~fpeer->connect & CONNECT_OK)
498 {
499 if (!event->seq)
500 event->attempts++;
501 event->due = time (NULL) + 20;
502 QueueEnqueue (event);
503 return;
504 }
505 else if (!event->seq)
506 {
507 fpeer->oscar_dc_seq = 0;
508 PeerPacketSend (fpeer, event->pak);
509 PacketD (event->pak);
510 event->pak = NULL;
511 }
512 else if (event->seq != fpeer->oscar_dc_seq)
513 {
514 event->due = time (NULL) + 10;
515 QueueEnqueue (event);
516 return;
517 }
518 else if (event->pak)
519 {
520 Connection *ffile;
521 struct stat finfo;
522
523 PeerPacketSend (fpeer, event->pak);
524 PacketD (event->pak);
525 event->pak = NULL;
526 QueueEnqueue (event);
527
528 ffile = ServerChild (fpeer->serv, fpeer->cont, TYPE_FILE);
529 fpeer->oscar_file = ffile;
530
531 if (stat (opt_text, &finfo))
532 {
533 rc = errno;
534 rl_printf (i18n (2071, "Couldn't stat file %s: %s (%d)\n"),
535 s_wordquote (opt_text), strerror (rc), rc);
536 }
537 ffile->oscar_file_len = finfo.st_size;
538
539 ffile->sok = open (opt_text, O_RDONLY);
540 if (ffile->sok == -1)
541 {
542 int rc = errno;
543 rl_log_for (cont->nick, COLCONTACT);
544 rl_printf (i18n (2083, "Cannot open file %s: %s (%d).\n"),
545 opt_text, strerror (rc), rc);
546 TCPClose (fpeer);
547 ConnectionD (ffile);
548 ConnectionD (fpeer);
549 return;
550 }
551 return;
552 }
553 else if (!fpeer->oscar_file || fpeer->connect & CONNECT_SELECT_W)
554 {
555 event->attempts++;
556 event->due = time (NULL) + 3;
557 QueueEnqueue (event);
558 return;
559 }
560 else
561 {
562 int len = 0;
563
564 pak = PeerPacketC (fpeer, 6);
565 len = read (fpeer->oscar_file->sok, pak->data + 1, 2048);
566 if (len == -1)
567 {
568 len = errno;
569 rl_log_for (cont->nick, COLCONTACT);
570 rl_printf (i18n (2086, "Error while reading file %s: %s (%d).\n"),
571 opt_text, strerror (len), len);
572 TCPClose (fpeer);
573 }
574 else
575 {
576 pak->len += len;
577 fpeer->oscar_file->oscar_file_done += len;
578 PeerPacketSend (fpeer, pak);
579 PacketD (pak);
580
581 if (len > 0)
582 {
583 if (fpeer->oscar_file->oscar_file_len)
584 ReadLinePromptUpdate (s_sprintf ("[%s%ld %02d%%%s] %s%s",
585 COLCONTACT, UD2UL (fpeer->oscar_file->oscar_file_done), (int)((100.0 * fpeer->oscar_file->oscar_file_done) / fpeer->oscar_file->oscar_file_len),
586 COLNONE, COLSERVER, i18n (2467, "climm>")));
587 else
588 ReadLinePromptUpdate (s_sprintf ("[%s%ld%s] %s%s",
589 COLCONTACT, UD2UL (fpeer->oscar_file->oscar_file_done),
590 COLNONE, COLSERVER, i18n (2467, "climm>")));
591 event->attempts = 0;
592 QueueEnqueue (event);
593 return;
594 }
595
596 ReadLinePromptReset ();
597 rl_log_for (cont->nick, COLCONTACT);
598 rl_printf (i18n (2087, "Finished sending file %s.\n"), opt_text);
599 ConnectionD (fpeer->oscar_file);
600 fpeer->oscar_dc_seq++;
601 event2 = QueueDequeue (fpeer, QUEUE_PEER_FILE, fpeer->oscar_dc_seq);
602 if (event2)
603 {
604 QueueEnqueue (event2);
605 QueueRetry (fpeer, QUEUE_PEER_FILE, fpeer->cont);
606 return;
607 }
608 else
609 {
610 rl_log_for (cont->nick, COLCONTACT);
611 rl_printf (i18n (2088, "Finished sending all %d files.\n"), fpeer->oscar_dc_seq - 1);
612 ConnectionD (fpeer);
613 }
614 }
615 }
616 EventD (event);
617 }
618
619 #endif /* ENABLE_PEER2PEER */
620