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