/* * lftp - file transfer program * * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "ftpclass.h" #include "xstring.h" #include "url.h" #include "FtpListInfo.h" #include "FileGlob.h" #include "FtpDirList.h" #include "log.h" #include "FileCopyFtp.h" #include "LsCache.h" #include "buffer_ssl.h" #include "buffer_zlib.h" #include "ascii_ctype.h" #include "misc.h" #include "strftime.h" #define TELNET_IAC '\377' //255 /* interpret as command: */ #define TELNET_IP '\364' //244 /* interrupt process--permanently */ #define TELNET_DM '\362' //242 /* for telfunc calls */ #define TELNET_WILL '\373' //251 #define TELNET_WONT '\374' //252 #define TELNET_DO '\375' //253 #define TELNET_DONT '\376' //254 #include #include #ifdef TM_IN_SYS_TIME # include #endif #ifdef HAVE_FCNTL_H # include #endif CDECL_BEGIN #include "regex.h" CDECL_END #if USE_SSL # include "lftp_ssl.h" #else # define control_ssl 0 const bool Ftp::ftps=false; #endif #define FTP_DEFAULT_PORT "21" #define FTPS_DEFAULT_PORT "990" #define FTP_DATA_PORT 20 #define FTPS_DATA_PORT 989 #define HTTP_DEFAULT_PROXY_PORT "3128" #define super NetAccess #define is5XX(code) ((code)>=500 && (code)<600) #define is4XX(code) ((code)>=400 && (code)<500) #define is3XX(code) ((code)>=300 && (code)<400) #define is2XX(code) ((code)>=200 && (code)<300) #define is1XX(code) ((code)>=100 && (code)<200) #define cmd_unsupported(code) ((code)==500 || (code)==502) #define site_cmd_unsupported(code) (cmd_unsupported((code)) || (code)==501) #ifndef EINPROGRESS #define EINPROGRESS -1 #endif FileAccess *Ftp::New() { return new Ftp(); } void Ftp::ClassInit() { // register the class Register("ftp",Ftp::New); FileCopy::fxp_create=FileCopyFtp::New; #if USE_SSL Register("ftps",FtpS::New); #endif } #if INET6 struct eprt_proto_match { int proto; int eprt_proto; }; static const eprt_proto_match eprt_proto[]= { { AF_INET, 1 }, { AF_INET6, 2 }, { -1, -1 } }; const char *Ftp::encode_eprt(const sockaddr_u *a) { int proto; if(a->sa.sa_family==AF_INET) proto=1; else if(a->sa.sa_family==AF_INET6) proto=2; else return 0; return xstring::format("|%d|%s|%d|",proto,a->address(),a->port()); } #endif bool Ftp::Connection::data_address_ok(const sockaddr_u *dp,bool verify_address,bool verify_port) { sockaddr_u d; sockaddr_u c; socklen_t len; len=sizeof(d); if(dp) d=*dp; else if(getpeername(data_sock,&d.sa,&len)==-1) { LogError(0,"getpeername(data_sock): %s\n",strerror(errno)); return !verify_address && !verify_port; } len=sizeof(c); if(getpeername(control_sock,&c.sa,&len)==-1) { LogError(0,"getpeername(control_sock): %s\n",strerror(errno)); return !verify_address; } #if INET6 if(d.sa.sa_family==AF_INET && c.sa.sa_family==AF_INET6 && IN6_IS_ADDR_V4MAPPED(&c.in6.sin6_addr)) { if(memcmp(&d.in.sin_addr,&c.in6.sin6_addr.s6_addr[12],4)) goto address_mismatch; if(d.in.sin_port!=htons(FTP_DATA_PORT) && d.in.sin_port!=htons(FTPS_DATA_PORT)) goto wrong_port; } #endif if(d.sa.sa_family!=c.sa.sa_family) return false; if(d.sa.sa_family==AF_INET) { if(memcmp(&d.in.sin_addr,&c.in.sin_addr,sizeof(d.in.sin_addr))) goto address_mismatch; if(d.in.sin_port!=htons(FTP_DATA_PORT) && d.in.sin_port!=htons(FTPS_DATA_PORT)) goto wrong_port; return true; } #if INET6 # ifndef IN6_ARE_ADDR_EQUAL # define IN6_ARE_ADDR_EQUAL(a,b) (!memcmp((a),(b),16)) # endif if(d.sa.sa_family==AF_INET6) { if(!IN6_ARE_ADDR_EQUAL(&d.in6.sin6_addr,&c.in6.sin6_addr)) goto address_mismatch; if(d.in6.sin6_port!=htons(FTP_DATA_PORT) && d.in6.sin6_port!=htons(FTPS_DATA_PORT)) goto wrong_port; return true; } #endif return true; wrong_port: if(!verify_port) return true; LogError(0,_("Data connection peer has wrong port number")); return false; address_mismatch: if(!verify_address) return true; LogError(0,_("Data connection peer has mismatching address")); return false; } /* Procedures for checking for a special answers */ /* General rule: check for valid replies first, errors second. */ void Ftp::RestCheck(int act) { if(is2XX(act) || is3XX(act)) { real_pos=conn->rest_pos; // REST successful conn->last_rest=conn->rest_pos; return; } real_pos=0; if(pos==0) return; if(is5XX(act)) { if(cmd_unsupported(act)) conn->rest_supported=false; LogNote(2,_("Switching to NOREST mode")); flags|=NOREST_MODE; if(mode==STORE) pos=0; if(copy_mode!=COPY_NONE) copy_failed=true; return; } Disconnect(line); } void Ftp::NoFileCheck(int act) { if(is2XX(act)) return; if(cmd_unsupported(act)) { SetError(FATAL,all_lines); return; } if(real_pos>0 && !GetFlag(IO_FLAG) && copy_mode==COPY_NONE && ((is4XX(act) && strstr(line,"Append/Restart not permitted")) || (is5XX(act) && !Transient5XX(act)))) { DataClose(); LogNote(2,_("Switching to NOREST mode")); flags|=NOREST_MODE; real_pos=0; if(mode==STORE) pos=0; state=EOF_STATE; // retry return; } if(is5XX(act) && !Transient5XX(act)) { SetError(NO_FILE,all_lines); return; } if(copy_mode!=COPY_NONE) { copy_failed=true; return; } DataClose(); state=EOF_STATE; eof=false; if(mode==STORE && GetFlag(IO_FLAG)) SetError(STORE_FAILED,0); else if(NextTry()) retry_timer.Set(2); // retry after 2 seconds } /* 5xx that aren't errors at all */ bool Ftp::NonError5XX(int act) const { return (mode==LIST && act==550 && (!file || !file[0])) // ...and proftpd workaround. || (mode==LIST && act==450 && strstr(line,"No files found")); } bool Ftp::ServerSaid(const char *s) const { return strstr(line,s) && (!file || !strstr(file,s)); } /* 5xx that are really transient like 4xx */ bool Ftp::Transient5XX(int act) const { if(!is5XX(act)) return false; if(act==530 && expect->FirstIs(Expect::PASS) && Retry530()) return true; // retry on these errors (ftp server ought to send 4xx code instead) if(ServerSaid("Broken pipe") || ServerSaid("Too many") || ServerSaid("timed out") || ServerSaid("closed by the remote host")) return true; // if there were some data received, assume it is temporary error. if(mode!=STORE && GetFlag(IO_FLAG)) return true; return false; } #if USE_SSL const char *Ftp::get_protect_res() { if(mode==LIST || mode==MP_LIST || (mode==LONG_LIST && !use_stat_for_list)) return "ftp:ssl-protect-list"; else if(mode==RETRIEVE || mode==STORE) return "ftp:ssl-protect-data"; return 0; } #endif // 226 Transfer complete. void Ftp::TransferCheck(int act) { if(act==225 || act==226) // data connection is still open or ABOR worked. { copy_done=true; conn->CloseAbortedDataConnection(); if(!conn->received_150 && state!=DATA_OPEN_STATE) goto simulate_eof; } if(act==211) { // permature STAT? conn->stat_timer.ResetDelayed(3); return; } if(act==213) // this must be a STAT reply. { conn->stat_timer.Reset(); long long p; // first try Serv-U format: // Status for user UUU from X.X.X.X // Stored 1 files, 0 Kbytes // Retrieved 0 files, 0 Kbytes // Receiving file XXX (YYY bytes) const char *r=strstr(all_lines,"Receiving file"); if(r) { r=strrchr(r,'('); char c=0; if(r && sscanf(r,"(%lld bytes%c",&p,&c)==2 && c==')') goto found_offset; } // wu-ftpd format: // Status: XXX of YYY bytes transferred // or // Status: XXX bytes transferred // // find the first number. for(const char *b=line+4; ; b++) { if(*b==0) return; if(!is_ascii_digit(*b)) continue; if(sscanf(b,"%lld",&p)==1) break; } found_offset: if(copy_mode==COPY_DEST) real_pos=pos=p; return; } if(copy_mode!=COPY_NONE && is4XX(act)) { copy_passive=!copy_passive; copy_failed=true; return; } if(NonError5XX(act)) goto simulate_eof; if(act==426 && copy_mode==COPY_NONE) { if(conn->data_sock==-1 && strstr(line,"Broken pipe")) return; } if(act==426 && mode==STORE) { DataClose(); state=EOF_STATE; SetError(FATAL,all_lines); } if(is2XX(act) && conn->data_sock==-1) eof=true; #if USE_SSL if(conn->auth_supported && act==522 && conn->prot=='C') { const char *res=get_protect_res(); if(res) { // try again with PROT P DataClose(); ResMgr::Set(res,hostname,"yes"); state=EOF_STATE; return; } } #endif NoFileCheck(act); return; simulate_eof: DataClose(); state=EOF_STATE; eof=true; return; } bool Ftp::Retry530() const { const char *rexp=Query("retry-530",hostname); if(re_match(all_lines,rexp,REG_ICASE)) { LogNote(9,_("Server reply matched ftp:retry-530, retrying")); return true; } if(!user) { rexp=Query("retry-530-anonymous",hostname); if(re_match(all_lines,rexp,REG_ICASE)) { LogNote(9,_("Server reply matched ftp:retry-530-anonymous, retrying")); return true; } } return false; } void Ftp::LoginCheck(int act) { if(conn->ignore_pass) return; if(act==530 && Retry530()) // overloaded server? goto retry; if(is5XX(act)) { SetError(LOGIN_FAILED,all_lines); return; } if(!is2XX(act) && !is3XX(act)) { retry: Disconnect(line); NextPeer(); if(peer_curr==0) reconnect_timer.Reset(); // count the reconnect-interval from this moment last_connection_failed=true; } if(is3XX(act) && !expect->Has(Expect::ACCT_PROXY)) { if(!QueryStringWithUserAtHost("acct")) { Disconnect(line); SetError(LOGIN_FAILED,_("Account is required, set ftp:acct variable")); } } } void Ftp::NoPassReqCheck(int act) // for USER command { if(is2XX(act)) // in some cases, ftpd does not ask for pass. { conn->ignore_pass=true; return; } if(act==331 && allow_skey && user && pass) { skey_pass.set(make_skey_reply()); if(force_skey && skey_pass==0) { SetError(LOGIN_FAILED,_("ftp:skey-force is set and server does not support OPIE nor S/KEY")); return; } } if(act==331 && allow_netkey && user && pass) { netkey_pass.set(make_netkey_reply()); } if(is3XX(act)) return; if(act==530 && Retry530()) // overloaded server? goto retry; if(is5XX(act)) { // proxy interprets USER as user@host, so we check for host name validity if(proxy && (strstr(line,"host") || strstr(line,"resolve"))) { LogNote(9,_("assuming failed host name lookup")); SetError(LOOKUP_ERROR,all_lines); return; } SetError(LOGIN_FAILED,all_lines); return; } retry: Disconnect(line); reconnect_timer.Reset(); // count the reconnect-interval from this moment last_connection_failed=true; } // login to proxy. void Ftp::proxy_LoginCheck(int act) { if(is2XX(act)) return; if(is5XX(act)) { SetError(LOGIN_FAILED,all_lines); return; } Disconnect(line); reconnect_timer.Reset(); // count the reconnect-interval from this moment } void Ftp::proxy_NoPassReqCheck(int act) { if(is3XX(act) || is2XX(act)) return; if(is5XX(act)) { SetError(LOGIN_FAILED,all_lines); return; } Disconnect(line); reconnect_timer.Reset(); // count the reconnect-interval from this moment } static void normalize_path_vms(char *path) { for(char *s=path; *s; s++) *s=to_ascii_lower(*s); char *colon=strchr(path,':'); if(colon) { memmove(path+1,path,strlen(path)+1); path[0]='/'; path=colon+1; if(path[1]=='[') memmove(path,path+1,strlen(path)); } else { path=strchr(path,'['); if(!*path) return; } *path++='/'; while(*path && *path!=']') { if(*path=='.') *path='/'; path++; } if(!*path) return; if(path[1]) *path='/'; else *path=0; } char *Ftp::ExtractPWD() { char *pwd=string_alloca(line.length()+1); const char *scan=strchr(line,'"'); if(scan==0) return 0; scan++; const char *right_quote=strrchr(scan,'"'); if(!right_quote) return 0; char *store=pwd; while(scanvms_path=true; normalize_path_vms(pwd); } else if(dev_len==2 || dev_len==3) { conn->dos_path=true; } if(!strchr(pwd,'/') || conn->dos_path) { // for safety -- against dosish ftpd for(char *s=pwd; *s; s++) if(*s=='\\') *s='/'; } return xstrdup(pwd); } int Ftp::SendCWD(const char *path,const char *path_url,Expect::expect_t c) { int cwd_count=0; if(QueryTriBool("ftp:use-tvfs",0,conn->tvfs_supported)) { conn->SendCmd2("CWD",path); expect->Push(new Expect(Expect::CWD_CURR,path)); cwd_count++; } else if(path_url) { path_url=url::path_ptr(path_url); if(path_url[0]=='/') path_url++; if(path_url[0]=='~') { if(path_url[1]==0) path_url++; else if(path_url[1]=='/') path_url+=2; } LogNote(9,"using URL path `%s'",path_url); char *path_url1=alloca_strdup(path_url); // to split it xstring path2("~"); for(char *dir_url=strtok(path_url1,"/"); dir_url; dir_url=strtok(NULL,"/")) { const char *dir=url::decode(dir_url); if(dir[0]=='/') path2.truncate(); if(path2.length()>0 && path2.last_char()!='/') path2.append('/'); path2.append(dir); conn->SendCmd2("CWD",dir); expect->Push(new Expect(Expect::CWD_CURR,path2)); cwd_count++; } } else { char *path1=alloca_strdup(path); // to split it char *path2=alloca_strdup(path); // to re-assemble if(AbsolutePath(path)) { if(real_cwd && !strncmp(real_cwd,path,real_cwd.length()) && path[real_cwd.length()]=='/') { path1+=real_cwd.length()+1; path2[real_cwd.length()]=0; } else { int dev_len=device_prefix_len(path); dev_len+=(path2[dev_len]=='/'); if(dev_len==1 && path[0]=='/' && real_cwd.ne("/")) { // just a root directory, append first path component const char *slash=strchr(path+1,'/'); if(slash) dev_len=slash-path; else dev_len=strlen(path); } path2[dev_len]=0; path1+=dev_len; if(!path2[0]) { if(real_cwd && strcmp(real_cwd,"~") && (!home.path || strcmp(real_cwd,home.path))) { conn->SendCmd("CWD"); expect->Push(new Expect(Expect::CWD_CURR,"~")); cwd_count++; } } else if(!real_cwd || strcmp(real_cwd,path2)) { conn->SendCmd2("CWD",path2); expect->Push(new Expect(Expect::CWD_CURR,path2)); cwd_count++; } } } else { strcpy(path2,"~"); if(path1[0]=='~') { if(path1[1]==0) path1++; else if(path1[1]=='/') path1+=2; } if(real_cwd && strcmp(real_cwd,"~") && (!home.path || strcmp(real_cwd,home.path))) { conn->SendCmd("CWD"); expect->Push(new Expect(Expect::CWD_CURR,"~")); cwd_count++; } } int path2_len=strlen(path2); for(char *dir=strtok(path1,"/"); dir; dir=strtok(NULL,"/")) { if(path2_len>0 && path2[path2_len-1]!='/') { strcpy(path2+path2_len,"/"); path2_len++; } strcpy(path2+path2_len,dir); path2_len+=strlen(dir); conn->SendCmd2("CWD",dir); expect->Push(new Expect(Expect::CWD_CURR,path2)); cwd_count++; } } Expect *last_cwd=expect->FindLastCWD(); if(last_cwd) { LogNote(9,"CWD path to be sent is `%s'",last_cwd->arg.get()); last_cwd->check_case=c; } return cwd_count; } Ftp::pasv_state_t Ftp::Handle_PASV() { unsigned a0,a1,a2,a3,p0,p1; /* * Extract address. RFC1123 says: * "...must scan the reply for the first digit..." */ for(const char *b=line+4; ; b++) { if(*b==0) { Disconnect(line); return PASV_NO_ADDRESS_YET; } if(!is_ascii_digit(*b)) continue; if(sscanf(b,"%u,%u,%u,%u,%u,%u",&a0,&a1,&a2,&a3,&p0,&p1)==6) break; } unsigned char *a,*p; conn->data_sa.sa.sa_family=conn->peer_sa.sa.sa_family; if(conn->data_sa.sa.sa_family==AF_INET) { a=(unsigned char*)&conn->data_sa.in.sin_addr; p=(unsigned char*)&conn->data_sa.in.sin_port; } #if INET6 else if(conn->data_sa.sa.sa_family==AF_INET6) { a=((unsigned char*)&conn->data_sa.in6.sin6_addr)+12; a[-1]=a[-2]=0xff; // V4MAPPED p=(unsigned char*)&conn->data_sa.in6.sin6_port; } #endif else { Disconnect("unsupported address family"); return PASV_NO_ADDRESS_YET; } a[0]=a0; a[1]=a1; a[2]=a2; a[3]=a3; p[0]=p0; p[1]=p1; bool ignore_pasv_address = QueryBool("ignore-pasv-address",hostname); if(ignore_pasv_address) LogNote(2,"Address returned by PASV is ignored according to ftp:ignore-pasv-address setting"); else if(conn->data_sa.is_reserved() || conn->data_sa.is_multicast() || (QueryBool("fix-pasv-address",hostname) && !conn->proxy_is_http && (conn->data_sa.is_private() != conn->peer_sa.is_private() || conn->data_sa.is_loopback() != conn->peer_sa.is_loopback()))) { // broken server, try to fix up ignore_pasv_address=true; conn->fixed_pasv=true; LogNote(2,"Address returned by PASV seemed to be incorrect and has been fixed"); } if(ignore_pasv_address) { if(conn->data_sa.sa.sa_family==AF_INET) memcpy(a,&conn->peer_sa.in.sin_addr,sizeof(conn->peer_sa.in.sin_addr)); #if INET6 else if(conn->data_sa.in.sin_family==AF_INET6) // peer_sa should be V4MAPPED memcpy(a,&conn->peer_sa.in6.sin6_addr.s6_addr[12],4); #endif } return PASV_HAVE_ADDRESS; } Ftp::pasv_state_t Ftp::Handle_EPSV() { char delim; char *format=alloca_strdup("|||%u|"); unsigned port; const char *c=strchr(line,'('); c=c?c+1:line+4; delim=*c; for(char *p=format; *p; p++) if(*p=='|') *p=delim; if(sscanf(c,format,&port)!=1) { LogError(0,_("cannot parse EPSV response")); Disconnect(_("cannot parse EPSV response")); return PASV_NO_ADDRESS_YET; } conn->data_sa=conn->peer_sa; if(conn->data_sa.sa.sa_family==AF_INET) conn->data_sa.in.sin_port=htons(port); #if INET6 else if(conn->data_sa.sa.sa_family==AF_INET6) conn->data_sa.in6.sin6_port=htons(port); #endif else { Disconnect("unsupported address family"); return PASV_NO_ADDRESS_YET; } return PASV_HAVE_ADDRESS; } void Ftp::CatchDATE(int act) { if(!fileset_for_info) return; FileInfo *fi=fileset_for_info->curr(); if(!fi) return; if(is2XX(act)) { if(line.length()>4 && is_ascii_digit(line[4])) fi->SetDate(ConvertFtpDate(line+4),0); } else if(is5XX(act)) { if(cmd_unsupported(act)) conn->mdtm_supported=false; } else { Disconnect(line); return; } fi->NoNeed(fi->DATE); if(!(fi->need&fi->SIZE)) fileset_for_info->next(); TrySuccess(); } void Ftp::CatchDATE_opt(int act) { if(!opt_date) return; if(is2XX(act) && line.length()>4 && is_ascii_digit(line[4])) { *opt_date=ConvertFtpDate(line+4); opt_date=0; } else { if(cmd_unsupported(act)) conn->mdtm_supported=false; *opt_date=NO_DATE; } } void Ftp::CatchSIZE(int act) { if(!fileset_for_info) return; FileInfo *fi=fileset_for_info->curr(); if(!fi) return; long long size=NO_SIZE; if(is2XX(act)) { if(line.length()>4) { if(sscanf(line+4,"%lld",&size)!=1) size=NO_SIZE; } } else if(is5XX(act)) { if(cmd_unsupported(act)) conn->size_supported=false; } else { Disconnect(line); return; } if(size>=1) fi->SetSize(size); fi->NoNeed(fi->SIZE); if(!(fi->need&fi->DATE)) fileset_for_info->next(); TrySuccess(); } void Ftp::CatchSIZE_opt(int act) { long long size=NO_SIZE; if(is2XX(act)) { if(line.length()>4) { if(sscanf(line+4,"%lld",&size)!=1) size=NO_SIZE; } } else { if(cmd_unsupported(act)) conn->size_supported=false; } // SIZE 0 is ignored (for some buggy servers). if(size<1) return; if(mode==RETRIEVE) entity_size=size; if(opt_size) { *opt_size=size; opt_size=0; } } Ftp::Connection::Connection(const char *c) : closure(c), send_cmd_buffer(DirectedBuffer::PUT) { control_sock=-1; telnet_layer_send=0; data_sock=-1; aborted_data_sock=-1; #if USE_SSL prot='C'; // current protection scheme 'C'lear or 'P'rivate auth_sent=false; auth_supported=true; cpsv_supported=false; sscn_supported=true; sscn_on=false; #endif type='A'; t_mode='S'; last_rest=0; rest_pos=0; quit_sent=false; fixed_pasv=false; translation_activated=false; sync_wait=1; // expect server greetings multiline_code=0; ignore_pass=false; try_feat_after_login=false; tune_after_login=false; utf8_activated=false; dos_path=false; vms_path=false; have_feat_info=false; mdtm_supported=true; size_supported=true; rest_supported=true; site_chmod_supported=true; site_utime_supported=true; site_utime2_supported=true; site_symlink_supported=true; site_mkdir_supported=false; pret_supported=false; utf8_supported=false; lang_supported=false; mlst_supported=false; clnt_supported=false; host_supported=false; mfmt_supported=false; mff_supported=false; epsv_supported=false; tvfs_supported=false; mode_z_supported=false; proxy_is_http=false; may_show_password=false; can_do_pasv=true; ssl_after_proxy=false; // Are we in the SSL stage, after using a proxy? nop_time=0; nop_count=0; nop_offset=0; abor_close_timer.SetResource("ftp:abor-max-wait",closure); stat_timer.SetResource("ftp:stat-interval",closure); waiting_150_timer.SetResource("ftp:waiting-150-timeout",closure); #if USE_SSL waiting_ssl_shutdown.SetResource("ftp:ssl-shutdown-timeout",closure); #endif } void Ftp::InitFtp() { #if USE_SSL ftps=false; // ssl and prot='P' by default (port 990) #endif eof=false; state=INITIAL_STATE; flags=SYNC_MODE; allow_skey=true; allow_netkey=true; force_skey=false; verify_data_address=true; use_stat=true; use_stat_for_list=true; use_mdtm=true; use_size=true; use_telnet_iac=true; use_pret=true; use_mlsd=false; max_buf=0x10000; copy_mode=COPY_NONE; copy_addr_valid=false; copy_passive=false; copy_protect=false; copy_ssl_connect=false; copy_done=false; copy_connection_open=false; copy_allow_store=false; copy_failed=false; disconnect_on_close=false; last_connection_failed=false; Reconfig(); } Ftp::Ftp() : super() { InitFtp(); } Ftp::Ftp(const Ftp *f) : super(f) { InitFtp(); state=INITIAL_STATE; flags=f->flags&MODES_MASK; Reconfig(); } Ftp::Connection::~Connection() { CloseAbortedDataConnection(); CloseDataConnection(); control_send=0; control_recv=0; #if USE_SSL control_ssl=0; // ssl should be freed after send/recv #endif if(control_sock!=-1) { LogNote(7,_("Closing control socket")); close(control_sock); } } void Ftp::PrepareToDie() { Enter(); Disconnect(); if(conn) { FlushSendQueue(); ReceiveResp(); } Disconnect(); Leave(); } bool Ftp::AbsolutePath(const char *s) const { if(!s || !*s) return false; int dev_len=device_prefix_len(s); return(s[0]=='/' || (s[0]=='~' && s[1]!=0 && s[1]!='/') || (conn && ((conn->dos_path && dev_len==3) || (conn->vms_path && dev_len>2)) && s[dev_len-1]=='/')); } // returns true if we need to sleep instead of moving to higher level. bool Ftp::GetBetterConnection(int level,bool limit_reached) { bool need_sleep=false; // if(level==0 && cwd==0) // return need_sleep; for(FA *fo=FirstSameSite(); fo!=0; fo=NextSameSite(fo)) { Ftp *o=(Ftp*)fo; // we are sure it is Ftp. if(o->GetConnectLevel()!=CL_LOGGED_IN) continue; if(!SameConnection(o)) continue; if(level==0 && xstrcmp(real_cwd,o->real_cwd)) continue; if(o->conn->data_sock!=-1 || o->state!=EOF_STATE || o->mode!=CLOSED) { /* session is in use; last resort is to takeover an active connection */ if(level<2) continue; /* only take over lower priority or suspended jobs */ if(!connection_takeover || (o->priority>=priority && !o->IsSuspended())) continue; if(o->conn->data_sock!=-1 && o->expect->Count()<=1) { /* don't take over active connections if they won't be able to resume */ if((o->flags&NOREST_MODE) && o->real_pos>0x1000) continue; if(o->QueryBool("web-mode",o->hostname)) continue; o->DataAbort(); o->DataClose(); if(!o->conn) return need_sleep; // oops... } else { if(!o->expect->IsEmpty() || o->disconnect_on_close) continue; } } else { if(limit_reached) { /* wait until job is diff seconds idle before taking it over */ int diff=o->last_priority-priority; if(diff>0) { /* number of seconds the task has been idle */ if(o->idle_timer.TimePassed()quit_sent=true; super::HandleTimeout(); DisconnectNow(); } // Create buffers after control socket had been connected. void Ftp::Connection::MakeBuffers() { #if USE_SSL control_ssl=0; #endif control_send=new IOBufferFDStream( new FDStream(control_sock,"control-socket"),IOBuffer::PUT); control_recv=new IOBufferFDStream( new FDStream(control_sock,"control-socket"),IOBuffer::GET); } void Ftp::Connection::InitTelnetLayer() { if(telnet_layer_send) return; control_send=telnet_layer_send=new IOBufferTelnet(control_send.borrow()); control_recv=new IOBufferTelnet(control_recv.borrow()); } bool Ftp::ProxyIsHttp() { if(!proxy_proto) return false; return !strcmp(proxy_proto,"http") || !strcmp(proxy_proto,"https"); } const char *Ftp::path_to_send() { if(mode==QUOTE_CMD || mode==LIST || mode==LONG_LIST) return file; xstring prefix(cwd.path.copy()); /* two cases: * root cwd / vs /file -> file * non-root cwd /cwd vs /cwd/file -> file */ if(prefix.last_char()!='/') prefix.append('/'); /* cwd//file or cwd/ are not converted */ if(file.begins_with(prefix) && file.length()>prefix.length() && file[prefix.length()]!='/') return file+prefix.length(); return file; } int Ftp::Do() { const char *command=0; bool append_file=false; int res; socklen_t addr_len; const unsigned char *a; const unsigned char *p; automate_state oldstate; int m=STALL; const char *error; // check if idle time exceeded if(mode==CLOSED && conn && idle_timer.Stopped()) { LogNote(1,_("Closing idle connection")); Disconnect(); if(conn) idle_timer.Reset(); return m; } if(conn && conn->quit_sent) { m|=FlushSendQueue(); m|=ReceiveResp(); if(expect && expect->IsEmpty()) { DisconnectNow(); return MOVED; } goto usual_return; } /* Some servers cannot detect ABOR, help them by reading remaining data and closing data connection in few seconds */ if(conn && conn->aborted_data_sock!=-1) { char discard[0x2000]; int res=read(conn->aborted_data_sock,discard,sizeof(discard)); if(res==0 || conn->abor_close_timer.Stopped()) conn->CloseAbortedDataConnection(); else Block(conn->aborted_data_sock,POLLIN); } if(Error() || eof || mode==CLOSED) { // inactive behavior if(conn) { m|=FlushSendQueue(); m|=ReceiveResp(); } if(eof || mode==CLOSED) goto notimeout_return; goto usual_return; } if(!hostname) return m; if(mode==MP_LIST && !use_mlsd) { SetError(NOT_SUPP,_("MLSD is disabled by ftp:use-mlsd")); return MOVED; } switch(state) { case(INITIAL_STATE): { // walk through ftp classes and try to find identical idle ftp session // first try "easy" cases of session take-over. int connection_limit=GetConnectionLimit(); for(int i=0; i<3; i++) { bool limit_reached=last_connection_failed || (connection_limit>0 && connection_limit<=CountConnections()); if(i>=2 && !limit_reached) break; bool need_sleep=GetBetterConnection(i,limit_reached); if(state!=INITIAL_STATE) return MOVED; if(need_sleep) return m; } if(!resolver && mode!=CONNECT_VERIFY && !ReconnectAllowed()) return m; if(ftps) m|=Resolve(FTPS_DEFAULT_PORT,"ftps","tcp"); else m|=Resolve(FTP_DEFAULT_PORT,"ftp","tcp"); if(!peer) return m; if(mode==CONNECT_VERIFY) return m; if(!ReconnectAllowed()) return m; if(!NextTry()) return MOVED; last_connection_failed=false; assert(!conn); assert(!expect); conn=new Connection(hostname); expect=new ExpectQueue(); conn->proxy_is_http=ProxyIsHttp(); if(conn->proxy_is_http) SetFlag(PASSIVE_MODE,1); conn->peer_sa=peer[peer_curr]; conn->control_sock=SocketCreateTCP(conn->peer_sa.sa.sa_family); if(conn->control_sock==-1) { conn=0; expect=0; if(peer_curr+1peer_sa.sa.sa_family); SetError(SEE_ERRNO,str); return MOVED; } if(QueryBool("use-ip-tos",hostname)) MinimizeLatency(conn->control_sock); SayConnectingTo(); res=SocketConnect(conn->control_sock,&conn->peer_sa); state=CONNECTING_STATE; if(res==-1 && errno!=EINPROGRESS) { saved_errno=errno; LogError(0,"connect(control_sock): %s",strerror(saved_errno)); if(NotSerious(saved_errno)) { Disconnect(strerror(saved_errno)); return MOVED; } goto system_error; } m=MOVED; timeout_timer.Reset(); } /* fallthrough */ case(CONNECTING_STATE): assert(conn && conn->control_sock!=-1); res=Poll(conn->control_sock,POLLOUT,&error); if(res==-1) { LogError(0,_("Socket error (%s) - reconnecting"),error); Disconnect(error); return MOVED; } if(!(res&POLLOUT)) goto usual_return; #if USE_SSL if(proxy && (!xstrcmp(proxy_proto,"ftps") || !xstrcmp(proxy_proto,"https"))) { conn->MakeSSLBuffers(hostname); } else // note the following block #endif { conn->MakeBuffers(); } if(!proxy || !conn->proxy_is_http) goto pre_CONNECTED_STATE; state=HTTP_PROXY_CONNECTED; m=MOVED; HttpProxySendConnect(); /* fallthrough */ case HTTP_PROXY_CONNECTED: if(!HttpProxyReplyCheck(conn->control_recv)) goto usual_return; pre_CONNECTED_STATE: #if USE_SSL if(ftps && (!proxy || conn->proxy_is_http)) { conn->MakeSSLBuffers(hostname); const char *initial_prot=ResMgr::Query("ftps:initial-prot",hostname); conn->prot=initial_prot[0]; } #endif if(use_telnet_iac) conn->InitTelnetLayer(); state=CONNECTED_STATE; m=MOVED; expect->Push(Expect::READY); if(use_feat) { if(!proxy || conn->proxy_is_http) { conn->SendCmd("FEAT"); expect->Push(Expect::FEAT); } else { conn->try_feat_after_login=true; } } /* fallthrough */ case CONNECTED_STATE: { m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=CONNECTED_STATE || Error()) return MOVED; if(expect->Has(Expect::FEAT) || conn->quit_sent) goto usual_return; #if USE_SSL if(QueryBool((user && pass)?"ssl-allow":"ssl-allow-anonymous",hostname) && !ftps && (!proxy || conn->proxy_is_http)) SendAuth(Query("ssl-auth",hostname)); if(state!=CONNECTED_STATE) return MOVED; if(conn->auth_sent && !expect->IsEmpty()) goto usual_return; #endif // Do connection tuning after AUTH TLS. if(conn->have_feat_info) TuneConnectionAfterFEAT(); const char *user_to_use=(user?user.get():anon_user.get()); const char *proxy_auth_type=Query("proxy-auth-type",proxy); // Only enter if we are using a proxy, and not a http proxy. // If we are in the ssl-auth stage after using a proxy, skip this. bool auth_allowed=false; if(proxy && !conn->proxy_is_http && !conn->ssl_after_proxy) { if(!strcmp(proxy_auth_type,"joined") && proxy_user && proxy_pass) { user_to_use=xstring::cat(user_to_use,"@",proxy_user.get(),"@", hostname.get(),portname?":":NULL,portname.get(),NULL); } else if(!strcmp(proxy_auth_type,"joined-acct") && proxy_user && proxy_pass) { user_to_use=xstring::cat(user_to_use,"@",hostname.get(), portname?":":"",portname?portname.get():"", " ",proxy_user.get(),NULL); // proxy_pass is sent later with ACCT command } else if(!strcmp(proxy_auth_type,"proxy-user@host") && proxy_user && proxy_pass) { expect->Push(Expect::USER_PROXY); conn->SendCmd2("USER",xstring::cat(proxy_user.get(),"@",hostname.get(), portname?":":"",portname?portname.get():"",NULL)); expect->Push(Expect::PASS_PROXY); conn->SendCmd2("PASS",proxy_pass); auth_allowed=true; } else // no proxy auth, or type is `open' or `user'. { if(proxy_user && proxy_pass) { expect->Push(Expect::USER_PROXY); conn->SendCmd2("USER",proxy_user); expect->Push(Expect::PASS_PROXY); conn->SendCmd2("PASS",proxy_pass); } if(!strcmp(proxy_auth_type,"open")) { expect->Push(Expect::OPEN_PROXY); conn->SendCmd2("OPEN",xstring::cat(hostname.get(), portname?":":NULL,portname.get(),NULL)); auth_allowed=true; } else // "user" or no proxy auth { user_to_use=xstring::cat(user_to_use,"@",hostname.get(), portname?":":NULL,portname.get(),NULL); } } #if USE_SSL if(auth_allowed) { if(QueryBool((user && pass)?"ssl-allow":"ssl-allow-anonymous",hostname)) { SendAuth(Query("ssl-auth",hostname)); if(state!=CONNECTED_STATE) return MOVED; // We are now waiting for auth TLS. conn->ssl_after_proxy=true; if(conn->auth_sent && !expect->IsEmpty()) goto usual_return; } } #endif } skey_pass.set(0); netkey_pass.set(0); expect->Push(Expect::USER); conn->SendCmd2("USER",user_to_use); state=USER_RESP_WAITING_STATE; m=MOVED; } /* fallthrough */ case(USER_RESP_WAITING_STATE): { if((GetFlag(SYNC_MODE) || (user && pass && allow_skey)) && !expect->IsEmpty()) { m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=USER_RESP_WAITING_STATE || Error()) return MOVED; if(!expect->IsEmpty()) goto usual_return; } const char *proxy_auth_type=Query("proxy-auth-type",proxy); if(!conn->ignore_pass) { conn->may_show_password = (skey_pass!=0) || (netkey_pass!=0) || (user==0) || pass_open; const char *pass_to_use=(pass?pass:anon_pass); if(allow_skey && skey_pass) pass_to_use=skey_pass; else if(allow_netkey && netkey_pass) pass_to_use=netkey_pass; else if(proxy && !conn->proxy_is_http && proxy_user && proxy_pass && !strcmp(proxy_auth_type,"joined")) pass_to_use=xstring::cat(pass_to_use,"@",proxy_pass.get(),NULL); expect->Push(Expect::PASS); conn->SendCmd2("PASS",pass_to_use); } if(proxy && !conn->proxy_is_http && proxy_user && proxy_pass && !strcmp(proxy_auth_type,"joined-acct")) { expect->Push(Expect::ACCT_PROXY); conn->SendCmd2("ACCT",proxy_pass); } SendAcct(); if(conn->try_feat_after_login) { conn->SendCmd("FEAT"); expect->Push(Expect::FEAT); } else { if(conn->tune_after_login) TuneConnectionAfterFEAT(); if(conn->mlst_attr_supported) SendOPTS_MLST(); } SendSiteGroup(); SendSiteIdle(); SendSiteCommands(); if(!home_auto) { // if we don't yet know the home location, try to get it conn->SendCmd("PWD"); expect->Push(Expect::PWD); } #if USE_SSL if(conn->ssl_is_activated()) { conn->SendCmd("PBSZ 0"); expect->Push(Expect::IGNORE); // select PROT mode before CCC if there is no need to change it later bool prot_data=QueryBool("ssl-protect-data"); bool prot_list=QueryBool("ssl-protect-list"); if(prot_data==prot_list) SendPROT(prot_data?'P':'C'); if(QueryBool("ssl-use-ccc")) { conn->SendCmd("CCC"); expect->Push(Expect::CCC); } } #endif // USE_SSL set_real_cwd(0); } /* fallthrough */ pre_EOF_STATE: state=EOF_STATE; m=MOVED; case(EOF_STATE): m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=EOF_STATE || Error()) return MOVED; if(expect->Has(Expect::FEAT) || expect->Has(Expect::OPTS_UTF8) || expect->Has(Expect::LANG)) goto usual_return; #if USE_SSL if(expect->Has(Expect::CCC) || expect->Has(Expect::PROT)) goto usual_return; #endif // USE_SSL if(!conn->utf8_activated && charset && *charset) conn->SetControlConnectionTranslation(charset); if(mode==CONNECT_VERIFY) goto notimeout_return; if(mode==CHANGE_MODE && !conn->mff_supported && !conn->site_chmod_supported) { SetError(NOT_SUPP,_("MFF and SITE CHMOD are not supported by this site")); return MOVED; } if(mode==MP_LIST && !conn->mlst_supported) { SetError(NOT_SUPP,_("MLST and MLSD are not supported by this site")); return MOVED; } if(home.path==0 && !expect->IsEmpty()) goto usual_return; if(!retry_timer.Stopped()) goto usual_return; if(real_cwd==0) set_real_cwd(home_auto); ExpandTildeInCWD(); if(!CheckRetries()) return MOVED; if(mode!=CHANGE_DIR) { Expect *last_cwd=expect->FindLastCWD(); // Send CWD if we have a CWD in flight and it differs from wanted cwd // or we don't have a CWD and read_cwd differs from wanted cwd if((!last_cwd && xstrcmp(cwd,real_cwd) && !(real_cwd==0 && !xstrcmp(cwd,"~"))) || (last_cwd && xstrcmp(last_cwd->arg,cwd))) { SendCWD(cwd,cwd.url,Expect::CWD_CURR); } else if(last_cwd && !xstrcmp(last_cwd->arg,cwd)) { // no need for extra CWD, one's already sent. last_cwd->check_case=Expect::CWD_CURR; } } #if USE_SSL if(conn->ssl_is_activated() || (conn->auth_supported && conn->auth_sent)) { char want_prot=conn->prot; const char *protect_res=get_protect_res(); if(protect_res) want_prot=QueryBool(protect_res,hostname)?'P':'C'; if(copy_mode!=COPY_NONE) want_prot=copy_protect?'P':'C'; bool want_sscn=conn->sscn_on; if(copy_mode!=COPY_NONE) want_sscn=copy_protect && copy_ssl_connect && !(copy_passive && conn->cpsv_supported); else if(mode==RETRIEVE || mode==STORE || mode==LIST || mode==MP_LIST || (mode==LONG_LIST && !use_stat_for_list)) want_sscn=false; if(conn->sscn_supported && want_sscn!=conn->sscn_on) { conn->SendCmd2("SSCN",want_sscn?"ON":"OFF"); expect->Push(new Expect(Expect::SSCN,want_sscn?'Y':'N')); } SendPROT(want_prot); } #endif state=CWD_CWD_WAITING_STATE; m=MOVED; /* fallthrough */ case CWD_CWD_WAITING_STATE: { m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=CWD_CWD_WAITING_STATE || Error()) return MOVED; // wait for all CWD to finish if(mode!=CHANGE_DIR && expect->FindLastCWD()) goto usual_return; #if USE_SSL // PROT and SSCN are critical for data transfers if(expect->Has(Expect::PROT) || expect->Has(Expect::SSCN)) goto usual_return; #endif // address of peer is not known yet if(copy_mode!=COPY_NONE && !copy_passive && !copy_addr_valid) goto usual_return; if(entity_size>=0 && entity_size<=pos && (mode==RETRIEVE || (mode==STORE && entity_size>0))) { if(mode==STORE) SendUTimeRequest(); if(mode==RETRIEVE) LogNote(9,"received all data but no EOF\n"); eof=true; goto pre_WAITING_STATE; // simulate eof. } if(!conn->rest_supported) flags|=NOREST_MODE; if(mode==STORE && GetFlag(NOREST_MODE) && pos>0) pos=0; if(copy_mode==COPY_NONE && (mode==RETRIEVE || mode==STORE || mode==LIST || mode==MP_LIST || (mode==LONG_LIST && !use_stat_for_list))) { assert(conn->data_sock==-1); conn->data_sock=SocketCreateUnboundTCP(conn->peer_sa.sa.sa_family,hostname); if(conn->data_sock==-1) { saved_errno=errno; LogError(0,"socket(data): %s",strerror(saved_errno)); goto system_error; } if(QueryBool("use-ip-tos",hostname)) MaximizeThroughput(conn->data_sock); addr_len=sizeof(conn->data_sa); getsockname(conn->control_sock,&conn->data_sa.sa,&addr_len); // Try to assign a port from given range Range range(Query("port-range")); for(int t=0; ; t++) { if(t>=10) { close(conn->data_sock); conn->data_sock=-1; TimeoutS(1); // retry later. return m; } if(t==9) ReuseAddress(conn->data_sock); // try to reuse address. int port=0; if(!range.IsFull()) port=range.Random(); bool do_addr_bind=QueryBool("bind-data-socket") && !conn->peer_sa.is_loopback(); if(!do_addr_bind && !port) break; // nothing to bind if(conn->data_sa.sa.sa_family==AF_INET) { conn->data_sa.in.sin_port=htons(port); if(!do_addr_bind) memset(&conn->data_sa.in.sin_addr,0,sizeof(conn->data_sa.in.sin_addr)); } #if INET6 else if(conn->data_sa.sa.sa_family==AF_INET6) { conn->data_sa.in6.sin6_port=htons(port); if(!do_addr_bind) memset(&conn->data_sa.in6.sin6_addr,0,sizeof(conn->data_sa.in6.sin6_addr)); } #endif else { Fatal("unsupported network protocol"); return MOVED; } if(bind(conn->data_sock,&conn->data_sa.sa,addr_len)==0) break; saved_errno=errno; // Fail unless socket was already taken if(saved_errno!=EINVAL && saved_errno!=EADDRINUSE) { LogError(0,"bind(data_sock,[%s]:%d): %s", SocketNumericAddress(&conn->data_sa),port,strerror(saved_errno)); close(conn->data_sock); conn->data_sock=-1; if(NonFatalError(saved_errno)) { TimeoutS(1); return m; } SetError(SEE_ERRNO,"Cannot bind data socket for ftp:port-range"); return MOVED; } LogError(10,"bind(data_sock,[%s]:%d): %s", SocketNumericAddress(&conn->data_sa),port,strerror(saved_errno)); } if(!GetFlag(PASSIVE_MODE)) listen(conn->data_sock,1); // get the allocated port addr_len=sizeof(conn->data_sa); getsockname(conn->data_sock,&conn->data_sa.sa,&addr_len); } char want_type=(ascii?'A':'I'); char want_t_mode='S'; if(conn->mode_z_supported && QueryBool("use-mode-z",hostname) && (mode==LIST || mode==LONG_LIST || mode==MP_LIST || ((mode==RETRIEVE || mode==STORE) && !re_match(file,Query("compressed-re"))))) { want_t_mode='Z'; } if(GetFlag(NOREST_MODE) || pos==0) real_pos=0; else real_pos=-1; // we don't yet know if REST will succeed flags&=~IO_FLAG; last_priority=priority; conn->received_150=false; switch((enum open_mode)mode) { case(RETRIEVE): if(file[0]==0) goto long_list; command="RETR"; append_file=true; break; case(STORE): if(!QueryBool("rest-stor",hostname)) { real_pos=0; // some old servers don't handle REST/STOR properly. pos=0; } command="STOR"; append_file=true; break; long_list: case(LONG_LIST): if(use_stat_for_list) { real_pos=0; command="STAT"; conn->data_iobuf=new IOBuffer(IOBuffer::GET); rate_limit=new RateLimit(hostname); want_type=conn->type; want_t_mode=conn->t_mode; } else { want_type='A'; if(!rest_list) real_pos=0; // some ftp servers do not do REST/LIST. command="LIST"; } if(list_options && list_options[0]) command=xstring::cat(command," ",list_options.get(),NULL); if(file && file[0]) append_file=true; if(use_stat_for_list && !append_file && !strchr(command,' ')) command="STAT ."; break; case(MP_LIST): want_type='A'; real_pos=0; // REST doesn't work for MLSD command="MLSD"; if(file && file[0]) append_file=true; break; case(LIST): want_type='A'; real_pos=0; // REST doesn't work for NLST command="NLST"; if(file && file[0]) append_file=true; break; case(CHANGE_DIR): if((real_cwd && !xstrcmp(real_cwd,file)) || SendCWD(file,file_url,Expect::CWD)==0) cwd.Set(file,false,file_url,device_prefix_len(file)); goto pre_WAITING_STATE; case(MAKE_DIR): command="MKD"; if(mkdir_p && conn->site_mkdir_supported) command="SITE MKDIR"; append_file=true; want_type=conn->type; break; case(REMOVE_DIR): command="RMD"; append_file=true; want_type=conn->type; break; case(REMOVE): command="DELE"; append_file=true; want_type=conn->type; break; case(QUOTE_CMD): real_pos=0; command=""; append_file=true; conn->data_iobuf=new IOBuffer(IOBuffer::GET); rate_limit=new RateLimit(hostname); break; case(RENAME): command="RNFR"; append_file=true; want_type=conn->type; break; case(LINK): command="SITE LINK"; append_file=true; want_type=conn->type; break; case(SYMLINK): if(!conn->site_symlink_supported) { SetError(NOT_SUPP,_("SITE SYMLINK is not supported by the server")); return MOVED; } command="SITE SYMLINK"; append_file=true; want_type=conn->type; break; case(ARRAY_INFO): break; case(CHANGE_MODE): { if(conn->mff_supported) command=xstring::format("MFF UNIX.mode=%03o;",chmod_mode); else command=xstring::format("SITE CHMOD %03o",chmod_mode); append_file=true; want_type=conn->type; break; } case(CONNECT_VERIFY): case(CLOSED): state=EOF_STATE; } if(want_type!=conn->type) { conn->SendCmdF("TYPE %c",want_type); expect->Push(new Expect(Expect::TYPE,want_type)); } if(want_t_mode!=conn->t_mode) { conn->SendCmdF("MODE %c",want_t_mode); expect->Push(new Expect(Expect::MODE,want_t_mode)); } const char *file=path_to_send(); if(opt_size && conn->size_supported && file[0] && use_size) { conn->SendCmd2("SIZE",file,url::path_ptr(file_url),home); expect->Push(Expect::SIZE_OPT); } if(opt_date && conn->mdtm_supported && file[0] && use_mdtm) { conn->SendCmd2("MDTM",file,url::path_ptr(file_url),home); expect->Push(Expect::MDTM_OPT); } if(mode==ARRAY_INFO) { SendArrayInfoRequests(); goto pre_WAITING_STATE; } const char *file_to_append=0; if(append_file) file_to_append=path_to_send(); if(mode==QUOTE_CMD || mode==CHANGE_MODE || (mode==LONG_LIST && use_stat_for_list) || mode==REMOVE || mode==REMOVE_DIR || mode==MAKE_DIR || mode==RENAME) { if(mode==MAKE_DIR && mkdir_p && !conn->site_mkdir_supported) { Ref dirs(MkdirMakeSet()); for(int i=0; iCount(); i++) { conn->SendCmd2("MKD",dirs->String(i)); expect->Push(Expect::IGNORE); } } if(append_file) conn->SendCmd2(command,file_to_append,url::path_ptr(file_url),home); else conn->SendCmd(command); Expect::expect_t e=Expect::FILE_ACCESS; if(mode==QUOTE_CMD) { e=Expect::QUOTED; if(!strncasecmp(file,"CWD",3) || !strncasecmp(file,"CDUP",4) || !strncasecmp(file,"XCWD",4) || !strncasecmp(file,"XCUP",4)) { LogNote(9,"Resetting cwd"); set_real_cwd(0); // we do not know the path now. } } else if(mode==LONG_LIST) e=Expect::QUOTED; else if(mode==RENAME) e=Expect::RNFR; expect->Push(new Expect(e,file,command)); goto pre_WAITING_STATE; } if(mode==LINK || mode==SYMLINK) { conn->SendCmdF("%s %s %s",command,file_to_append,file1.get()); expect->Push(new Expect(Expect::FILE_ACCESS,0,command)); goto pre_WAITING_STATE; } if((copy_mode==COPY_NONE && GetFlag(PASSIVE_MODE)) || (copy_mode!=COPY_NONE && copy_passive)) { if(use_pret && conn->pret_supported) { conn->SendCmd(xstring::cat("PRET ",command," ",file_to_append,NULL)); expect->Push(Expect::PRET); } conn->can_do_pasv=(conn->peer_sa.sa.sa_family==AF_INET); #if INET6 conn->can_do_pasv|=(conn->peer_sa.sa.sa_family==AF_INET6 && IN6_IS_ADDR_V4MAPPED(&conn->peer_sa.in6.sin6_addr)); #endif #if USE_SSL if(conn->can_do_pasv && copy_mode!=COPY_NONE && conn->prot=='P' && !conn->sscn_on && copy_ssl_connect) { conn->SendCmd("CPSV"); // same as PASV, but server does SSL_connect expect->Push(Expect::PASV); } else #endif // note the following statement if(!conn->can_do_pasv || (conn->epsv_supported && QueryBool("prefer-epsv",hostname))) { conn->SendCmd("EPSV"); expect->Push(Expect::EPSV); } else { conn->SendCmd("PASV"); expect->Push(Expect::PASV); } pasv_state=PASV_NO_ADDRESS_YET; } else // !PASSIVE { sockaddr_u control_sa; if(copy_mode!=COPY_NONE) conn->data_sa=copy_addr; if(conn->data_sa.sa.sa_family==AF_INET) { a=(const unsigned char*)&conn->data_sa.in.sin_addr; p=(const unsigned char*)&conn->data_sa.in.sin_port; // check if data socket address is unbound if((a[0]|a[1]|a[2]|a[3])==0) { socklen_t addr_len=sizeof(control_sa); getsockname(conn->control_sock,&control_sa.sa,&addr_len); a=(const unsigned char*)&control_sa.in.sin_addr; } #if INET6 ipv4_port: #endif if(copy_mode==COPY_NONE) { const char *port_ipv4=Query("port-ipv4",hostname); struct in_addr fake_ip; if(port_ipv4 && port_ipv4[0]) { if(inet_pton(AF_INET,port_ipv4,&fake_ip)) a=(const unsigned char*)&fake_ip; } } conn->SendCmdF("PORT %d,%d,%d,%d,%d,%d",a[0],a[1],a[2],a[3],p[0],p[1]); expect->Push(Expect::PORT); } else { #if INET6 if(conn->data_sa.sa.sa_family==AF_INET6 && IN6_IS_ADDR_V4MAPPED(&conn->data_sa.in6.sin6_addr)) { a=((unsigned char*)&conn->data_sa.in6.sin6_addr)+12; p=(unsigned char*)&conn->data_sa.in6.sin6_port; goto ipv4_port; } conn->SendCmd2("EPRT",encode_eprt(&conn->data_sa)); expect->Push(Expect::PORT); #else Fatal(_("unsupported network protocol")); return MOVED; #endif } } if(mode==STORE && entity_size!=NO_SIZE && QueryBool("use-allo",hostname)) { // ALLO is usually ignored by servers, but send it anyway. conn->SendCmdF("ALLO %lld",(long long)entity_size); expect->Push(Expect::ALLO); } // some broken servers don't reset REST after a transfer, // so check if last_rest was different. if(real_pos==-1 || conn->last_rest!=real_pos) { conn->rest_pos=(real_pos!=-1?real_pos:pos); conn->SendCmdF("REST %lld",(long long)conn->rest_pos); expect->Push(Expect::REST); real_pos=-1; } if(copy_mode!=COPY_DEST || copy_allow_store) { if(append_file) conn->SendCmd2(command,file_to_append,url::path_ptr(file_url),home); else conn->SendCmd(command); expect->Push(Expect::TRANSFER); } m=MOVED; if(copy_mode!=COPY_NONE && !copy_passive) goto pre_WAITING_STATE; if((copy_mode==COPY_NONE && GetFlag(PASSIVE_MODE)) || (copy_mode!=COPY_NONE && copy_passive)) { state=DATASOCKET_CONNECTING_STATE; goto datasocket_connecting_state; } state=ACCEPTING_STATE; } /* fallthrough */ case(ACCEPTING_STATE): m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=ACCEPTING_STATE || Error()) return MOVED; res=Poll(conn->data_sock,POLLIN,&error); if(res==-1) { LogError(0,_("Data socket error (%s) - reconnecting"),error); Disconnect(error); return MOVED; } if(!(res&POLLIN)) goto usual_return; res=SocketAccept(conn->data_sock,&conn->data_sa,hostname); if(res==-1) { saved_errno=errno; if(saved_errno==EWOULDBLOCK) goto usual_return; if(NotSerious(saved_errno)) { LogError(0,"%s",strerror(saved_errno)); Disconnect(strerror(saved_errno)); return MOVED; } goto system_error; } close(conn->data_sock); conn->data_sock=res; if(QueryBool("use-ip-tos",hostname)) MaximizeThroughput(conn->data_sock); LogNote(5,_("Accepted data connection from (%s) port %u"), SocketNumericAddress(&conn->data_sa),SocketPort(&conn->data_sa)); if(!conn->data_address_ok(0,verify_data_address,verify_data_port)) { Disconnect("invalid data connection address"); return MOVED; } goto pre_waiting_150; case(DATASOCKET_CONNECTING_STATE): datasocket_connecting_state: if(pasv_state!=PASV_DATASOCKET_CONNECTING) m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=DATASOCKET_CONNECTING_STATE || Error()) return MOVED; switch(pasv_state) { case PASV_NO_ADDRESS_YET: goto usual_return; case PASV_HAVE_ADDRESS: if(copy_mode==COPY_NONE && !conn->data_address_ok(&conn->data_sa,verify_data_address,/*port_verify*/false)) { Disconnect("invalid data connection address"); return MOVED; } pasv_state=PASV_DATASOCKET_CONNECTING; if(copy_mode!=COPY_NONE) { memcpy(©_addr,&conn->data_sa,sizeof(conn->data_sa)); copy_addr_valid=true; goto pre_WAITING_STATE; } if(!conn->proxy_is_http) { LogNote(5,_("Connecting data socket to (%s) port %u"), SocketNumericAddress(&conn->data_sa),SocketPort(&conn->data_sa)); res=SocketConnect(conn->data_sock,&conn->data_sa); } else // proxy_is_http { LogNote(5,_("Connecting data socket to proxy %s (%s) port %u"), proxy.get(),SocketNumericAddress(&conn->peer_sa),SocketPort(&conn->peer_sa)); res=SocketConnect(conn->data_sock,&conn->peer_sa); } if(res==-1 && errno!=EINPROGRESS) { saved_errno=errno; LogError(0,"connect: %s",strerror(saved_errno)); Disconnect(strerror(saved_errno)); if(NotSerious(saved_errno)) return MOVED; goto system_error; } m=MOVED; /* fallthrough */ case PASV_DATASOCKET_CONNECTING: res=Poll(conn->data_sock,POLLOUT,&error); if(res==-1) { LogError(0,_("Data socket error (%s) - reconnecting"),error); if(conn->fixed_pasv && QueryBool("auto-passive-mode",hostname)) { LogNote(2,_("Switching passive mode off")); SetFlag(PASSIVE_MODE,0); } Disconnect(error); return MOVED; } if(!(res&POLLOUT)) goto usual_return; LogNote(9,_("Data connection established")); if(!conn->proxy_is_http) goto pre_waiting_150; pasv_state=PASV_HTTP_PROXY_CONNECTED; m=MOVED; conn->data_iobuf=new IOBufferFDStream(new FDStream(conn->data_sock,"data-socket"),IOBuffer::PUT); HttpProxySendConnectData(); conn->data_iobuf->Roll(); // FIXME, data_iobuf could be not done yet conn->data_iobuf=new IOBufferFDStream(new FDStream(conn->data_sock,"data-socket"),IOBuffer::GET); /* fallthrough */ case PASV_HTTP_PROXY_CONNECTED: if(HttpProxyReplyCheck(conn->data_iobuf)) goto pre_waiting_150; goto usual_return; } /* fallthrough */ pre_waiting_150: state=WAITING_150_STATE; conn->waiting_150_timer.Reset(); rate_limit=new RateLimit(hostname); m=MOVED; case WAITING_150_STATE: m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=WAITING_150_STATE || Error()) return MOVED; if(!conn->received_150 && !expect->IsEmpty() && !conn->waiting_150_timer.Stopped()) goto usual_return; // now init data connection properly and start data exchange state=DATA_OPEN_STATE; m=MOVED; #if USE_SSL if(conn->prot=='P') { Ref ssl(new lftp_ssl(conn->data_sock,lftp_ssl::CLIENT,hostname)); if(QueryBool("ssl-data-use-keys",hostname) || !conn->control_ssl) ssl->load_keys(); // share session id between control and data connections. if(conn->control_ssl && QueryBool("ssl-copy-sid",hostname)) ssl->copy_sid(conn->control_ssl); IOBuffer::dir_t dir=(mode==STORE?IOBuffer::PUT:IOBuffer::GET); IOBufferSSL *ssl_buf=new IOBufferSSL(ssl.borrow(),dir); conn->data_iobuf=ssl_buf; } else // note the following block #endif { IOBuffer::dir_t dir=(mode==STORE?IOBuffer::PUT:IOBuffer::GET); if(!conn->data_iobuf || conn->data_iobuf->GetDirection()!=dir) conn->data_iobuf=new IOBufferFDStream(new FDStream(conn->data_sock,"data-socket"),dir); } if(conn->t_mode=='Z') { if(mode==STORE) conn->AddDataTranslator(new DataDeflator(Query("mode-z-level",hostname))); else conn->AddDataTranslator(new DataInflator()); } if(mode==LIST || mode==LONG_LIST || mode==MP_LIST) { const char *cset=conn->utf8_activated?"UTF-8":charset.get(); if(cset && *cset) conn->AddDataTranslation(cset,true); } rate_limit->SetBufferSize(conn->data_iobuf,max_buf); /* fallthrough */ case(DATA_OPEN_STATE): { if(expect->IsEmpty() && conn->data_sock!=-1) { // When ftp server has sent "Transfer complete" it is idle, // but the data can be still unsent in server side kernel buffer. // So the ftp server can decide the connection is idle for too long // time and disconnect. This hack is to prevent the above. if(now.UnixTime() >= conn->nop_time+nop_interval) { // prevent infinite NOOP's if(conn->nop_offset==pos && timeout_timer.GetLastSetting()nop_count*nop_interval) { LogError(1,"NOOP timeout"); HandleTimeout(); return MOVED; } if(conn->nop_time!=0) { conn->nop_count++; conn->SendCmd("NOOP"); expect->Push(Expect::IGNORE); } conn->nop_time=now; if(conn->nop_offset!=pos) conn->nop_count=0; conn->nop_offset=pos; } TimeoutS(nop_interval-(time_t(now)-conn->nop_time)); } oldstate=state; m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=oldstate || Error()) return MOVED; timeout_timer.Reset(conn->data_iobuf->EventTime()); if(conn->data_iobuf->Error() && conn->data_sock!=-1) { LogError(0,"%s",conn->data_iobuf->ErrorText()); conn->CloseDataSocket(); // workaround for proftpd bug - it resets data connection when no files found. if(mode==LIST && expect->IsEmpty() && !conn->received_150 && conn->data_iobuf->GetPos()==0) { DataClose(); state=EOF_STATE; eof=true; return MOVED; } } // handle errors on data connection only when storing or got all replies // and read all data. if(conn->data_iobuf->Error() && (mode==STORE || (expect->IsEmpty() && conn->data_iobuf->Size()==0))) { if(conn->data_iobuf->ErrorFatal()) SetError(FATAL,conn->data_iobuf->ErrorText()); if(!expect->IsEmpty()) DisconnectNow(); else { DataClose(); state=EOF_STATE; if(mode==STORE && GetFlag(IO_FLAG)) SetError(STORE_FAILED,0); else if(NextTry()) retry_timer.Set(2); // retry after 2 seconds } return MOVED; } if(mode!=STORE) { if(conn->data_iobuf->Size()>=rate_limit->BytesAllowedToGet()) { conn->data_iobuf->Suspend(); TimeoutS(1); } else if(conn->data_iobuf->Size()>=max_buf) { conn->data_iobuf->Suspend(); m=MOVED; } else if(conn->data_iobuf->IsSuspended() && !IsSuspended()) { conn->data_iobuf->Resume(); if(conn->data_iobuf->Size()>0) m=MOVED; } if(conn->data_iobuf->Size()==0 && (conn->data_iobuf->Eof() || conn->data_iobuf->TranslationEOF())) { if(conn->data_iobuf->Eof()) LogNote(9,"Got EOF on data connection"); else if(conn->data_iobuf->TranslationEOF()) LogNote(9,"Whole entity has been received and decoded"); conn->data_iobuf->PutEOF(); // for ssl shutdown DataClose(); if(expect->IsEmpty()) { eof=true; m=MOVED; } state=WAITING_STATE; } } if(state!=oldstate || Error()) return MOVED; CheckTimeout(); if(state!=oldstate) return MOVED; goto usual_return; } pre_WAITING_STATE: if(copy_mode!=COPY_NONE) TrySuccess(); // it is enough to get here in copying. state=WAITING_STATE; m=MOVED; case(WAITING_STATE): { oldstate=state; m|=FlushSendQueue(); m|=ReceiveResp(); if(state!=oldstate || Error()) return MOVED; // more work to do? if(expect->IsEmpty() && mode==ARRAY_INFO && fileset_for_info->curr()) { SendArrayInfoRequests(); return MOVED; } if(conn->data_iobuf) { if(expect->IsEmpty() && conn->data_sock==-1 && !conn->data_iobuf->Eof()) { conn->data_iobuf->PutEOF(); m=MOVED; } timeout_timer.Reset(conn->data_iobuf->EventTime()); if(conn->data_iobuf->Eof() && conn->data_iobuf->Size()==0) { state=EOF_STATE; DataAbort(); DataClose(); idle_timer.Reset(); eof=true; return MOVED; } } if(copy_mode==COPY_DEST && !copy_allow_store) goto notimeout_return; if(copy_mode==COPY_DEST && !copy_done && copy_connection_open && expect->Count()==1 && use_stat && !conn->ssl_is_activated() && !conn->proxy_is_http) { if(conn->stat_timer.Stopped()) { // send STAT to know current position. SendUrgentCmd("STAT"); expect->Push(Expect::TRANSFER); FlushSendQueue(true); m=MOVED; } } // FXP is special - no data connection at all. if(copy_mode!=COPY_NONE) goto notimeout_return; if(expect->IsEmpty() && !eof && !conn->data_iobuf) { eof=true; m=MOVED; } goto usual_return; } case WAITING_CCC_SHUTDOWN: if(conn->control_recv->Error()) { if(conn->control_recv->ErrorFatal()) SetError(FATAL,conn->control_recv->ErrorText()); Disconnect(conn->control_recv->ErrorText()); return MOVED; } if(conn->control_recv->Eof() || conn->waiting_ssl_timer.Stopped()) { conn->MakeBuffers(); goto pre_EOF_STATE; } break; } /* end of switch */ usual_return: if(m==MOVED) return MOVED; if(conn && CheckTimeout()) return MOVED; notimeout_return: if(m==MOVED) return MOVED; if(conn && conn->data_sock!=-1) { if(state==ACCEPTING_STATE) Block(conn->data_sock,POLLIN); else if(state==DATASOCKET_CONNECTING_STATE) { if(pasv_state==PASV_DATASOCKET_CONNECTING) Block(conn->data_sock,POLLOUT); } } if(conn && conn->control_sock!=-1) { if(state==CONNECTING_STATE) Block(conn->control_sock,POLLOUT); } return m; system_error: assert(saved_errno!=0); if(NonFatalError(saved_errno)) { TimeoutS(1); return m; } DisconnectNow(); SetError(SEE_ERRNO,0); return MOVED; } #if USE_SSL void Ftp::SendAuth(const char *auth) { if(conn->auth_sent || conn->ssl_is_activated()) return; if(!conn->auth_supported) { if(QueryBool("ssl-force",hostname)) SetError(LOGIN_FAILED,_("ftp:ssl-force is set and server does not support or allow SSL")); return; } if(conn->auth_args_supported) { char *a=alloca_strdup(conn->auth_args_supported); bool saw_ssl=false; bool saw_tls=false; for(a=strtok(a,";"); a; a=strtok(0,";")) { if(!strcasecmp(a,auth)) break; if(!strcasecmp(a,"SSL")) saw_ssl=true; else if(!strcasecmp(a,"TLS")) saw_tls=true; } if(!a) { const char *old_auth=auth; if(saw_tls) auth="TLS"; else if(saw_ssl) auth="SSL"; LogError(1,"AUTH %s is not supported, using AUTH %s instead",old_auth,auth); } } conn->SendCmd2("AUTH",auth); expect->Push(Expect::AUTH_TLS); conn->auth_sent=true; conn->prot='\0'; // send PROT command always, for non-conforming servers } void Ftp::SendPROT(char want_prot) { if(want_prot==conn->prot || !conn->auth_supported) return; conn->SendCmdF("PROT %c",want_prot); expect->Push(new Expect(Expect::PROT,want_prot)); } #endif // USE_SSL void Ftp::SendSiteIdle() { if(!QueryBool("use-site-idle")) return; conn->SendCmd2("SITE IDLE",idle_timer.GetLastSetting().Seconds()); expect->Push(Expect::IGNORE); } void Ftp::SendUTimeRequest() { if(entity_date==NO_DATE || !file) return; char d[15]; time_t n=entity_date; strftime(d,sizeof(d),"%Y%m%d%H%M%S",gmtime(&n)); d[sizeof(d)-1]=0; const char *file_to_append=path_to_send(); if(conn->mfmt_supported) { conn->SendCmd2(xstring::format("MFMT %s",d),file_to_append,url::path_ptr(file_url),home); expect->Push(Expect::IGNORE); } else if(conn->mff_supported) { conn->SendCmd2(xstring::format("MFF modify=%s;",d),file_to_append,url::path_ptr(file_url),home); expect->Push(Expect::IGNORE); } else if(QueryBool("use-site-utime2") && conn->site_utime2_supported) { conn->SendCmd2(xstring::format("SITE UTIME %s",d),file_to_append,url::path_ptr(file_url),home); expect->Push(Expect::SITE_UTIME2); } else if(QueryBool("use-site-utime") && conn->site_utime_supported) { conn->SendCmd(xstring::format("SITE UTIME %s %s %s %s UTC",file_to_append,d,d,d)); expect->Push(Expect::SITE_UTIME); } else if(QueryBool("use-mdtm-overloaded")) { conn->SendCmd2(xstring::format("MDTM %s",d),file_to_append,url::path_ptr(file_url),home); expect->Push(Expect::IGNORE); } } const char *Ftp::QueryStringWithUserAtHost(const char *var) { const char *u=user?user.get():"anonymous"; const char *h=hostname?hostname.get():""; const char *closure=xstring::cat(u,"@",h,NULL); const char *val=Query(var,closure); if(!val || !val[0]) val=Query(var,hostname); if(!val || !val[0]) return 0; return val; } void Ftp::SendAcct() { const char *acct=QueryStringWithUserAtHost("acct"); if(!acct) return; conn->SendCmd2("ACCT",acct); expect->Push(Expect::IGNORE); } void Ftp::SendSiteGroup() { const char *group=QueryStringWithUserAtHost("site-group"); if(!group) return; conn->SendCmd2("SITE GROUP",group); expect->Push(Expect::IGNORE); } void Ftp::SendSiteCommands() { const char *site_commands=QueryStringWithUserAtHost("site"); if(!site_commands) return; char *cmd=alloca_strdup(site_commands); for(;;) { char *sep=strstr(cmd," "); if(sep) *sep=0; conn->SendCmd2("SITE",cmd); expect->Push(Expect::IGNORE); if(!sep) break; cmd=sep+2; } } void Ftp::SendArrayInfoRequests() { for(int i=fileset_for_info->curr_index(); icount(); i++) { FileInfo *fi=(*fileset_for_info)[i]; bool sent=false; if((fi->need&fi->DATE) && conn->mdtm_supported && use_mdtm) { conn->SendCmd2("MDTM",ExpandTildeStatic(fi->name)); expect->Push(Expect::MDTM); sent=true; } if((fi->need&fi->SIZE) && conn->size_supported && use_size) { conn->SendCmd2("SIZE",ExpandTildeStatic(fi->name)); expect->Push(Expect::SIZE); sent=true; } if(!sent) { if(i==fileset_for_info->curr_index()) fileset_for_info->next(); // if it is the first one, just skip it. else break; // otherwise, wait until it is the first. } else { if(GetFlag(SYNC_MODE)) break; // don't flood the queues. } } } int Ftp::ReplyLogPriority(int code) const { // Greeting messages if(code==220 || code==230) return 3; if(code==250 && mode==CHANGE_DIR) return 3; if(code==451 && mode==CLOSED) return 4; /* Most 5XXs go to level 4, as it's the job's responsibility to * print fatal errors. Some 5XXs are treated as 4XX's; send those * to level 0. (Maybe they should go to 1; we're going to retry them, * after all. */ if(is5XX(code)) return Transient5XX(code)? 0:4; if(is4XX(code)) return 0; // 221 is the reply to QUIT, but we don't expect it. if(code==221 && !conn->quit_sent) return 0; return 4; } int Ftp::ReceiveOneLine() { const char *resp; int resp_size; conn->control_recv->Get(&resp,&resp_size); if(resp==0) // eof { if(!conn->quit_sent) LogError(0,_("Peer closed connection")); DisconnectNow(); return -1; } if(resp_size==0) return 0; int line_len=0; int skip_len=0; // find pair const char *nl=find_char(resp,resp_size,'\n'); for(;;) { if(!nl) { if(conn->control_recv->Eof()) { skip_len=line_len=resp_size; break; } return 0; } if(nl>resp && nl[-1]=='\r') { line_len=nl-resp-1; skip_len=nl-resp+1; break; } if(nl==resp+resp_size-1 && now-conn->control_recv->EventTime()>5) { LogError(1,"server bug: single "); nl=find_char(resp,resp_size,'\n'); line_len=nl-resp; skip_len=nl-resp+1; break; } nl=find_char(nl+1,resp_size-(nl+1-resp),'\n'); } line.nset(resp,line_len); conn->control_recv->Skip(skip_len); // Change to according to RFC2640. // Other occurencies of are changed to '!'. char *w=line.get_non_const(); const char *r=w; for(int i=line.length(); i>0; i--,r++) { if(*r) *w++=*r; else if(r==line || r[-1]!='\r') *w++='!'; } line.truncate(line.length()-(r-w)); return line.length(); } int Ftp::ReceiveResp() { int m=STALL; if(!conn || !conn->control_recv) return m; timeout_timer.Reset(conn->control_recv->EventTime()); if(conn->control_recv->Error()) { LogError(0,"%s",conn->control_recv->ErrorText()); if(conn->control_recv->ErrorFatal()) SetError(FATAL,conn->control_recv->ErrorText()); DisconnectNow(); return MOVED; } for(;;) // handle all lines in buffer, one line per loop { if(!conn || !conn->control_recv) return m; int res=ReceiveOneLine(); if(res==-1) return MOVED; if(res==0) return m; int code=0; if(line.length()>=3 && is_ascii_digit(line[0]) && is_ascii_digit(line[1]) && is_ascii_digit(line[2])) sscanf(line,"%3d",&code); if(conn->multiline_code && conn->multiline_code!=code && QueryBool("ftp:strict-multiline",closure)) code=0; // reply can only terminate with the same code int log_prio=ReplyLogPriority(conn->multiline_code?conn->multiline_code:code); bool is_first_line=(line[3]=='-' && conn->multiline_code==0); bool is_last_line=(line[3]!='-' && code!=0); bool is_data=(!expect->IsEmpty() && expect->FirstIs(Expect::QUOTED) && conn->data_iobuf); int data_offset=0; if(is_data && mode==LONG_LIST) { if(code && !is2XX(code)) is_data=false; if(code && line.length()>4) { data_offset=4; if(is_first_line && strstr(line+data_offset,"FTP server status")) { TurnOffStatForList(); is_data=false; } if((is_first_line && !strncasecmp(line+data_offset,"Stat",4)) || (is_last_line && !strncasecmp(line+data_offset,"End",3))) is_data=false; } } if(is_data && conn->data_iobuf) { if(line[data_offset]==' ') data_offset++; conn->data_iobuf->Put(line+data_offset,line.length()-data_offset); conn->data_iobuf->Put("\n"); log_prio=10; } LogRecv(log_prio,line); if(conn->multiline_code==0 || all_lines.length()==0) all_lines.set(line); // not continuation else if(all_lines.length()<0x4000) all_lines.vappend("\n",line.get(),NULL); if(code==0) continue; if(line[3]=='-') { if(conn->multiline_code==0) conn->multiline_code=code; continue; } if(conn->multiline_code && line[3]!=' ') continue; // The space is required to terminate multiline reply conn->multiline_code=0; if(!is1XX(code)) { if(conn->sync_wait>0) conn->sync_wait--; // clear the flag to send next command else { if(code!=421) { LogError(3,_("extra server response")); return m; } } } CheckResp(code); m=MOVED; if(error_code==NO_FILE || error_code==LOGIN_FAILED) { if(error_code==LOGIN_FAILED) reconnect_timer.Reset(); // count the reconnect-interval from this moment if(persist_retries++& buf) { if(!proxy_user || !proxy_pass) return; xstring& auth=xstring::cat(proxy_user.get(),":",proxy_pass.get(),NULL); int auth_len=auth.length(); char *buf64=string_alloca(base64_length(auth_len)+1); base64_encode(auth,buf64,auth_len); buf->Format("Proxy-Authorization: Basic %s\r\n",buf64); Log::global->Format(4,"+--> Proxy-Authorization: Basic %s\r\n",buf64); } void Ftp::HttpProxySendConnect() { const char *the_port=portname?portname.get():ftps?FTPS_DEFAULT_PORT:FTP_DEFAULT_PORT; conn->control_send->Format("CONNECT %s:%s HTTP/1.0\r\n",hostname.get(),the_port); Log::global->Format(4,"+--> CONNECT %s:%s HTTP/1.0\n",hostname.get(),the_port); HttpProxySendAuth(conn->control_send); conn->control_send->Put("\r\n"); http_proxy_status_code=0; } void Ftp::HttpProxySendConnectData() { const char *the_host=SocketNumericAddress(&conn->data_sa); int the_port=SocketPort(&conn->data_sa); conn->data_iobuf->Format("CONNECT %s:%d HTTP/1.0\r\n",the_host,the_port); Log::global->Format(4,"+--> CONNECT %s:%d HTTP/1.0\n",the_host,the_port); HttpProxySendAuth(conn->data_iobuf); conn->data_iobuf->Put("\r\n"); http_proxy_status_code=0; } // Check reply and return true when the reply is received and is ok. bool Ftp::HttpProxyReplyCheck(const SMTaskRef& buf) { const char *b; int s; buf->Get(&b,&s); const char *nl=b?(const char*)memchr(b,'\n',s):0; if(!nl) { if(buf->Error()) { LogError(0,"%s",buf->ErrorText()); if(buf->ErrorFatal()) SetError(FATAL,buf->ErrorText()); } else if(buf->Eof()) LogError(0,_("Peer closed connection")); if(conn && (buf->Eof() || buf->Error())) DisconnectNow(); return false; } char *line=string_alloca(nl-b); memcpy(line,b,nl-b-1); // don't copy \r line[nl-b-1]=0; buf->Skip(nl-b+1); // skip \r\n too. Log::global->Format(4,"<--+ %s\n",line); if(!http_proxy_status_code) { if(1!=sscanf(line,"HTTP/%*d.%*d %d",&http_proxy_status_code) || !is2XX(http_proxy_status_code)) { // check for retriable codes if(http_proxy_status_code==408 // Request Timeout || http_proxy_status_code==502 // Bad Gateway || http_proxy_status_code==503 // Service Unavailable || http_proxy_status_code==504)// Gateway Timeout { DisconnectNow(); return false; } SetError(FATAL,line); return false; } } if(!*line) return true; return false; } void Ftp::SendUrgentCmd(const char *cmd) { if(!use_telnet_iac || !conn->telnet_layer_send) { conn->SendCmd(cmd); return; } static const char pre_cmd[]={TELNET_IAC,TELNET_IP,TELNET_IAC,TELNET_DM}; #if USE_SSL if(conn->ssl_is_activated()) { // no way to send urgent data over ssl, send normally. conn->telnet_layer_send->Buffer::Put(pre_cmd,4); } else // note the following block #endif { int fl=fcntl(conn->control_sock,F_GETFL); fcntl(conn->control_sock,F_SETFL,fl&~O_NONBLOCK); FlushSendQueue(/*all=*/true); if(!conn || !conn->control_send) return; if(conn->control_send->Size()>0) conn->control_send->Roll(); // only DM byte is to be sent in urgent mode send(conn->control_sock,pre_cmd,3,0); send(conn->control_sock,pre_cmd+3,1,MSG_OOB); fcntl(conn->control_sock,F_SETFL,fl); } conn->SendCmd(cmd); } void Ftp::DataAbort() { if(!conn || state==CONNECTING_STATE || conn->quit_sent) return; if(conn->data_sock==-1 && copy_mode==COPY_NONE) return; // nothing to abort if(copy_mode!=COPY_NONE) { if(expect->IsEmpty()) return; // the transfer seems to be finished if(!copy_addr_valid) return; // data connection cannot be established at this time if(!copy_connection_open && expect->FirstIs(Expect::TRANSFER)) { // wu-ftpd-2.6.0 cannot interrupt accept() or connect(). DisconnectNow(); return; } } copy_connection_open=false; // if transfer has been completed then ABOR is not needed if(conn->data_sock!=-1 && expect->IsEmpty()) return; expect->Close(); if(!QueryBool("use-abor",hostname) || expect->Count()>1 || conn->proxy_is_http) { // check that we have a data socket to close, and the server is not // in uninterruptible accept() state. if(copy_mode==COPY_NONE && !(GetFlag(PASSIVE_MODE) && state==DATASOCKET_CONNECTING_STATE && (pasv_state==PASV_NO_ADDRESS_YET || pasv_state==PASV_HAVE_ADDRESS))) DataClose(); // just close data connection else { // otherwise, just close control connection. DisconnectNow(); } return; } if(conn->aborted_data_sock!=-1) // don't allow double ABOR. { DisconnectNow(); return; } SendUrgentCmd("ABOR"); expect->Push(Expect::ABOR); FlushSendQueue(true); conn->abor_close_timer.Reset(); // don't close it now, wait for ABOR result conn->AbortDataConnection(); // ABOR over SSL connection does not always work, // closing data socket should help it. if(conn->ssl_is_activated()) conn->CloseAbortedDataConnection(); if(QueryBool("web-mode")) Disconnect(); } void Ftp::ControlClose() { if(conn && conn->control_send) conn->control_send->PutEOF(); conn=0; expect=0; } void Ftp::DisconnectNow() { DataClose(); ControlClose(); state=INITIAL_STATE; http_proxy_status_code=0; if(copy_mode!=COPY_NONE) { if(copy_addr_valid) copy_failed=true; } else { if(mode==STORE && GetFlag(IO_FLAG)) SetError(STORE_FAILED,0); } copy_addr_valid=false; } void Ftp::DisconnectLL() { if(!conn) return; if(conn->quit_sent) return; /* protect against re-entering from FlushSendQueue */ static bool disconnect_in_progress=false; if(disconnect_in_progress) return; disconnect_in_progress=true; bool no_greeting=(!expect->IsEmpty() && expect->FirstIs(Expect::READY)); expect->Close(); DataAbort(); DataClose(); if(conn && state!=CONNECTING_STATE && state!=HTTP_PROXY_CONNECTED && expect->Count()<2 && QueryBool("use-quit",hostname)) { conn->SendCmd("QUIT"); expect->Push(Expect::IGNORE); conn->quit_sent=true; goto out; } ControlClose(); if(state==CONNECTING_STATE || no_greeting) NextPeer(); DisconnectNow(); out: disconnect_on_close=false; Timeout(0); disconnect_in_progress=false; } void Ftp::Connection::CloseDataSocket() { if(data_sock==-1) return; LogNote(7,_("Closing data socket")); close(data_sock); data_sock=-1; } void Ftp::Connection::CloseDataConnection() { data_iobuf=0; fixed_pasv=false; CloseDataSocket(); } void Ftp::Connection::AbortDataConnection() { CloseAbortedDataConnection(); aborted_data_sock=data_sock; data_sock=-1; CloseDataConnection(); // clean up all other members. } void Ftp::Connection::CloseAbortedDataConnection() { if(aborted_data_sock!=-1) { LogNote(9,_("Closing aborted data socket")); close(aborted_data_sock); aborted_data_sock=-1; } } void Ftp::DataClose() { rate_limit=0; if(!conn) return; conn->nop_time=0; conn->nop_offset=0; conn->nop_count=0; if(conn->data_sock!=-1 && QueryBool("web-mode")) disconnect_on_close=true; conn->CloseDataConnection(); if(state==DATA_OPEN_STATE || state==DATASOCKET_CONNECTING_STATE) state=WAITING_STATE; } int Ftp::Connection::FlushSendQueueOneCmd() { const char *send_cmd_ptr; int send_cmd_count; send_cmd_buffer.Get(&send_cmd_ptr,&send_cmd_count); if(send_cmd_count==0) return 0; const char *cmd_begin=send_cmd_ptr; const char *line_end=(const char*)memchr(send_cmd_ptr,'\n',send_cmd_count); if(!line_end) return 0; int to_write=line_end+1-send_cmd_ptr; control_send->Put(send_cmd_ptr,to_write); send_cmd_buffer.Skip(to_write); sync_wait++; int log_level=5; if(!may_show_password && !strncasecmp(cmd_begin,"PASS ",5)) LogSend(log_level,"PASS XXXX"); else { xstring log; for(const char *s=cmd_begin; s<=line_end; s++) { if(*s==0) log.append(""); else if(*s==TELNET_IAC && telnet_layer_send) { s++; if(*s==TELNET_IAC) log.append('\377'); else if(*s==TELNET_IP) log.append(""); else if(*s==TELNET_DM) log.append(""); } else log.append(*s?*s:'!'); } LogSend(log_level,log); } return 1; } int Ftp::FlushSendQueue(bool all) { int m=STALL; if(!conn || !conn->control_send) return m; if(conn->control_send->Error()) { LogError(0,"%s",conn->control_send->ErrorText()); if(conn->control_send->ErrorFatal()) { #if USE_SSL if(conn->ssl_is_activated() && !ftps && !QueryBool("ssl-force",hostname) && !conn->control_ssl->cert_error) { // retry without ssl ResMgr::Set("ftp:ssl-allow",hostname,"no"); DontSleep(); } else #endif SetError(FATAL,conn->control_send->ErrorText()); } DisconnectNow(); return MOVED; } if(conn->send_cmd_buffer.Size()==0) return m; while(conn->sync_wait<=0 || all || !GetFlag(SYNC_MODE)) { int res=conn->FlushSendQueueOneCmd(); if(!res) break; m|=MOVED; } if(m==MOVED) conn->control_send->Roll(); timeout_timer.Reset(conn->control_send->EventTime()); return m; } void Ftp::Connection::Send(const char *buf) { while(*buf) { char ch=*buf++; send_cmd_buffer.Put(&ch,1); if(ch=='\r') send_cmd_buffer.PutRaw("",1); // RFC2640 } } void Ftp::Connection::SendEncoded(const char *buf) { while(*buf) { char ch=*buf++; if(ch=='%' && isxdigit((unsigned char)buf[0]) && isxdigit((unsigned char)buf[1])) { int n=0; if(sscanf(buf,"%2x",&n)==1) { buf+=2; ch=n; // don't translate encoded bytes send_cmd_buffer.PutRaw(&ch,1); send_cmd_buffer.ResetTranslation(); goto next; } } send_cmd_buffer.Put(&ch,1); next: if(ch=='\r') send_cmd_buffer.PutRaw("",1); // RFC2640 } } void Ftp::Connection::SendCRNL() { send_cmd_buffer.PutRaw("\r\n",2); send_cmd_buffer.ResetTranslation(); } void Ftp::Connection::SendCmd(const char *cmd) { Send(cmd); SendCRNL(); } void Ftp::Connection::SendURI(const char *u,const char *home) { if(u[0]=='/' && u[1]=='~') u++; else if(!strncasecmp(u,"/%2F",4)) { Send("/"); u+=4; } else if(home && strcmp(home,"/")) Send(home); SendEncoded(u); } void Ftp::Connection::SendCmd2(const char *cmd,const char *f,const char *u,const char *home) { if(cmd && cmd[0]) { Send(cmd); send_cmd_buffer.Put(" ",1); } if(u) SendURI(u,home); else Send(f); SendCRNL(); } void Ftp::Connection::SendCmd2(const char *cmd,int v) { char buf[32]; snprintf(buf,sizeof(buf),"%d",v); SendCmd2(cmd,buf); } void Ftp::Connection::SendCmdF(const char *f,...) { va_list v; va_start(v,f); xstring& s=xstring::vformat(f,v); va_end(v); SendCmd(s); } void Ftp::Connection::AddDataTranslator(DataTranslator *t) { if(data_iobuf->GetTranslator()) data_iobuf=new IOBufferStacked(data_iobuf.borrow()); data_iobuf->SetTranslator(t); } void Ftp::Connection::AddDataTranslation(const char *charset,bool translit) { #ifdef HAVE_ICONV if(data_iobuf->GetTranslator()) data_iobuf=new IOBufferStacked(data_iobuf.borrow()); data_iobuf->SetTranslation(charset,translit); #endif } int Ftp::SendEOT() { if(mode!=STORE) return(OK); /* nothing to do */ if(state!=DATA_OPEN_STATE) return(DO_AGAIN); if(!conn->data_iobuf->Eof()) conn->data_iobuf->PutEOF(); if(!conn->data_iobuf->Done()) return(DO_AGAIN); DataClose(); state=WAITING_STATE; return(OK); } void Ftp::Close() { if(mode!=CLOSED) idle_timer.Reset(); flags&=~NOREST_MODE; // can depend on a particular file eof=false; Resume(); ExpandTildeInCWD(); DataAbort(); DataClose(); if(conn) { expect->Close(); switch(state) { case(CONNECTING_STATE): case(HTTP_PROXY_CONNECTED): case(CONNECTED_STATE): case(USER_RESP_WAITING_STATE): Disconnect(); break; case(ACCEPTING_STATE): case(DATASOCKET_CONNECTING_STATE): case(CWD_CWD_WAITING_STATE): case(WAITING_STATE): case(DATA_OPEN_STATE): case(WAITING_150_STATE): state=EOF_STATE; break; case(INITIAL_STATE): case(EOF_STATE): case(WAITING_CCC_SHUTDOWN): break; } } else { state=INITIAL_STATE; } copy_mode=COPY_NONE; copy_protect=false; copy_ssl_connect=false; copy_addr_valid=false; copy_done=false; copy_connection_open=false; copy_allow_store=false; copy_failed=false; super::Close(); if(disconnect_on_close) Disconnect(); } Ftp::ExpectQueue::ExpectQueue() { first=0; last=&first; count=0; } Ftp::ExpectQueue::~ExpectQueue() { while(first) delete Pop(); } void Ftp::ExpectQueue::Push(Expect *e) { *last=e; last=&e->next; e->next=0; count++; } void Ftp::ExpectQueue::Push(Expect::expect_t e) { Push(new Expect(e)); } Ftp::Expect *Ftp::ExpectQueue::Pop() { if(!first) return 0; Expect *res=first; first=first->next; if(last==&res->next) last=&first; res->next=0; count--; return res; } bool Ftp::ExpectQueue::Has(Expect::expect_t cc) const { for(const Expect *scan=first; scan; scan=scan->next) if(cc==scan->check_case) return true; return false; } bool Ftp::ExpectQueue::FirstIs(Expect::expect_t cc) const { if(first && first->check_case==cc) return true; return false; } void Ftp::ExpectQueue::Close() { for(Expect *scan=first; scan; scan=scan->next) { switch(scan->check_case) { case(Expect::IGNORE): case(Expect::PWD): case(Expect::USER): case(Expect::USER_PROXY): case(Expect::PASS): case(Expect::PASS_PROXY): case(Expect::OPEN_PROXY): case(Expect::ACCT_PROXY): case(Expect::READY): case(Expect::ABOR): case(Expect::CWD_STALE): case(Expect::PRET): case(Expect::PASV): case(Expect::EPSV): case(Expect::TRANSFER_CLOSED): case(Expect::FEAT): case(Expect::SITE_UTIME): case(Expect::SITE_UTIME2): case(Expect::TYPE): case(Expect::MODE): case(Expect::LANG): case(Expect::OPTS_UTF8): case(Expect::ALLO): #if USE_SSL case(Expect::AUTH_TLS): case(Expect::PROT): case(Expect::SSCN): case(Expect::CCC): #endif break; case(Expect::CWD_CURR): case(Expect::CWD): scan->check_case=Expect::CWD_STALE; break; case(Expect::NONE): case(Expect::REST): case(Expect::SIZE): case(Expect::SIZE_OPT): case(Expect::MDTM): case(Expect::MDTM_OPT): case(Expect::PORT): case(Expect::FILE_ACCESS): case(Expect::RNFR): case(Expect::QUOTED): scan->check_case=Expect::IGNORE; break; case(Expect::TRANSFER): scan->check_case=Expect::TRANSFER_CLOSED; break; } } } Ftp::Expect *Ftp::ExpectQueue::FindLastCWD() const { Expect *last_cwd=0; for(Expect *scan=first; scan; scan=scan->next) { switch(scan->check_case) { case(Expect::CWD_CURR): case(Expect::CWD_STALE): case(Expect::CWD): last_cwd=scan; default: ; } } return last_cwd; } bool Ftp::IOReady() { if(copy_mode!=COPY_NONE && !copy_passive && !copy_addr_valid) return true; // simulate to be ready as other fxp peer has to go if(Error()) return true; // report ready to propagate the error. return (state==DATA_OPEN_STATE || state==WAITING_STATE) && real_pos!=-1 && IsOpen(); } void Ftp::SuspendInternal() { super::SuspendInternal(); if(conn) conn->SuspendInternal(); } void Ftp::ResumeInternal() { if(conn) conn->ResumeInternal(); super::ResumeInternal(); } int Ftp::CanRead() { if(Error()) return(error_code); if(mode==CLOSED || eof) return(0); if(!conn || !conn->data_iobuf) return DO_AGAIN; if(expect->Has(Expect::REST) && real_pos==-1) return DO_AGAIN; if(state==DATASOCKET_CONNECTING_STATE) return DO_AGAIN; int size=conn->data_iobuf->Size(); if(state==DATA_OPEN_STATE) { assert(rate_limit!=0); int allowed=rate_limit->BytesAllowedToGet(); if(allowed==0) return DO_AGAIN; if(size>allowed) size=allowed; } if(norest_manual && real_pos==0 && pos>0) return DO_AGAIN; if(size==0) return DO_AGAIN; return size; } int Ftp::Read(Buffer *buf,int size) { int size1=CanRead(); if(size1<=0) return size1; if(size>size1) size=size1; int skip=0; if(real_pos+size0) { conn->data_iobuf->Skip(skip); rate_limit->BytesGot(skip); real_pos+=skip; size-=skip; if(size<=0) return DO_AGAIN; } assert(real_pos==pos); size=buf->MoveDataHere(conn->data_iobuf,size); if(size<=0) return DO_AGAIN; rate_limit->BytesGot(size); real_pos+=size; pos+=size; TrySuccess(); flags|=IO_FLAG; return(size); } /* Write - send data to ftp server * Uploading is not reliable in this realization * Well, not less reliable than in any usual ftp client. The reason for this is uncheckable receiving of data on the remote end. Since that, we have to leave re-putting up to caller. Fortunately, class FileCopy does it. */ int Ftp::Write(const void *buf,int size) { if(mode!=STORE) return(0); if(Error()) return(error_code); if(!conn || state!=DATA_OPEN_STATE || (expect->Has(Expect::REST) && real_pos==-1)) return DO_AGAIN; if(!conn->data_iobuf) return DO_AGAIN; { assert(rate_limit!=0); int allowed=rate_limit->BytesAllowedToPut(); if(allowed==0) return DO_AGAIN; if(size>allowed) size=allowed; } if(size+conn->data_iobuf->Size()>=max_buf) size=max_buf-conn->data_iobuf->Size(); if(size<=0) return 0; conn->data_iobuf->Put((const char*)buf,size); if(retries+persist_retries>0 && conn->data_iobuf->GetPos()>Buffered()+0x20000) { // reset retry count if some data were actually written to server. LogNote(10,"resetting retry count"); TrySuccess(); } assert(rate_limit!=0); rate_limit->BytesPut(size); pos+=size; real_pos+=size; flags|=IO_FLAG; return(size); } int Ftp::StoreStatus() { if(Error()) return(error_code); if(mode!=STORE) return(OK); if(state==DATA_OPEN_STATE) { // have not send EOT by SendEOT, do it now SendEOT(); } if(state==WAITING_STATE && expect->IsEmpty()) { eof=true; return(OK); } return(IN_PROGRESS); } void Ftp::MoveConnectionHere(Ftp *o) { expect=o->expect.borrow(); expect->Close(); // we need not handle other session's replies. assert(o->conn->data_iobuf==0); conn=o->conn.borrow(); conn->ResumeInternal(); o->state=INITIAL_STATE; line.move_here(o->line); all_lines.move_here(o->all_lines); if(peer_curr>=peer.count()) peer_curr=0; timeout_timer.Reset(o->timeout_timer); if(!home) set_home(home_auto); set_real_cwd(o->real_cwd); o->Disconnect(); state=EOF_STATE; } void Ftp::SendOPTS_MLST() { char *facts=alloca_strdup(conn->mlst_attr_supported); char *store=facts; bool differs=false; for(char *tok=strtok(facts,";"); tok; tok=strtok(0,";")) { bool was_enabled=false; bool want_enable=false; int len=strlen(tok); if(len>0 && tok[len-1]=='*') { was_enabled=true; tok[--len]=0; } // "unique" not needed yet. static const char *const needed[]={ "type","size","modify","perm", "UNIX.mode","UNIX.owner","UNIX.uid","UNIX.group","UNIX.gid", 0}; for(const char *const *scan=needed; *scan; scan++) { if(!strcasecmp(tok,*scan)) { memmove(store,tok,len); store+=len; *store++=';'; want_enable=true; break; } } differs|=(was_enabled^want_enable); } if(store>facts && store[-1]==';') --store; if(!differs || store==facts) return; *store=0; conn->SendCmd2("OPTS MLST",facts); expect->Push(Expect::IGNORE); } void Ftp::TuneConnectionAfterFEAT() { if(conn->clnt_supported) { const char *client=Query("client",hostname); if(client && client[0]) { conn->SendCmd2("CLNT",client); expect->Push(Expect::IGNORE); } } if(conn->lang_supported) { const char *lang_to_use=Query("lang",hostname); if(lang_to_use && lang_to_use[0]) conn->SendCmd2("LANG",lang_to_use); else conn->SendCmd("LANG"); expect->Push(Expect::LANG); } if(conn->utf8_supported && QueryBool("use-utf8",hostname)) { // some non-RFC2640 servers require this command. conn->SendCmd("OPTS UTF8 ON"); expect->Push(Expect::OPTS_UTF8); } if(conn->host_supported) { conn->SendCmd2("HOST",hostname); expect->Push(Expect::IGNORE); } if(conn->try_feat_after_login && conn->mlst_attr_supported) SendOPTS_MLST(); if(proxy) conn->epsv_supported=false; } void Ftp::Connection::CheckFEAT(char *reply,const char *line,bool trust) { if(trust) { // turn off these pre-FEAT extensions only when trusting FEAT reply, // as some servers forget to advertise them. mdtm_supported=false; size_supported=false; rest_supported=false; tvfs_supported=false; } #if USE_SSL auth_supported=false; auth_args_supported.set(0); cpsv_supported=false; sscn_supported=false; #endif pret_supported=false; epsv_supported=false; char *scan=strchr(reply,'\n'); if(scan) scan++; if(!scan || !*scan) return; for(char *f=strtok(scan,"\r\n"); f; f=strtok(0,"\r\n")) { if(!strncmp(f,line,3)) { if(f[3]==' ') break; // last line if(f[3]=='-') f+=4; // workaround for broken servers, RFC2389 does not allow it. } while(*f==' ') f++; if(!strcasecmp(f,"UTF8")) utf8_supported=true; else if(!strncasecmp(f,"LANG ",5)) lang_supported=true; else if(!strcasecmp(f,"PRET")) pret_supported=true; else if(!strcasecmp(f,"MDTM")) mdtm_supported=true; else if(!strcasecmp(f,"SIZE")) size_supported=true; else if(!strcasecmp(f,"CLNT") || !strncasecmp(f,"CLNT ",5)) clnt_supported=true; else if(!strcasecmp(f,"HOST")) host_supported=true; else if(!strcasecmp(f,"MFMT")) mfmt_supported=true; else if(!strcasecmp(f,"MFF")) mff_supported=true; else if(!strncasecmp(f,"REST ",5)) // FIXME: actually REST STREAM rest_supported=true; else if(!strcasecmp(f,"REST")) rest_supported=true; else if(!strncasecmp(f,"MLST ",5)) { mlst_supported=true; mlst_attr_supported.set(f+5); } else if(!strcasecmp(f,"EPSV")) epsv_supported=true; else if(!strcasecmp(f,"TVFS")) tvfs_supported=true; else if(!strncasecmp(f,"MODE Z",6)) { mode_z_supported=true; mode_z_opts_supported.set(f[6]==' '?f+7:NULL); } else if(!strcasecmp(f,"SITE SYMLINK")) site_symlink_supported=true; else if(!strcasecmp(f,"SITE MKDIR")) site_mkdir_supported=true; #if USE_SSL else if(!strncasecmp(f,"AUTH ",5)) { auth_supported=true; if(auth_args_supported) auth_args_supported.vappend(";",f+5,NULL); else auth_args_supported.append(f+5); } else if(!strcasecmp(f,"AUTH")) auth_supported=true; else if(!strcasecmp(f,"CPSV")) cpsv_supported=true; else if(!strcasecmp(f,"SSCN")) sscn_supported=true; #endif // USE_SSL } if(!trust) { // turn on EPSV support based on some other modern features epsv_supported|=mlst_supported|host_supported; #if USE_SSL // same for AUTH auth_supported|=epsv_supported; #endif } have_feat_info=true; } void Ftp::TurnOffStatForList() { DataClose(); expect->Close(); state=EOF_STATE; LogNote(2,"Setting ftp:use-stat-for-list to off"); ResMgr::Set("ftp:use-stat-for-list",hostname,"off"); use_stat_for_list=false; } void Ftp::CheckResp(int act) { // close aborted accepting data socket when the connection is established if(is1XX(act) && GetFlag(PASSIVE_MODE) && conn->aborted_data_sock!=-1) conn->CloseAbortedDataConnection(); if(is1XX(act) && expect->FirstIs(Expect::TRANSFER)) { // allow data transfer conn->received_150=true; if(state==WAITING_STATE) { // set the FXP flag copy_connection_open=true; conn->stat_timer.ResetDelayed(2); } if(mode==RETRIEVE && entity_size<0 && QueryBool("catch-size",hostname)) { // try to catch size const char *s=strrchr(line,'('); if(s && is_ascii_digit(s[1])) { long long size_ll; int n; if(1<=sscanf(s+1,"%lld%n",&size_ll,&n) && !strncmp(s+1+n," bytes",6)) { entity_size=size_ll; if(opt_size) *opt_size=entity_size; LogNote(7,_("saw file size in response")); } } } } if(is1XX(act)) // intermediate responses are ignored return; if(act==421) { // server is going to disconnect: // don't try sending QUIT. conn->quit_sent=true; // adjust connection limit const char *rexp=Query("too-many-re",hostname); if(re_match(line,rexp,REG_ICASE)) { SiteData *data=GetSiteData(); if(data->GetConnectionLimit()==0) data->SetConnectionLimit(CountConnections()); data->DecreaseConnectionLimit(); } } Expect *exp=expect->Pop(); if(!exp) { if(act!=421) LogError(3,_("extra server response")); if(is2XX(act)) // some buggy servers send several 226 replies return; Disconnect(line); return; } Expect::expect_t cc=exp->check_case; const char *arg=exp->arg; // some servers mess all up if(act==331 && cc==Expect::READY && !GetFlag(SYNC_MODE) && expect->Count()>1) { delete expect->Pop(); LogNote(2,_("Turning on sync-mode")); ResMgr::Set("ftp:sync-mode",hostname,"on"); Disconnect(); DontSleep(); // retry immediately goto leave; } switch(cc) { case Expect::NONE: if(is2XX(act)) // default rule. break; Disconnect(line); break; case Expect::QUOTED: if(mode==LONG_LIST && !is2XX(act) && use_stat_for_list) TurnOffStatForList(); break; case Expect::IGNORE: ignore: break; case Expect::READY: case Expect::OPEN_PROXY: if(!GetFlag(SYNC_MODE) && re_match(all_lines,Query("auto-sync-mode",hostname))) { LogNote(2,_("Turning on sync-mode")); ResMgr::Set("ftp:sync-mode",hostname,"on"); assert(GetFlag(SYNC_MODE)); Disconnect(); DontSleep(); // retry immediately } if(!is2XX(act)) { Disconnect(line); if(cc==Expect::OPEN_PROXY && act==403) { SetError(LOGIN_FAILED,all_lines); break; } NextPeer(); if(peer_curr==0) reconnect_timer.Reset(); // count the reconnect-interval from this moment last_connection_failed=true; } break; case Expect::REST: RestCheck(act); break; case Expect::CWD: case Expect::CWD_CURR: if(is2XX(act)) { if(cc==Expect::CWD) cwd.Set(file,false,file_url,device_prefix_len(file)); set_real_cwd(arg); cache->SetDirectory(this, arg, true); break; } if(is5XX(act)) { if(!strcmp(arg,"~")) { // reconnect will change CWD to home directory Disconnect(); DontSleep(); break; } SetError(NO_FILE,all_lines); cache->SetDirectory(this, arg, false); break; } Disconnect(line); break; case Expect::CWD_STALE: if(is2XX(act)) set_real_cwd(arg); goto ignore; case Expect::ABOR: conn->CloseAbortedDataConnection(); goto ignore; case Expect::SIZE: CatchSIZE(act); break; case Expect::SIZE_OPT: CatchSIZE_opt(act); break; case Expect::MDTM: CatchDATE(act); break; case Expect::MDTM_OPT: CatchDATE_opt(act); break; case Expect::FILE_ACCESS: file_access: if(mode==CHANGE_MODE && site_cmd_unsupported(act)) { if(exp->cmd.begins_with("SITE CHMOD")) conn->site_chmod_supported=false; else if(exp->cmd.begins_with("MFF")) conn->mff_supported=false; SetError(NO_FILE,all_lines); break; } if(mode==SYMLINK && site_cmd_unsupported(act)) { if(exp->cmd.begins_with("SITE SYMLINK")) conn->site_symlink_supported=false; SetError(NO_FILE,all_lines); break; } NoFileCheck(act); break; case Expect::PRET: if(cmd_unsupported(act)) { conn->pret_supported=false; break; } if(is5XX(act)) SetError(NO_FILE,all_lines); break; case Expect::ALLO: if(cmd_unsupported(act) || act==202) ResMgr::Set("ftp:use-allo",hostname,"no"); else if(is5XX(act)) SetError(NO_FILE,all_lines); break; case Expect::PASV: case Expect::EPSV: if(is2XX(act)) { if(line.length()<=4) goto passive_off; memset(&conn->data_sa,0,sizeof(conn->data_sa)); if(cc==Expect::PASV) pasv_state=Handle_PASV(); else // cc==Expect::EPSV pasv_state=Handle_EPSV(); if(pasv_state==PASV_NO_ADDRESS_YET) goto passive_off; if(conn->aborted_data_sock!=-1) SocketConnect(conn->aborted_data_sock,&conn->data_sa); break; } if(cmd_unsupported(act) && cc==Expect::EPSV && conn->can_do_pasv && QueryBool("prefer-epsv",hostname)) { ResMgr::Set("ftp:prefer-epsv",hostname,"no"); Disconnect("EPSV failed, will try PASV"); DontSleep(); // retry immediately break; } if(copy_mode!=COPY_NONE) { copy_passive=!copy_passive; copy_failed=true; break; } if(is5XX(act)) { passive_off: if(QueryBool("auto-passive-mode",hostname)) { LogNote(2,_("Switching passive mode off")); SetFlag(PASSIVE_MODE,0); } } Disconnect(line); break; case Expect::PORT: if(is2XX(act)) break; if(copy_mode!=COPY_NONE) { copy_passive=!copy_passive; copy_failed=true; break; } if(is5XX(act)) { if(QueryBool("auto-passive-mode",hostname)) { LogNote(2,_("Switching passive mode on")); SetFlag(PASSIVE_MODE,1); } } Disconnect(line); break; case Expect::PWD: if(is2XX(act)) { if(!home_auto) { home_auto.set_allocated(ExtractPWD()); PropagateHomeAuto(); } if(!home) set_home(home_auto); cache->SetDirectory(this, home, true); break; } break; case Expect::RNFR: if(is3XX(act)) { conn->SendCmd2("RNTO",file1); expect->Push(Expect::FILE_ACCESS); break; } goto file_access; case Expect::USER_PROXY: proxy_NoPassReqCheck(act); break; case Expect::USER: NoPassReqCheck(act); break; case Expect::PASS_PROXY: case Expect::ACCT_PROXY: proxy_LoginCheck(act); break; case Expect::PASS: LoginCheck(act); break; case Expect::TRANSFER: TransferCheck(act); if(mode==STORE && is2XX(act) && entity_size==real_pos) SendUTimeRequest(); break; case Expect::TRANSFER_CLOSED: if(strstr(line,"ABOR") && expect->Count()>=2 && expect->FirstIs(Expect::ABOR)) { LogError(1,"server bug: 426 reply missed"); delete expect->Pop(); conn->CloseAbortedDataConnection(); } break; case Expect::FEAT: if(is2XX(act)) { conn->CheckFEAT(all_lines.get_non_const(),line,QueryBool("trust-feat",hostname)); if(conn->try_feat_after_login && conn->have_feat_info) TuneConnectionAfterFEAT(); } else if(is5XX(act) && !cmd_unsupported(act)) conn->try_feat_after_login=true; break; case Expect::SITE_UTIME: if(site_cmd_unsupported(act)) { conn->site_utime_supported=false; SendUTimeRequest(); // try another method. } break; case Expect::SITE_UTIME2: if(site_cmd_unsupported(act)) { conn->site_utime2_supported=false; SendUTimeRequest(); // try another method. } break; case Expect::TYPE: if(is2XX(act)) conn->type=arg[0]; break; case Expect::MODE: if(is2XX(act)) conn->t_mode=arg[0]; break; case Expect::OPTS_UTF8: case Expect::LANG: if(is2XX(act) && conn->utf8_supported) { conn->utf8_activated=true; conn->SetControlConnectionTranslation("UTF-8"); } else if(is5XX(act) && !cmd_unsupported(act)) conn->tune_after_login=true; break; #if USE_SSL case Expect::AUTH_TLS: if(is2XX(act) || is3XX(act)) { conn->MakeSSLBuffers(hostname); } else { if(QueryBool("ssl-force",hostname)) SetError(LOGIN_FAILED,_("ftp:ssl-force is set and server does not support or allow SSL")); conn->prot='C'; conn->auth_supported=false; } break; case Expect::PROT: if(is2XX(act)) conn->prot=arg[0]; else if(!conn->prot) conn->prot=(ftps?'P':'C'); break; case Expect::SSCN: if(is2XX(act)) conn->sscn_on=(arg[0]=='Y'); else if(cmd_unsupported(act)) conn->sscn_supported=false; break; case Expect::CCC: if(is2XX(act)) { conn->control_send->PutEOF(); state=WAITING_CCC_SHUTDOWN; conn->waiting_ssl_timer.Reset(); } break; #endif // USE_SSL } /* end switch */ leave: delete exp; } const char *Ftp::CurrentStatus() { if(Error()) return StrError(error_code); if(expect && expect->Has(Expect::FEAT)) return _("FEAT negotiation..."); switch(state) { case(EOF_STATE): if(conn && conn->control_sock!=-1) { if(conn->send_cmd_buffer.Size()>0) return(_("Sending commands...")); if(!expect->IsEmpty()) return(_("Waiting for response...")); if(!retry_timer.Stopped()) return _("Delaying before retry"); return(_("Connection idle")); } return(_("Not connected")); case(INITIAL_STATE): if(hostname) { if(resolver) return(_("Resolving host address...")); if(!ReconnectAllowed()) return DelayingMessage(); } return(_("Not connected")); case(CONNECTING_STATE): case(HTTP_PROXY_CONNECTED): return(_("Connecting...")); case(CONNECTED_STATE): #if USE_SSL if(conn->auth_sent) return _("TLS negotiation..."); #endif return _("Connected"); case(USER_RESP_WAITING_STATE): return(_("Logging in...")); case(DATASOCKET_CONNECTING_STATE): if(pasv_state==PASV_NO_ADDRESS_YET) return(_("Waiting for response...")); return(_("Making data connection...")); case(CWD_CWD_WAITING_STATE): if(expect->FindLastCWD()) return(_("Changing remote directory...")); /*fallthrough*/ case(WAITING_STATE): if(copy_mode==COPY_SOURCE) return ""; if(copy_mode==COPY_DEST && expect->IsEmpty()) return _("Waiting for other copy peer..."); if(mode==STORE) return(_("Waiting for transfer to complete")); case(WAITING_150_STATE): return(_("Waiting for response...")); case(WAITING_CCC_SHUTDOWN): return(_("Waiting for TLS shutdown...")); case(ACCEPTING_STATE): return(_("Waiting for data connection...")); case(DATA_OPEN_STATE): #if USE_SSL if(conn->prot=='P') { if(mode==STORE) return(_("Sending data/TLS")); else return(_("Receiving data/TLS")); } #endif if(conn->data_sock!=-1) { if(mode==STORE) return(_("Sending data")); else return(_("Receiving data")); } return(_("Waiting for transfer to complete")); } abort(); } time_t Ftp::ConvertFtpDate(const char *s) { struct tm tm; memset(&tm,0,sizeof(tm)); int year,month,day,hour,minute,second; int skip=0; int n=sscanf(s,"%4d%n",&year,&skip); // try to workaround server's y2k bug // I hope in the next 300 years the y2k bug will be finally fixed :) if(n==1 && year>=1910 && year<=1930) { n=sscanf(s,"%5d%n",&year,&skip); year=year-19100+2000; } if(n!=1) return NO_DATE; n=sscanf(s+skip,"%2d%2d%2d%2d%2d",&month,&day,&hour,&minute,&second); if(n!=5) return NO_DATE; tm.tm_year=year-1900; tm.tm_mon=month-1; tm.tm_mday=day; tm.tm_hour=hour; tm.tm_min=minute; tm.tm_sec=second; return mktime_from_utc(&tm); } void Ftp::SetFlag(int flag,bool val) { flag&=MODES_MASK; // only certain flags can be changed if(val) flags|=flag; else flags&=~flag; } bool Ftp::SameSiteAs(const FileAccess *fa) const { if(!SameProtoAs(fa)) return false; Ftp *o=(Ftp*)fa; return(!xstrcasecmp(hostname,o->hostname) && !xstrcmp(portname,o->portname) && !xstrcmp(user,o->user) && !xstrcmp(pass,o->pass) && ftps==o->ftps); } bool Ftp::SameConnection(const Ftp *o) const { if(!strcasecmp(hostname,o->hostname) && !xstrcmp(portname,o->portname) && !xstrcmp(user,o->user) && !xstrcmp(pass,o->pass) && (user || !xstrcmp(anon_user,o->anon_user)) && (pass || !xstrcmp(anon_pass,o->anon_pass)) && ftps==o->ftps) return true; return false; } bool Ftp::SameLocationAs(const FileAccess *fa) const { if(!SameProtoAs(fa)) return false; Ftp *o=(Ftp*)fa; if(!hostname || !o->hostname) return false; if(SameConnection(o)) { if(home && o->home && strcmp(home,o->home)) return false; return !xstrcmp(cwd,o->cwd); } return false; } int Ftp::Done() { if(Error()) return(error_code); if(mode==CLOSED) return OK; if(mode==ARRAY_INFO) { if(state==WAITING_STATE && expect->IsEmpty() && !fileset_for_info->curr()) return(OK); return(IN_PROGRESS); } if(copy_mode==COPY_DEST && !copy_allow_store) return(IN_PROGRESS); if(mode==CHANGE_DIR || mode==RENAME || mode==MAKE_DIR || mode==REMOVE_DIR || mode==REMOVE || mode==CHANGE_MODE || mode==LINK || mode==SYMLINK || copy_mode!=COPY_NONE) { if(state==WAITING_STATE && expect->IsEmpty()) return(OK); return(IN_PROGRESS); } if(mode==CONNECT_VERIFY) { if(state!=INITIAL_STATE) return OK; return(peer?OK:IN_PROGRESS); } abort(); } void Ftp::ResetLocationData() { super::ResetLocationData(); flags=0; home_auto.set(FindHomeAuto()); Reconfig(); state=INITIAL_STATE; } bool Ftp::AnonymousQuietMode() { if(user && user.ne("anonymous") && user.ne("ftp")) return false; const char *pass_to_use=(pass?pass:anon_pass); return pass_to_use && *pass_to_use=='-'; // minus sign in password means quiet mode } void Ftp::Reconfig(const char *name) { closure.set(hostname); super::Reconfig(name); if(!xstrcmp(name,"net:idle") || !xstrcmp(name,"ftp:use-site-idle")) { if(conn && conn->data_sock==-1 && state==EOF_STATE && !conn->quit_sent) SendSiteIdle(); return; } SetFlag(SYNC_MODE, QueryBool("sync-mode")); if(!conn || !conn->proxy_is_http) SetFlag(PASSIVE_MODE,QueryBool("passive-mode")); rest_list = QueryBool("rest-list"); nop_interval = Query("nop-interval").to_number(1,30); allow_skey = QueryBool("skey-allow"); force_skey = QueryBool("skey-force"); allow_netkey = QueryBool("netkey-allow"); verify_data_address = QueryBool("verify-address"); verify_data_port = QueryBool("verify-port"); use_stat = QueryBool("use-stat"); use_stat_for_list=QueryBool("use-stat-for-list") && !AnonymousQuietMode(); use_mdtm = QueryBool("use-mdtm"); use_size = QueryBool("use-size"); use_pret = QueryBool("use-pret"); use_feat = QueryBool("use-feat"); use_mlsd = QueryBool("use-mlsd"); use_telnet_iac = QueryBool("use-telnet-iac"); max_buf = Query("xfer:buffer-size"); anon_user.set(Query("anon-user")); anon_pass.set(Query("anon-pass")); if(!name || !xstrcmp(name,"ftp:list-options")) { if(name && !IsSuspended()) cache->TreeChanged(this,"/"); list_options.set(Query("list-options")); } if(!name || !xstrcmp(name,"ftp:charset")) { if(name && !IsSuspended()) cache->TreeChanged(this,"/"); charset.set(Query("charset")); if(conn && conn->have_feat_info && !conn->utf8_activated && !(expect->Has(Expect::LANG) || expect->Has(Expect::OPTS_UTF8)) && charset && *charset) conn->SetControlConnectionTranslation(charset); } const char *h=QueryStringWithUserAtHost("home"); if(h && h[0] && AbsolutePath(h)) set_home(h); else set_home(home_auto); if(NoProxy(hostname)) SetProxy(0); else SetProxy(Query("proxy")); if(proxy && proxy_port==0) { if(ProxyIsHttp()) proxy_port.set(HTTP_DEFAULT_PROXY_PORT); else proxy_port.set(FTP_DEFAULT_PORT); } if(conn && conn->control_sock!=-1) SetSocketBuffer(conn->control_sock); if(conn && conn->data_sock!=-1) SetSocketBuffer(conn->data_sock); if(conn && conn->data_iobuf && rate_limit) rate_limit->SetBufferSize(conn->data_iobuf,max_buf); } void Ftp::SetError(int ec,const char *e) { // join multiline error message into single line, removing `550-' prefix. if(e && strchr(e,'\n')) { char *joined=string_alloca(strlen(e)+1); const char *prefix=e; char *store=joined; while(*e) { if(*e=='\n') { if(e[1]) *store++=' '; e++; if(!strncmp(e,prefix,3) && (e[3]=='-' || e[3]==' ')) e+=4; } else { *store++=*e++; } } *store=0; e=joined; } super::SetError(ec,e); switch((status)ec) { case(SEE_ERRNO): case(LOOKUP_ERROR): case(NO_HOST): case(FATAL): case(LOGIN_FAILED): Disconnect(); break; case(IN_PROGRESS): case(OK): case(NOT_OPEN): case(NO_FILE): case(FILE_MOVED): case(STORE_FAILED): case(DO_AGAIN): case(NOT_SUPP): break; } } Ftp::ConnectLevel Ftp::GetConnectLevel() const { if(!conn) return CL_NOT_CONNECTED; if(state==CONNECTING_STATE || state==HTTP_PROXY_CONNECTED) return CL_CONNECTING; if(state==CONNECTED_STATE) return CL_JUST_CONNECTED; if(state==USER_RESP_WAITING_STATE) return CL_NOT_LOGGED_IN; if(conn->quit_sent) return CL_JUST_BEFORE_DISCONNECT; return CL_LOGGED_IN; } ListInfo *Ftp::MakeListInfo(const char *path) { return new FtpListInfo(this,path); } Glob *Ftp::MakeGlob(const char *pattern) { return new GenericGlob(this,pattern); } DirList *Ftp::MakeDirList(ArgV *args) { return new FtpDirList(this,args); } extern "C" const char *calculate_skey_response (int, const char *, const char *); const char *Ftp::make_skey_reply() { static const char * const skey_head[] = { "S/Key MD5 ", "s/key ", "opiekey ", "otp-md5 ", 0 }; const char *cp; for(int i=0; ; i++) { if(skey_head[i]==0) return 0; cp=strstr(all_lines,skey_head[i]); if(cp) { cp+=strlen(skey_head[i]); break; } } LogNote(9,"found s/key substring"); int skey_sequence=0; char *buf=string_alloca(strlen(cp)); if(sscanf(cp,"%d %s",&skey_sequence,buf)!=2 || skey_sequence<1) return 0; return calculate_skey_response(skey_sequence,buf,pass); } extern "C" const char *calculate_netkey_response (const char *, const char *); const char *Ftp::make_netkey_reply() { static const char * const netkey_head[] = { "encrypt challenge, ", 0 }; const char *cp; for(int i=0; ; i++) { if(netkey_head[i]==0) return 0; cp=strstr(all_lines,netkey_head[i]); if(cp) { cp+=strlen(netkey_head[i]); break; } } if(cp) { xstring &buf=xstring::get_tmp(cp); buf.truncate_at(' '); buf.truncate_at(','); LogNote(9,"found netkey challenge %s",buf.get()); return calculate_netkey_response(pass,buf); } return 0; } int Ftp::Buffered() { if(!conn || !conn->data_iobuf) return 0; if(state!=DATA_OPEN_STATE || conn->data_sock==-1 || mode!=STORE) return 0; return conn->data_iobuf->Size()+SocketBuffered(conn->data_sock); } const char *Ftp::ProtocolSubstitution(const char *host) { if(NoProxy(host)) return 0; const char *proxy=ResMgr::Query("ftp:proxy",host); if(proxy && QueryBool("use-hftp",host) && (!strncmp(proxy,"http://",7) || !strncmp(proxy,"https://",8))) return "hftp"; return 0; } #if USE_SSL #undef super #define super Ftp FtpS::FtpS() { ftps=true; res_prefix="ftp"; } FtpS::~FtpS() { } FtpS::FtpS(const FtpS *o) : super(o) { ftps=true; res_prefix="ftp"; Reconfig(); } FileAccess *FtpS::New() { return new FtpS(); } void Ftp::Connection::MakeSSLBuffers(const char *hostname) { control_ssl=new lftp_ssl(control_sock,lftp_ssl::CLIENT,hostname); control_ssl->load_keys(); IOBufferSSL *send_ssl=new IOBufferSSL(control_ssl,IOBufferSSL::PUT); IOBufferSSL *recv_ssl=new IOBufferSSL(control_ssl,IOBufferSSL::GET); control_send=send_ssl; control_recv=recv_ssl; telnet_layer_send=0; } #endif void TelnetEncode::PutTranslated(Buffer *target,const char *put_buf,int size) { size_t put_size=size; const char *iac; while(put_size>0) { iac=(const char*)memchr(put_buf,(unsigned char)TELNET_IAC,put_size); if(!iac) { target->Put(put_buf,put_size); break; } target->Put(put_buf,iac+1-put_buf); put_size-=iac+1-put_buf; put_buf=iac+1; // double the IAC to send it literally. target->Put(iac,1); } } void TelnetDecode::PutTranslated(Buffer *target,const char *put_buf,int size) { if(Size()>0) { Put(put_buf,size); Get(&put_buf,&size); } if(size<=0) return; size_t put_size=size; const char *iac; while(put_size>0) { iac=(const char*)memchr(put_buf,(unsigned char)TELNET_IAC,put_size); if(!iac) break; target->Put(put_buf,iac-put_buf); Skip(iac-put_buf); put_size-=iac-put_buf; put_buf=iac; if(put_size<2) { if(Size()==0) Put(put_buf,put_size); // remember incomplete sequence return; } switch(iac[1]) { // 3-byte commands case TELNET_WILL: case TELNET_WONT: case TELNET_DO: case TELNET_DONT: if(put_size<3) { if(Size()==0) Put(put_buf,put_size); // remember incomplete sequence return; } Skip(3); put_buf+=3; put_size-=3; break; // 2-byte commands case TELNET_IAC: target->Put(iac,1); /*fallthrough*/ default: Skip(2); put_buf+=2; put_size-=2; } } if(put_size>0) { target->Put(put_buf,put_size); Skip(put_size); } } void Ftp::Connection::SetControlConnectionTranslation(const char *cs) { if(translation_activated) return; if(telnet_layer_send) { // cannot do two conversions in one DirectedBuffer, stack it. control_recv=new IOBufferStacked(control_recv.borrow()); } send_cmd_buffer.SetTranslation(cs,false); control_recv->SetTranslation(cs,true); translation_activated=true; } #include "modconfig.h" #ifdef MODULE_PROTO_FTP void module_init() { Ftp::ClassInit(); } #endif