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