1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2015 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 #include <stddef.h>
22 #include "Fish.h"
23 #include "trio.h"
24 #include <unistd.h>
25 #include <errno.h>
26 #include <stdarg.h>
27 #include <assert.h>
28 #include <time.h>
29 #include "ascii_ctype.h"
30 #include "LsCache.h"
31 #include "misc.h"
32 #include "log.h"
33 #include "ArgV.h"
34 
35 #define super SSH_Access
36 
37 #define max_buf 0x10000
38 
GetBetterConnection(int level)39 void Fish::GetBetterConnection(int level)
40 {
41    for(FA *fo=FirstSameSite(); fo!=0; fo=NextSameSite(fo))
42    {
43       Fish *o=(Fish*)fo; // we are sure it is Fish.
44 
45       if(!o->recv_buf)
46 	 continue;
47 
48       if(o->state!=CONNECTED || o->mode!=CLOSED)
49       {
50 	 if(level<2)
51 	    continue;
52 	 if(!connection_takeover || (o->priority>=priority && !o->IsSuspended()))
53 	    continue;
54 	 o->Disconnect();
55 	 return;
56       }
57 
58       if(level==0 && xstrcmp(real_cwd,o->real_cwd))
59 	 continue;
60 
61       // borrow the connection
62       MoveConnectionHere(o);
63       return;
64    }
65 }
66 
Do()67 int Fish::Do()
68 {
69    int m=STALL;
70    int fd;
71 
72    // check if idle time exceeded
73    if(mode==CLOSED && send_buf && idle_timer.Stopped())
74    {
75       LogNote(1,_("Closing idle connection"));
76       Disconnect();
77       return m;
78    }
79 
80    if(Error())
81       return m;
82 
83    if(!hostname)
84       return m;
85 
86    if(send_buf && send_buf->Error())
87    {
88       Disconnect();
89       return MOVED;
90    }
91    if(state!=CONNECTING_1)
92       m|=HandleReplies();
93 
94    if(Error())
95       return m;
96 
97    if(send_buf)
98       timeout_timer.Reset(send_buf->EventTime());
99    if(recv_buf)
100       timeout_timer.Reset(recv_buf->EventTime());
101    if(pty_send_buf)
102       timeout_timer.Reset(pty_send_buf->EventTime());
103    if(pty_recv_buf)
104       timeout_timer.Reset(pty_recv_buf->EventTime());
105 
106    // check for timeout only if there should be connection activity.
107    if(state!=DISCONNECTED && (state!=CONNECTED || !RespQueueIsEmpty())
108    && mode!=CLOSED && CheckTimeout())
109       return MOVED;
110 
111    if((state==FILE_RECV || state==FILE_SEND)
112    && rate_limit==0)
113       rate_limit=new RateLimit(hostname);
114 
115    const char *charset;
116    switch(state)
117    {
118    case DISCONNECTED:
119    {
120       if(mode==CLOSED)
121 	 return m;
122       if(mode==CONNECT_VERIFY)
123 	 return m;
124 
125       // walk through Fish classes and try to find identical idle session
126       // first try "easy" cases of session take-over.
127       for(int i=0; i<3; i++)
128       {
129 	 if(i>=2 && (connection_limit==0 || connection_limit>CountConnections()))
130 	    break;
131 	 GetBetterConnection(i);
132 	 if(state!=DISCONNECTED)
133 	    return MOVED;
134       }
135 
136       if(!ReconnectAllowed())
137 	 return m;
138 
139       if(!NextTry())
140 	 return MOVED;
141 
142       const char *shell=Query("shell",hostname);
143       const char *init=xstring::cat("echo FISH:;",shell,NULL);
144       const char *prog=Query("connect-program",hostname);
145       if(!prog || !prog[0])
146 	 prog="ssh -a -x";
147       ArgV args;
148       if(user)
149       {
150 	 args.Add("-l");
151 	 args.Add(user);
152       }
153       if(portname)
154       {
155 	 args.Add("-p");
156 	 args.Add(portname);
157       }
158       args.Add(hostname);
159       args.Add(init);
160       xstring_ca cmd_q(args.CombineShellQuoted(0));
161       xstring& cmd_str=xstring::cat(prog," ",cmd_q.get(),NULL);
162       LogNote(9,"%s (%s)\n",_("Running connect program"),cmd_str.get());
163       ssh=new PtyShell(cmd_str);
164       ssh->UsePipes();
165       state=CONNECTING;
166       timeout_timer.Reset();
167       m=MOVED;
168    }
169    case CONNECTING:
170       fd=ssh->getfd();
171       if(fd==-1)
172       {
173 	 if(ssh->error())
174 	 {
175 	    SetError(FATAL,ssh->error_text);
176 	    return MOVED;
177 	 }
178 	 TimeoutS(1);
179 	 return m;
180       }
181       MakePtyBuffers();
182       set_real_cwd("~");
183       state=CONNECTING_1;
184       m=MOVED;
185 
186    case CONNECTING_1:
187       m|=HandleSSHMessage();
188       if(state!=CONNECTING_1)
189 	 return MOVED;
190       if(!received_greeting)
191 	 return m;
192 
193       charset=ResMgr::Query("fish:charset",hostname);
194       if(charset && *charset)
195       {
196 	 send_buf->SetTranslation(charset,false);
197 	 recv_buf->SetTranslation(charset,true);
198       }
199 
200       Send("#FISH\n"
201 	   "TZ=GMT;export TZ;LC_ALL=C;export LC_ALL;"
202 	   "exec 2>&1;echo;start_fish_server;"
203 	   "echo '### 200'\n");
204       PushExpect(EXPECT_FISH);
205       Send("#VER 0.0.2\n"
206 	   "echo '### 000'\n");
207       PushExpect(EXPECT_VER);
208       if(home_auto==0)
209       {
210 	 Send("#PWD\n"
211 	      "pwd; echo '### 200'\n");
212 	 PushExpect(EXPECT_PWD);
213       }
214       state=CONNECTED;
215       m=MOVED;
216 
217    case CONNECTED:
218       if(mode==CLOSED)
219 	 return m;
220       if(home.path==0 && !RespQueueIsEmpty())
221 	 break;
222       ExpandTildeInCWD();
223       if(mode!=CHANGE_DIR && xstrcmp(cwd,real_cwd))
224       {
225 	 if(xstrcmp(path_queue.LastString(),cwd))
226 	 {
227 	    Send("#CWD %s\n"
228 		 "cd %s; echo '### 000'\n",cwd.path.get(),shell_encode(cwd).get());
229 	    PushExpect(EXPECT_CWD);
230 	    PushDirectory(cwd);
231 	 }
232 	 if(!RespQueueIsEmpty())
233 	    break;
234       }
235       SendMethod();
236       if(mode==LONG_LIST || mode==LIST || mode==QUOTE_CMD)
237       {
238 	 state=FILE_RECV;
239 	 m=MOVED;
240 	 break;
241       }
242       state=WAITING;
243       m=MOVED;
244    case WAITING:
245       if(RespQueueSize()==1 && mode==RETRIEVE)
246       {
247 	 state=FILE_RECV;
248 	 m=MOVED;
249       }
250       if(RespQueueSize()==1 && mode==STORE)
251       {
252 	 state=FILE_SEND;
253 	 real_pos=0;
254 	 pos=0;
255 	 m=MOVED;
256       }
257       if(RespQueueSize()==0)
258       {
259 	 state=DONE;
260 	 m=MOVED;
261       }
262       break;
263    case FILE_RECV:
264       if(recv_buf->Size()>=rate_limit->BytesAllowedToGet())
265       {
266 	 recv_buf->Suspend();
267 	 Timeout(1000);
268       }
269       else if(recv_buf->Size()>=max_buf)
270       {
271 	 recv_buf->Suspend();
272 	 m=MOVED;
273       }
274       else if(recv_buf->IsSuspended())
275       {
276 	 recv_buf->Resume();
277 	 if(recv_buf->Size()>0 || (recv_buf->Size()==0 && recv_buf->Eof()))
278 	    m=MOVED;
279       }
280       break;
281    case FILE_SEND:
282    case DONE:
283       break;
284    }
285    return m;
286 }
287 
MoveConnectionHere(Fish * o)288 void Fish::MoveConnectionHere(Fish *o)
289 {
290    super::MoveConnectionHere(o);
291    rate_limit=o->rate_limit.borrow();
292    path_queue.MoveHere(o->path_queue);
293    RespQueue.move_here(o->RespQueue);
294    timeout_timer.Reset(o->timeout_timer);
295    set_real_cwd(o->real_cwd);
296    state=CONNECTED;
297    o->Disconnect();
298    if(!home)
299       set_home(home_auto);
300    ResumeInternal();
301 }
302 
DisconnectLL()303 void Fish::DisconnectLL()
304 {
305    super::DisconnectLL();
306    EmptyRespQueue();
307    EmptyPathQueue();
308    state=DISCONNECTED;
309    if(mode==STORE)
310       SetError(STORE_FAILED,0);
311    home_auto.set(FindHomeAuto());
312 }
313 
Init()314 void Fish::Init()
315 {
316    state=DISCONNECTED;
317    max_send=0;
318    eof=false;
319 }
320 
Fish()321 Fish::Fish() : SSH_Access("FISH:")
322 {
323    Init();
324    Reconfig(0);
325 }
326 
~Fish()327 Fish::~Fish()
328 {
329    Disconnect();
330 }
331 
Fish(const Fish * o)332 Fish::Fish(const Fish *o) : super(o)
333 {
334    Init();
335    Reconfig(0);
336 }
337 
Close()338 void Fish::Close()
339 {
340    switch(state)
341    {
342    case(DISCONNECTED):
343    case(CONNECTED):
344    case(DONE):
345       break;
346    case(WAITING):
347       if(mode==STORE || mode==RETRIEVE)
348 	 Disconnect();
349       break;
350    case(FILE_SEND):
351       if(!RespQueueIsEmpty())
352 	 Disconnect();
353       break;
354    case(FILE_RECV):
355    case(CONNECTING):
356    case(CONNECTING_1):
357       Disconnect();
358    }
359 //    if(!RespQueueIsEmpty())
360 //       Disconnect(); // play safe.
361    CloseExpectQueue();
362    state=(recv_buf?CONNECTED:DISCONNECTED);
363    eof=false;
364    encode_file=true;
365    super::Close();
366 }
367 
Send(const char * format,...)368 void Fish::Send(const char *format,...)
369 {
370    va_list va;
371    va_start(va,format);
372    xstring& str=xstring::vformat(format,va);
373    va_end(va);
374    LogSend(5,str);
375    send_buf->Put(str);
376 }
377 
SendArrayInfoRequests()378 void Fish::SendArrayInfoRequests()
379 {
380    for(int i=fileset_for_info->curr_index(); i<fileset_for_info->count(); i++)
381    {
382       FileInfo *fi=(*fileset_for_info)[i];
383       if(fi->need)
384       {
385 	 const char *e=shell_encode(fi->name);
386 	 Send("#INFO %s\n"
387 	      "ls -lLd %s; echo '### 200'\n",fi->name.get(),e);
388 	 PushExpect(EXPECT_INFO);
389       }
390    }
391 }
392 
SendMethod()393 void Fish::SendMethod()
394 {
395    const char *e=file?alloca_strdup(shell_encode(file)):0;
396    const char *e1=shell_encode(file1);
397    switch((open_mode)mode)
398    {
399    case CHANGE_DIR:
400       Send("#CWD %s\n"
401 	   "cd %s; echo '### 000'\n",e,e);
402       PushExpect(EXPECT_CWD);
403       PushDirectory(file);
404       break;
405    case LONG_LIST:
406       if(!encode_file)
407 	 e=file;
408       Send("#LIST %s\n"
409 	   "ls -la %s; echo '### 200'\n",e,e);
410       PushExpect(EXPECT_DIR);
411       real_pos=0;
412       break;
413    case LIST:
414       if(!encode_file)
415 	 e=file;
416       Send("#LIST %s\n"
417 	   "ls -a %s; echo '### 200'\n",e,e);
418       PushExpect(EXPECT_DIR);
419       real_pos=0;
420       break;
421    case RETRIEVE:
422       if(pos>0)
423       {
424 	 int bs=0x1000;
425 	 real_pos=pos-pos%bs;
426 	 // non-standard extension
427 	 Send("#RETRP %lld %s\n"
428 	      "ls -lLd %s; "
429 	      "echo '### 100'; "
430 	      "dd ibs=%d skip=%lld if=%s 2>/dev/null; "
431 	      "echo '### 200'\n",
432 	    (long long)real_pos,e,e,bs,(long long)real_pos/bs,e);
433       }
434       else
435       {
436 	 Send("#RETR %s\n"
437 	   "ls -lLd %s; "
438 	   "echo '### 100'; cat %s; echo '### 200'\n",e,e,e);
439 	 real_pos=0;
440       }
441       PushExpect(EXPECT_RETR_INFO);
442       PushExpect(EXPECT_RETR);
443       break;
444    case STORE:
445       if(entity_size<0)
446       {
447 	 SetError(NO_FILE,"Have to know file size before upload");
448 	 break;
449       }
450       if(entity_size>0)
451       {
452 	 Send("#STOR %lld %s\n"
453 	      "rest=%lld;file=%s;:>$file;echo '### 001';"
454 	      "if echo 1|head -c 1 -q ->/dev/null 2>&1;then "
455 		  "head -c $rest -q -|(cat>$file;cat>/dev/null);"
456 	      "else while [ $rest -gt 0 ];do "
457 		  "bs=4096;cnt=`expr $rest / $bs`;"
458 		  "[ $cnt -eq 0 ] && { cnt=1;bs=$rest; }; "
459 		  "n=`dd ibs=$bs count=$cnt 2>/dev/null|tee -a $file|wc -c`;"
460 		  "[ \"$n\" -le 0 ] && exit;"
461 		  "rest=`expr $rest - $n`; "
462 	      "done;fi;echo '### 200'\n",
463 	    (long long)entity_size,e,(long long)entity_size,e);
464 #if 0
465 	 // dd pays attension to read boundaries and reads wrong number
466 	 // of bytes when ibs>1. Have to use the inefficient ibs=1.
467 	 Send("#STOR %lld %s\n"
468 	      ">%s;echo '### 001';"
469 	      "dd ibs=1 count=%lld 2>/dev/null"
470 	      "|(cat>%s;cat>/dev/null);echo '### 200'\n",
471 	      (long long)entity_size,e,
472 	      e,
473 	      (long long)entity_size,
474 	      e);
475 #endif
476       }
477       else
478       {
479 	 Send("#STOR %lld %s\n"
480 	      ">%s;echo '### 001';echo '### 200'\n",
481 	      (long long)entity_size,e,e);
482       }
483       PushExpect(EXPECT_STOR_PRELIMINARY);
484       PushExpect(EXPECT_STOR);
485       real_pos=0;
486       pos=0;
487       break;
488    case ARRAY_INFO:
489       SendArrayInfoRequests();
490       break;
491    case REMOVE:
492       Send("#DELE %s\n"
493 	   "rm -f %s; echo '### 000'\n",e,e);
494       PushExpect(EXPECT_DEFAULT);
495       break;
496    case REMOVE_DIR:
497       Send("#RMD %s\n"
498 	   "rmdir %s; echo '### 000'\n",e,e);
499       PushExpect(EXPECT_DEFAULT);
500       break;
501    case MAKE_DIR:
502       Send("#MKD %s\n"
503 	   "mkdir %s; echo '### 000'\n",e,e);
504       PushExpect(EXPECT_DEFAULT);
505       break;
506    case RENAME:
507       Send("#RENAME %s %s\n"
508 	   "mv %s %s; echo '### 000'\n",e,e1,e,e1);
509       PushExpect(EXPECT_DEFAULT);
510       break;
511    case CHANGE_MODE:
512       Send("#CHMOD %04o %s\n"
513 	   "chmod %04o %s; echo '### 000'\n",chmod_mode,e,chmod_mode,e);
514       PushExpect(EXPECT_DEFAULT);
515       break;
516    case LINK:
517       Send("#LINK %s %s\n"
518 	   "ln %s %s; echo '### 000'\n",e,e1,e,e1);
519       PushExpect(EXPECT_DEFAULT);
520       break;
521    case SYMLINK:
522       Send("#SYMLINK %s %s\n"
523 	   "ln -s %s %s; echo '### 000'\n",e,e1,e,e1);
524       PushExpect(EXPECT_DEFAULT);
525       break;
526    case QUOTE_CMD:
527       // non-standard extension
528       Send("#EXEC %s\n"
529 	   "%s; echo '### 200'\n",e,file.get());
530       PushExpect(EXPECT_QUOTE);
531       real_pos=0;
532       break;
533    case MP_LIST:
534       SetError(NOT_SUPP);
535       break;
536    case CONNECT_VERIFY:
537    case CLOSED:
538       abort();
539    }
540 }
541 
ReplyLogPriority(int code)542 int Fish::ReplyLogPriority(int code)
543 {
544    if(code==-1)
545       return 3;
546    return 4;
547 }
548 
HandleReplies()549 int Fish::HandleReplies()
550 {
551    int m=STALL;
552    if(recv_buf==0)
553       return m;
554    if(state==FILE_RECV) {
555       const char *err=pty_recv_buf->Get();
556       if(err && err[0]) {
557 	 const char *eol=strchr(err,'\n');
558 	 if(eol) {
559 	    xstring &e=xstring::get_tmp(err,eol-err);
560 	    LogError(0,"%s",e.get());
561 	    SetError(NO_FILE,e);
562 	    if(pty_recv_buf)
563 	       pty_recv_buf->Skip(eol-err+1);
564 	    return MOVED;
565 	 }
566       }
567       if(pty_recv_buf->Eof()) {
568 	 Disconnect();
569 	 return MOVED;
570       }
571       if(entity_size!=NO_SIZE && real_pos<entity_size)
572 	 return m;
573       if(entity_size==NO_SIZE)
574 	 return m;
575    }
576    recv_buf->Put(pty_recv_buf->Get(),pty_recv_buf->Size()); // join the messages.
577    pty_recv_buf->Skip(pty_recv_buf->Size());
578    if(recv_buf->Size()<5)
579    {
580    hup:
581       if(recv_buf->Error())
582       {
583 	 Disconnect();
584 	 return MOVED;
585       }
586       if(recv_buf->Eof())
587       {
588 	 LogError(0,_("Peer closed connection"));
589 	 // Solaris' shell exists when is given with wrong directory
590 	 if(!RespQueueIsEmpty() && RespQueue[0]==EXPECT_CWD && message)
591 	    SetError(NO_FILE,message);
592 	 Disconnect();
593 	 m=MOVED;
594       }
595       return m;
596    }
597 
598    const char *b;
599    int s;
600    recv_buf->Get(&b,&s);
601    const char *eol=(const char*)memchr(b,'\n',s);
602    if(!eol)
603    {
604       if(recv_buf->Eof() || recv_buf->Error())
605 	 goto hup;
606       return m;
607    }
608 
609    m=MOVED;
610    s=eol-b+1;
611    line.nset(b,s-1);
612    recv_buf->Skip(s);
613 
614    int code=-1;
615    if(s>7 && !memcmp(line,"### ",4)) {
616       if(sscanf(line+4,"%3d",&code)!=1)
617 	 code=-1;
618    }
619 
620    LogRecv(ReplyLogPriority(code),line);
621    if(code==-1)
622    {
623       if(message==0)
624 	 message.set(line);
625       else {
626 	 message.append('\n');
627 	 message.append(line);
628       }
629       return m;
630    }
631 
632    if(RespQueueIsEmpty())
633    {
634       LogError(3,_("extra server response"));
635       message.set(0);
636       return m;
637    }
638    expect_t e=RespQueue.next();
639 
640    bool keep_message=false;
641    switch(e)
642    {
643    case EXPECT_FISH:
644    case EXPECT_VER:
645       /* nothing yet */
646       break;;
647    case EXPECT_PWD:
648       if(!message)
649 	 break;
650       home_auto.set(message);
651       LogNote(9,"home set to %s\n",home_auto.get());
652       PropagateHomeAuto();
653       if(!home)
654 	 set_home(home_auto);
655       cache->SetDirectory(this, home, true);
656       break;
657    case EXPECT_CWD: {
658       xstring p;
659       PopDirectory(&p);
660       if(message==0)
661       {
662 	 set_real_cwd(p);
663 	 if(mode==CHANGE_DIR && RespQueueIsEmpty())
664 	 {
665 	    cwd.Set(p);
666 	    eof=true;
667 	 }
668 	 cache->SetDirectory(this,p,true);
669       }
670       else
671 	 SetError(NO_FILE,message);
672       break;
673    }
674    case EXPECT_RETR_INFO:
675       if(message && is_ascii_digit(message[0]) && !strchr(message,':'))
676       {
677 	 long long size_ll;
678 	 if(1==sscanf(message,"%lld",&size_ll))
679 	 {
680 	    entity_size=size_ll;
681 	    if(opt_size)
682 	       *opt_size=entity_size;
683 	 }
684       }
685       else if(message && message[0]!='#')
686       {
687 	 FileInfo *fi=FileInfo::parse_ls_line(message,"GMT");
688 	 if(!fi || !strncmp(message,"ls: ",4))
689 	 {
690 	    SetError(NO_FILE,message);
691 	    break;
692 	 }
693 	 if(fi->defined&fi->SIZE)
694 	 {
695 	    entity_size=fi->size;
696 	    if(opt_size)
697 	       *opt_size=entity_size;
698 	 }
699 	 if(fi->defined&fi->DATE)
700 	 {
701 	    entity_date=fi->date;
702 	    if(opt_date)
703 	       *opt_date=entity_date;
704 	 }
705       }
706       state=FILE_RECV;
707       break;
708    case EXPECT_INFO:
709    {
710       Ref<FileInfo> new_info(FileInfo::parse_ls_line(message,"GMT"));
711       FileInfo *fi=fileset_for_info->curr();
712       while(!fi->need)
713 	 fi=fileset_for_info->next();
714       fi->Merge(*new_info);
715       fi->need=0;
716       break;
717    }
718    case EXPECT_RETR:
719    case EXPECT_DIR:
720    case EXPECT_QUOTE:
721       eof=true;
722       state=DONE;
723       break;
724    case EXPECT_DEFAULT:
725       if(message)
726 	 SetError(NO_FILE,message);
727       break;
728    case EXPECT_STOR_PRELIMINARY:
729       if(message)
730       {
731 	 Disconnect();
732 	 SetError(NO_FILE,message);
733       }
734       break;
735    case EXPECT_STOR:
736       if(message)
737       {
738 	 Disconnect();
739 	 SetError(NO_FILE,message);
740       }
741       break;
742    case EXPECT_IGNORE:
743       break;
744    }
745 
746    if(!keep_message)
747       message.set(0);
748 
749    return m;
750 }
PushExpect(expect_t e)751 void Fish::PushExpect(expect_t e)
752 {
753    RespQueue.push(e);
754 }
CloseExpectQueue()755 void Fish::CloseExpectQueue()
756 {
757    int count=RespQueue.count();
758    for(int i=0; i<count; i++)
759    {
760       switch(RespQueue[i])
761       {
762       case EXPECT_IGNORE:
763       case EXPECT_FISH:
764       case EXPECT_VER:
765       case EXPECT_PWD:
766       case EXPECT_CWD:
767 	 break;
768       case EXPECT_INFO:
769       case EXPECT_DIR:
770       case EXPECT_DEFAULT:
771 	 RespQueue[i]=EXPECT_IGNORE;
772 	 break;
773       case EXPECT_QUOTE:
774       case EXPECT_RETR_INFO:
775       case EXPECT_RETR:
776       case EXPECT_STOR_PRELIMINARY:
777       case EXPECT_STOR:
778 	 Disconnect();
779 	 break;
780       }
781    }
782 }
783 
memstr(const char * mem,size_t len,const char * str)784 const char *memstr(const char *mem,size_t len,const char *str)
785 {
786    size_t str_len=strlen(str);
787    while(len>=str_len)
788    {
789       if(!memcmp(mem,str,str_len))
790 	 return mem;
791       mem++;
792       len--;
793    }
794    return 0;
795 }
796 
Read(Buffer * buf,int size)797 int Fish::Read(Buffer *buf,int size)
798 {
799    if(Error())
800       return error_code;
801    if(mode==CLOSED)
802       return 0;
803    if(state==DONE)
804       return 0;	  // eof
805    if(state==FILE_RECV && real_pos>=0)
806    {
807       const char *buf1;
808       int size1;
809    get_again:
810       if(recv_buf->Size()==0 && recv_buf->Error())
811       {
812 	 Disconnect();
813 	 return DO_AGAIN;
814       }
815       recv_buf->Get(&buf1,&size1);
816       if(buf1==0) // eof
817       {
818 	 Disconnect();
819 	 return DO_AGAIN;
820       }
821       if(size1==0)
822 	 return DO_AGAIN;
823       if(entity_size!=NO_SIZE && real_pos<entity_size)
824       {
825 	 if(real_pos+size1>entity_size)
826 	    size1=entity_size-real_pos;
827       }
828       else
829       {
830 	 const char *end=memstr(buf1,size1,"### ");
831 	 if(end)
832 	 {
833 	    size1=end-buf1;
834 	    if(size1==0)
835 	    {
836 	       state=WAITING;
837 	       if(HandleReplies()==MOVED)
838 		  current->Timeout(0);
839 	       return DO_AGAIN;
840 	    }
841 	 }
842 	 else
843 	 {
844 	    for(int j=0; j<3; j++)
845 	       if(size1>0 && buf1[size1-1]=='#')
846 		  size1--;
847 	    if(size1==0 && recv_buf->Eof())
848 	    {
849 	       Disconnect();
850 	       return DO_AGAIN;
851 	    }
852 	 }
853       }
854 
855       int bytes_allowed=rate_limit->BytesAllowedToGet();
856       if(size1>bytes_allowed)
857 	 size1=bytes_allowed;
858       if(size1==0)
859 	 return DO_AGAIN;
860       if(norest_manual && real_pos==0 && pos>0)
861 	 return DO_AGAIN;
862       if(real_pos<pos)
863       {
864 	 off_t to_skip=pos-real_pos;
865 	 if(to_skip>size1)
866 	    to_skip=size1;
867 	 recv_buf->Skip(to_skip);
868 	 real_pos+=to_skip;
869 	 goto get_again;
870       }
871       if(size>size1)
872 	 size=size1;
873       size=buf->MoveDataHere(recv_buf,size);
874       if(size<=0)
875 	 return DO_AGAIN;
876       pos+=size;
877       real_pos+=size;
878       rate_limit->BytesGot(size);
879       TrySuccess();
880       return size;
881    }
882    return DO_AGAIN;
883 }
884 
Write(const void * buf,int size)885 int Fish::Write(const void *buf,int size)
886 {
887    if(mode!=STORE)
888       return(0);
889 
890    Resume();
891    Do();
892    if(Error())
893       return(error_code);
894 
895    if(state!=FILE_SEND || rate_limit==0)
896       return DO_AGAIN;
897 
898    {
899       int allowed=rate_limit->BytesAllowedToPut();
900       if(allowed==0)
901 	 return DO_AGAIN;
902       if(size+send_buf->Size()>allowed)
903 	 size=allowed-send_buf->Size();
904    }
905    if(size+send_buf->Size()>0x4000)
906       size=0x4000-send_buf->Size();
907    if(pos+size>entity_size)
908    {
909       size=entity_size-pos;
910       // tried to write more than originally requested. Make it retry with Open:
911       if(size==0)
912 	 return STORE_FAILED;
913    }
914    if(size<=0)
915       return 0;
916    send_buf->Put((char*)buf,size);
917    TrySuccess();
918    rate_limit->BytesPut(size);
919    pos+=size;
920    real_pos+=size;
921    return(size);
922 }
Buffered()923 int Fish::Buffered()
924 {
925    if(send_buf==0)
926       return 0;
927    return send_buf->Size();
928 }
StoreStatus()929 int Fish::StoreStatus()
930 {
931    if(Error())
932       return error_code;
933    if(state!=FILE_SEND)
934       return IN_PROGRESS;
935    if(real_pos!=entity_size)
936    {
937       Disconnect();
938       return IN_PROGRESS;
939    }
940    if(RespQueueSize()==0)
941       return OK;
942    return IN_PROGRESS;
943 }
944 
Done()945 int Fish::Done()
946 {
947    if(mode==CLOSED)
948       return OK;
949    if(Error())
950       return error_code;
951    if(eof || state==DONE)
952       return OK;
953    if(mode==CONNECT_VERIFY)
954       return OK;
955    return IN_PROGRESS;
956 }
957 
SuspendInternal()958 void Fish::SuspendInternal()
959 {
960    super::SuspendInternal();
961    if(recv_buf)
962       recv_buf->SuspendSlave();
963    if(send_buf)
964       send_buf->SuspendSlave();
965 }
ResumeInternal()966 void Fish::ResumeInternal()
967 {
968    if(recv_buf)
969       recv_buf->ResumeSlave();
970    if(send_buf)
971       send_buf->ResumeSlave();
972    super::ResumeInternal();
973 }
974 
CurrentStatus()975 const char *Fish::CurrentStatus()
976 {
977    switch(state)
978    {
979    case DISCONNECTED:
980       if(!ReconnectAllowed())
981 	 return DelayingMessage();
982       return _("Not connected");
983    case CONNECTING:
984       if(ssh && ssh->status)
985 	 return ssh->status;
986    case CONNECTING_1:
987       return _("Connecting...");
988    case CONNECTED:
989       return _("Connected");
990    case WAITING:
991       return _("Waiting for response...");
992    case FILE_RECV:
993       return _("Receiving data");
994    case FILE_SEND:
995       return _("Sending data");
996    case DONE:
997       return _("Done");
998    }
999    return "";
1000 }
1001 
SameSiteAs(const FileAccess * fa) const1002 bool Fish::SameSiteAs(const FileAccess *fa) const
1003 {
1004    if(!SameProtoAs(fa))
1005       return false;
1006    Fish *o=(Fish*)fa;
1007    return(!xstrcasecmp(hostname,o->hostname) && !xstrcmp(portname,o->portname)
1008    && !xstrcmp(user,o->user) && !xstrcmp(pass,o->pass));
1009 }
1010 
SameLocationAs(const FileAccess * fa) const1011 bool Fish::SameLocationAs(const FileAccess *fa)	const
1012 {
1013    if(!SameSiteAs(fa))
1014       return false;
1015    Fish *o=(Fish*)fa;
1016    if(xstrcmp(cwd,o->cwd))
1017       return false;
1018    return true;
1019 }
1020 
Reconfig(const char * name)1021 void Fish::Reconfig(const char *name)
1022 {
1023    super::Reconfig(name);
1024    if(!xstrcmp(name,"fish:charset") && recv_buf && send_buf)
1025    {
1026       if(!IsSuspended())
1027 	 cache->TreeChanged(this,"/");
1028       const char *charset=ResMgr::Query("fish:charset",hostname);
1029       if(charset && *charset)
1030       {
1031 	 send_buf->SetTranslation(charset,false);
1032 	 recv_buf->SetTranslation(charset,true);
1033       }
1034       else
1035       {
1036 	 send_buf->SetTranslator(0);
1037 	 recv_buf->SetTranslator(0);
1038       }
1039    }
1040 }
1041 
ClassInit()1042 void Fish::ClassInit()
1043 {
1044    // register the class
1045    Register("fish",Fish::New);
1046 }
New()1047 FileAccess *Fish::New() { return new Fish(); }
1048 
MakeDirList(ArgV * args)1049 DirList *Fish::MakeDirList(ArgV *args)
1050 {
1051    return new FishDirList(this,args);
1052 }
1053 #include "FileGlob.h"
MakeGlob(const char * pattern)1054 Glob *Fish::MakeGlob(const char *pattern)
1055 {
1056    return new GenericGlob(this,pattern);
1057 }
MakeListInfo(const char * p)1058 ListInfo *Fish::MakeListInfo(const char *p)
1059 {
1060    return new FishListInfo(this,p);
1061 }
1062 
1063 #undef super
1064 #define super DirList
1065 #include "ArgV.h"
1066 
Do()1067 int FishDirList::Do()
1068 {
1069    if(done)
1070       return STALL;
1071 
1072    if(buf->Eof())
1073    {
1074       done=true;
1075       return MOVED;
1076    }
1077 
1078    if(!ubuf)
1079    {
1080       const char *cache_buffer=0;
1081       int cache_buffer_size=0;
1082       int err;
1083       if(use_cache && FileAccess::cache->Find(session,pattern,FA::LONG_LIST,&err,
1084 				    &cache_buffer,&cache_buffer_size))
1085       {
1086 	 if(err)
1087 	 {
1088 	    SetErrorCached(cache_buffer);
1089 	    return MOVED;
1090 	 }
1091 	 ubuf=new IOBuffer(IOBuffer::GET);
1092 	 ubuf->Put(cache_buffer,cache_buffer_size);
1093 	 ubuf->PutEOF();
1094       }
1095       else
1096       {
1097 	 session->Open(pattern,FA::LONG_LIST);
1098 	 session.Cast<Fish>()->DontEncodeFile();
1099 	 ubuf=new IOBufferFileAccess(session);
1100 	 if(FileAccess::cache->IsEnabled(session->GetHostName()))
1101 	    ubuf->Save(FileAccess::cache->SizeLimit());
1102       }
1103    }
1104 
1105    const char *b;
1106    int len;
1107    ubuf->Get(&b,&len);
1108    if(b==0) // eof
1109    {
1110       buf->PutEOF();
1111       FileAccess::cache->Add(session,pattern,FA::LONG_LIST,FA::OK,ubuf);
1112       return MOVED;
1113    }
1114 
1115    int m=STALL;
1116 
1117    if(len>0)
1118    {
1119       buf->Put(b,len);
1120       ubuf->Skip(len);
1121       m=MOVED;
1122    }
1123 
1124    if(ubuf->Error())
1125    {
1126       SetError(ubuf->ErrorText());
1127       m=MOVED;
1128    }
1129    return m;
1130 }
1131 
Status()1132 const char *FishDirList::Status()
1133 {
1134    if(ubuf && !ubuf->Eof() && session->IsOpen())
1135    {
1136       return xstring::format(_("Getting file list (%lld) [%s]"),
1137 		     (long long)session->GetPos(),session->CurrentStatus());
1138    }
1139    return "";
1140 }
1141 
SuspendInternal()1142 void FishDirList::SuspendInternal()
1143 {
1144    super::SuspendInternal();
1145    if(ubuf)
1146       ubuf->SuspendSlave();
1147 }
ResumeInternal()1148 void FishDirList::ResumeInternal()
1149 {
1150    if(ubuf)
1151       ubuf->ResumeSlave();
1152    super::ResumeInternal();
1153 }
1154 
ls_to_FileSet(const char * b,int len)1155 static FileSet *ls_to_FileSet(const char *b,int len)
1156 {
1157    FileSet *set=new FileSet;
1158    while(len>0) {
1159       // find one line
1160       const char *line=b;
1161       int ll=len;
1162       const char *eol=find_char(b,len,'\n');
1163       if(eol) {
1164 	 ll=eol-b;
1165 	 len-=ll+1;
1166 	 b+=ll+1;
1167       } else {
1168 	 len=0;
1169       }
1170       if(ll && line[ll-1]=='\r')
1171 	 --ll;
1172       if(ll==0)
1173 	 continue;
1174 
1175       FileInfo *f=FileInfo::parse_ls_line(line,ll,"GMT");
1176 
1177       if(!f)
1178 	 continue;
1179 
1180       set->Add(f);
1181    }
1182    return set;
1183 }
1184 
ParseLongList(const char * b,int len,int * err) const1185 FileSet *Fish::ParseLongList(const char *b,int len,int *err) const
1186 {
1187    if(err)
1188       *err=0;
1189    return ls_to_FileSet(b,len);
1190 }
1191 
1192 // FishListInfo implementation
Parse(const char * b,int len)1193 FileSet *FishListInfo::Parse(const char *b,int len)
1194 {
1195    return ls_to_FileSet(b,len);
1196 }
1197 
1198 
1199 #include "modconfig.h"
1200 #ifdef MODULE_PROTO_FISH
module_init()1201 void module_init()
1202 {
1203    Fish::ClassInit();
1204 }
1205 #endif
1206