/* * lftp - file transfer program * * Copyright (c) 1996-2016 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 "FileAccess.h" #include #include #include #include #include #include #include #include "ascii_ctype.h" #include #include "LsCache.h" #include "log.h" #include "url.h" #include "misc.h" #include "DummyProto.h" #include "netrc.h" #include "ArgV.h" #include "ConnectionSlot.h" #include "SignalHook.h" #include "FileGlob.h" #ifdef WITH_MODULES # include "module.h" #endif xlist_head FileAccess::all_fa; const FileAccessRef FileAccessRef::null; void FileAccess::Init() { ClassInit(); pass_open=false; default_cwd="~"; cwd.Set(default_cwd,false,0); limit=FILE_END; real_pos=UNKNOWN_POS; pos=0; mode=CLOSED; retries=0; max_retries=0; opt_date=0; opt_size=0; fileset_for_info=0; error_code=OK; saved_errno=0; mkdir_p=false; rename_f=false; ascii=false; norest_manual=false; entity_size=NO_SIZE; entity_date=NO_DATE; res_prefix=0; chmod_mode=0644; priority=last_priority=0; all_fa.add(all_fa_node); } FileAccess::FileAccess(const FileAccess *fa) : all_fa_node(this) { Init(); cwd=fa->cwd; home=fa->home; user.set(fa->user); pass.set(fa->pass); pass_open=fa->pass_open; hostname.set(fa->hostname); portname.set(fa->portname); vproto.set(fa->vproto); } FileAccess::~FileAccess() { all_fa_node.remove(); } void FileAccess::Open(const char *fn,int mode,off_t offs) { #ifdef OPEN_DEBUG printf("%p->FA::Open(%s,%d)\n",this,fn?fn:"NULL",mode); #endif if(IsOpen()) Close(); Resume(); file.set(fn); real_pos=UNKNOWN_POS; pos=offs; this->mode=mode; mkdir_p=false; rename_f=false; Timeout(0); switch((open_mode)mode) { case STORE: case REMOVE: case MAKE_DIR: case CHANGE_MODE: cache->FileChanged(this,file); break; case REMOVE_DIR: cache->FileChanged(this,file); cache->TreeChanged(this,file); break; default: break; } } const char *FileAccess::StrError(int err) { static xstring str; // note to translators: several errors should not be displayed to user; // so no need to translate them. switch(err) { case(IN_PROGRESS): return("Operation is in progress"); case(OK): return("Error 0"); case(SEE_ERRNO): if(error) return str.vset(error.get(),": ",strerror(saved_errno),NULL); return(strerror(saved_errno)); case(LOOKUP_ERROR): return(error); case(NOT_OPEN): // Actually this means an error in application return("Class is not Open()ed"); case(NO_FILE): if(error) return str.vset(_("Access failed: "),error.get(),NULL); return(_("File cannot be accessed")); case(NO_HOST): return(_("Not connected")); case(FATAL): if(error) return str.vset(_("Fatal error"),": ",error.get(),NULL); return(_("Fatal error")); case(STORE_FAILED): return(_("Store failed - you have to reput")); case(LOGIN_FAILED): if(error) return str.vset(_("Login failed"),": ",error.get(),NULL); return(_("Login failed")); case(NOT_SUPP): if(error) return str.vset(_("Operation not supported"),": ",error.get(),NULL); return(_("Operation not supported")); case(FILE_MOVED): if(error) return str.vset(_("File moved"),": ",error.get(),NULL); else return str.vset(_("File moved to `"),location?location.get():"?","'",NULL); } return(""); } void FileAccess::Close() { file.set(0); file_url.set(0); file1.set(0); new_cwd=0; mode=CLOSED; opt_date=0; opt_size=0; fileset_for_info=0; retries=0; entity_size=NO_SIZE; entity_date=NO_DATE; ascii=false; norest_manual=false; location.set(0); entity_content_type.set(0); entity_charset.set(0); ClearError(); } void FileAccess::Open2(const char *f,const char *f1,open_mode o) { Close(); file1.set(f1); Open(f,o); cache->TreeChanged(this,file); cache->FileChanged(this,file); cache->FileChanged(this,file1); } void FileAccess::Rename(const char *rfile,const char *to,bool clobber) { Open2(rfile,to,RENAME); rename_f=clobber; } void FileAccess::Mkdir(const char *fn,bool allp) { Open(fn,MAKE_DIR); mkdir_p=allp; } StringSet *FileAccess::MkdirMakeSet() const { StringSet *set=new StringSet; const char *sl=strchr(file,'/'); while(sl) { if(sl>file) { xstring& tmp=xstring::get_tmp(file,sl-file); if(tmp.ne(".") && tmp.ne("..")) set->Append(tmp); } sl=strchr(sl+1,'/'); } return set; } bool FileAccess::SameLocationAs(const FileAccess *fa) const { return SameSiteAs(fa); } bool FileAccess::SameSiteAs(const FileAccess *fa) const { return SameProtoAs(fa); } const xstring& FileAccess::GetFileURL(const char *f,int flags) const { const char *proto=GetVisualProto(); if(proto[0]==0) return xstring::get_tmp(""); ParsedURL u; u.proto.set(proto); if(!(flags&NO_USER)) u.user.set(user); if((pass_open || (flags&WITH_PASSWORD)) && !(flags&NO_PASSWORD)) u.pass.set(pass); u.host.set(hostname); u.port.set(portname); if(!(flags&NO_PATH)) { if(cwd.url) { Path f_path(cwd); if(f) f_path.Change(f,true); if(f_path.url) { int f_path_index=url::path_index(f_path.url); return u.CombineTo(xstring::get_tmp(""),home) .append(f_path.url+f_path_index); } } bool is_dir=((!f || !*f) && !cwd.is_file); if(!f || (f[0]!='/' && f[0]!='~')) f=dir_file(cwd.path?cwd.path.get():"~",f); u.path.set(f); if(is_dir && url::dir_needs_trailing_slash(proto) && u.path.last_char()!='/') u.path.append('/'); } return u.CombineTo(xstring::get_tmp(""),home); } const xstring& FileAccess::GetConnectURL(int flags) const { return GetFileURL(0,flags); } void FileAccess::Connect(const char *host1,const char *port1) { Close(); hostname.set(host1); portname.set(port1); DontSleep(); ResetLocationData(); } void FileAccess::Login(const char *user1,const char *pass1) { Close(); user.set(user1); pass.set(pass1); pass_open=false; if(user && pass==0) { xlist_for_each(FileAccess,all_fa,node,o) { pass.set(o->pass); if(SameSiteAs(o) && o->pass) break; } if(!o) pass.set(0); if(pass==0 && hostname) // still no pass? Try .netrc { NetRC::Entry *nrc=NetRC::LookupHost(hostname,user); if(nrc) pass.set(nrc->pass); } } ResetLocationData(); } void FileAccess::ResetLocationData() { cwd.Set(default_cwd,false,0); home.Set((char*)0); } void FileAccess::SetPasswordGlobal(const char *p) { pass.set(p); xstring save_pass; xlist_for_each(FileAccess,all_fa,node,o) { if(o==this) continue; save_pass.set(o->pass); // cheat SameSiteAs. o->pass.set(pass); if(!SameSiteAs(o)) o->pass.set(save_pass); } } void FileAccess::GetInfoArray(FileSet *info) { Open(0,ARRAY_INFO); fileset_for_info=info; fileset_for_info->rewind(); } static void expand_tilde(xstring &path, const char *home, int i=0) { if(!(path[i]=='~' && (path[i+1]==0 || path[i+1]=='/'))) return; char prefix_len=(last_char(home)=='/' ? 2 : 1); if(home[0]=='/' && i>0 && path[i-1]=='/') home++; path.set_substr(i,prefix_len,home); } void FileAccess::ExpandTildeInCWD() { if(home) { cwd.ExpandTilde(home); if(new_cwd) new_cwd->ExpandTilde(home); if(real_cwd) expand_tilde(real_cwd,home); if(file) expand_tilde(file,home); if(file1) expand_tilde(file1,home); } } void FileAccess::set_home(const char *h) { home.Set(h); ExpandTildeInCWD(); } const char *FileAccess::ExpandTildeStatic(const char *s) const { if(!home || !(s[0]=='~' && (s[1]=='/' || s[1]==0))) return s; static xstring buf; buf.set(s); expand_tilde(buf,home); return buf; } static inline bool last_element_is_doubledot(const char *path,const char *end) { return((end==path+2 && !strncmp(path,"..",2)) || (end>path+2 && !strncmp(end-3,"/..",3))); } int FileAccess::device_prefix_len(const char *path) const { ResValue dp=Query("device-prefix",hostname); if(dp.is_nil() || !dp.to_bool()) return 0; int i=0; while(path[i] && (is_ascii_alnum(path[i]) || strchr("$_-",path[i]))) i++; if(i>0 && path[i]==':') return i+1+(path[i+1]=='/'); return 0; } void FileAccess::Path::Optimize(xstring& path,int device_prefix_len) { int prefix_size=0; if(path[0]=='/' && path[1]=='~' && device_prefix_len==1) { prefix_size=2; while(path[prefix_size]!='/' && path[prefix_size]!='\0') prefix_size++; } else if(path[0]=='/') { prefix_size=1; if(path[1]=='/' && (!path[2] || path[2]!='/')) prefix_size=2; } else if(path[0]=='~') { prefix_size=1; while(path[prefix_size]!='/' && path[prefix_size]!='\0') prefix_size++; } else { // handle VMS and DOS devices. prefix_size=device_prefix_len; } char *in; char *out; in=out=path.get_non_const()+prefix_size; while((in[0]=='.' && (in[1]=='/' || in[1]==0)) || (in>path && in[-1]=='/' && (in[0]=='/' || (in[0]=='.' && in[1]=='.' && (in[2]=='/' || in[2]==0))))) { if(in[0]=='.' && in[1]=='.') in++; in++; if(*in=='/') in++; } while(*in) { if(in[0]=='/') { // double slash if(in[1]=='/') { in++; continue; } if(in[1]=='.') { // . - cur dir if(in[2]=='/' || in[2]=='\0') { in+=2; continue; } // .. - prev dir if(in[2]=='.' && (in[3]=='/' || in[3]=='\0')) { if(last_element_is_doubledot(path+prefix_size,out) || out==path || (out==path+prefix_size && out[-1]!='/')) { if(out>path && out[-1]!='/') *out++='/'; *out++='.'; *out++='.'; } else { while(out>path+prefix_size && *--out!='/') ; } in+=3; continue; } } // don't add slash after prefix with slash if(out>path && out[-1]=='/') { in++; continue; } } *out++=*in++; } path.truncate(path.length()-(in-out)); } void FileAccess::Chdir(const char *path,bool verify) { cwd.ExpandTilde(home); Close(); new_cwd=new Path(&cwd); new_cwd->Change(path,false); if(verify) Open(new_cwd->path,CHANGE_DIR); else { cwd.Set(new_cwd); new_cwd=0; } } void FileAccess::PathVerify(const Path &p) { Close(); new_cwd=new Path(p); Open(new_cwd->path,CHANGE_DIR); } void FileAccess::Chmod(const char *file,int m) { chmod_mode=m; Open(file,CHANGE_MODE); } void FileAccess::SetError(int ec,const char *e) { if(ec==SEE_ERRNO && !saved_errno) saved_errno=errno; if(ec==NO_FILE && file && file[0] && !strstr(e,file)) error.vset(e," (",file.get(),")",NULL); else error.set(e); error_code=ec; } void FileAccess::ClearError() { saved_errno=0; error_code=OK; error.set(0); } void FileAccess::Fatal(const char *e) { SetError(FATAL,e); } void FileAccess::SetSuggestedFileName(const char *fn) { suggested_filename.set(0); if(fn==0) return; // don't allow subdirectories. if(strchr(fn,'/') || strchr(fn,'\\') || strchr(fn,':')) return; for(int i=0; fn[i]; i++) { // don't allow control chars. if(iscntrl((unsigned char)fn[i])) return; } if(!*fn || *fn=='.') return; suggested_filename.set(fn); } void FileAccess::SetFileURL(const char *u) { file_url.set(u); if(new_cwd && mode==CHANGE_DIR) new_cwd->SetURL(u); } FileAccess *SessionPool::pool[pool_size]; void SessionPool::Reuse(FileAccess *f) { if(f==0) return; if(f->GetHostName()==0) { SMTask::Delete(f); return; } f->Close(); f->SetPriority(0); int i; for(i=0; iIsBetterThan(pool[i])) { SMTask::Delete(pool[i]); pool[i]=f; return; } } SMTask::Delete(f); } void SessionPool::Print(FILE *f) { int arr[pool_size]; int n=0; int i; for(i=0; iSameLocationAs(pool[i])) break; if(j==n) arr[n++]=i; } // sort? for(i=0; iGetConnectURL().get()); } FileAccess *SessionPool::GetSession(int n) { if(n<0 || n>=pool_size) return 0; FileAccess *s=pool[n]; pool[n]=0; return s; } FileAccess *SessionPool::Walk(int *n,const char *proto) { for( ; *nGetProto(),proto)) return pool[*n]; } return 0; } void SessionPool::ClearAll() { int pass=0; for(;;) { int left=0; for(int n=0; nDisconnect(); if(!pool[n]->IsConnected()) { SMTask::Delete(pool[n]); pool[n]=0; } else { left++; } } if(left==0) break; SMTask::Schedule(); SMTask::Block(); pass++; } } void FileAccess::SetTryTime(time_t t) { if(t) reconnect_timer.Reset(Time(t)); else reconnect_timer.Stop(); } bool FileAccess::IsBetterThan(const FileAccess *fa) const { return(SameProtoAs(fa) && this->IsConnected() > fa->IsConnected()); } void FileAccess::Reconfig(const char *) {} void FileAccess::ConnectVerify() { mode=CONNECT_VERIFY; } const char *FileAccess::CurrentStatus() { return ""; } int FileAccess::Buffered() { return 0; } bool FileAccess::IOReady() { return IsOpen(); } int FileAccess::IsConnected() const { return 0; } void FileAccess::UseCache(bool) {} bool FileAccess::NeedSizeDateBeforehand() { return false; } void FileAccess::Cleanup() {} void FileAccess::CleanupThis() {} ListInfo *FileAccess::MakeListInfo(const char *path) { return 0; } Glob *FileAccess::MakeGlob(const char *pattern) { return new NoGlob(pattern); } DirList *FileAccess::MakeDirList(ArgV *a) { delete a; return 0; } void FileAccess::CleanupAll() { xlist_for_each(FileAccess,all_fa,node,o) { Enter(o); o->CleanupThis(); Leave(o); } } FileAccess *FileAccess::NextSameSite(FA *scan) const { if(scan==0) scan=all_fa.first_obj(); else scan=scan->all_fa_node.next_obj(); for( ; scan; scan=scan->all_fa_node.next_obj()) if(scan!=this && SameSiteAs(scan)) return scan; return 0; } FileAccess *FileAccess::New(const char *proto,const char *host,const char *port) { ClassInit(); if(proto==0) proto="file"; if(!strcmp(proto,"slot")) { const FA *session=ConnectionSlot::FindSession(host); return session?session->Clone():0; } FA *session=Protocol::NewSession(proto); if(!session) return 0; const char *n_proto=session->ProtocolSubstitution(host); if(n_proto && strcmp(n_proto,proto)) { FA *n_session=Protocol::NewSession(n_proto); if(n_session) { Delete(session); session=n_session; session->SetVisualProto(proto); } } if(host) session->Connect(host,port); return session; } FileAccess *FileAccess::New(const ParsedURL *u,bool dummy) { const char *proto=u->proto?u->proto.get():"file"; FileAccess *s=New(proto,u->host); if(!s) { if(!dummy) return 0; return new DummyNoProto(proto); } if(strcmp(proto,"slot")) s->Connect(u->host,u->port); if(u->user) s->Login(u->user,u->pass); // path? return s; } // FileAccess::Protocol implementation xmap_p FileAccess::Protocol::proto_by_name; FileAccess::Protocol::Protocol(const char *proto, SessionCreator *creator) { this->proto=proto; this->New=creator; proto_by_name.add(proto,this); } FileAccess::Protocol *FileAccess::Protocol::FindProto(const char *proto) { return proto_by_name.lookup(proto); } FileAccess *FileAccess::Protocol::NewSession(const char *proto) { Protocol *p; p=FindProto(proto); if(p) return p->New(); #ifdef WITH_MODULES #define PROTO_PREFIX "proto-" const char *mod=xstring::cat(PROTO_PREFIX,proto,NULL); void *map=module_load(mod,0,0); if(map==0) { fprintf(stderr,"%s\n",module_error_message()); return 0; } p=FindProto(proto); if(p) return p->New(); #endif return 0; } // FileAccessOperation implementation void FileAccessOperation::SetError(const char *e) { error_text.set(e); done=true; } void FileAccessOperation::SetErrorCached(const char *e) { SetError(e); error_text.append(_(" [cached]")); } DirList::DirList(FileAccess *s,ArgV *a) : FileAccessOperation(s), buf(new Buffer()), args(a), color(false) { } DirList::~DirList() { } // ListInfo implementation ListInfo::ListInfo(FileAccess *s,const char *p) : FileAccessOperation(s), exclude_prefix(0), exclude(0), need(0), follow_symlinks(false), try_recursive(false), is_recursive(false) { if(session && p) { saved_cwd=session->GetCwd(); session->Chdir(p,false); } } void ListInfo::PrepareToDie() { if(session) session->Close(); if(session && saved_cwd) session->SetCwd(saved_cwd); } ListInfo::~ListInfo() {} // Path implementation void FileAccess::Path::init() { device_prefix_len=0; is_file=false; } FileAccess::Path::~Path() { } void FileAccess::Path::Set(const char *new_path,bool new_is_file,const char *new_url,int new_device_prefix_len) { path.set(new_path); is_file=new_is_file; url.set(new_url); device_prefix_len=new_device_prefix_len; } void FileAccess::Path::Set(const Path *o) { Set(o->path,o->is_file,o->url,o->device_prefix_len); } void FileAccess::Path::Change(const char *new_path,bool new_is_file,const char *new_path_enc,int new_device_prefix_len) { if(!new_path && new_path_enc) new_path=url::decode(new_path_enc); if(!new_path || !*new_path) return; const char *bn=basename_ptr(new_path); if(!strcmp(bn,".") || !strcmp(bn,"..")) new_is_file=false; int path_index=0; if(url) { path_index=url::path_index(url); xstring new_url_path(url+path_index); if(is_file) { dirname_modify(new_url_path); if(!new_url_path[0]) new_url_path.set("/~"); } if(new_url_path.last_char()!='/') new_url_path.append('/'); if(new_path[0]=='/' || new_path[0]=='~' || new_device_prefix_len!=0) { bool have_slash=((new_path_enc?new_path_enc:new_path)[0]=='/'); new_url_path.set(have_slash?"":"/"); } if(new_path_enc) new_url_path.append(new_path_enc); else new_url_path.append(url::encode(new_path,URL_PATH_UNSAFE)); if(!new_is_file && url::dir_needs_trailing_slash(url) && new_url_path.last_char()!='/') new_url_path.append('/'); Optimize(new_url_path,!strncmp(new_url_path,"/~",2)); url.truncate(path_index); url.append(new_url_path); } if(new_path[0]!='/' && new_path[0]!='~' && new_device_prefix_len==0 && path && path[0]) { if(is_file) { dirname_modify(path); if(!path[0]) path.set("~"); } if(last_char(path)=='/') new_path=xstring::format("%s%s",path.get(),new_path); else new_path=xstring::format("%s/%s",path.get(),new_path); } path.set(new_path); device_prefix_len=new_device_prefix_len; Optimize(); strip_trailing_slashes(path); is_file=new_is_file; if(!strcmp(path,"/") || !strcmp(path,"//")) is_file=false; // sanity check if(url) { ParsedURL u(url); if(u.path.length()>1) u.path.chomp('/'); if(!u.path.eq(path)) { LogError(0,"URL mismatch %s [%s] vs %s, dropping URL\n",url.get(),u.path.get(),path.get()); url.set(0); } } } bool FileAccess::Path::operator==(const Path &p2) const { const Path &p1=*this; if(p1.is_file!=p2.is_file) return false; if(xstrcmp(p1.path,p2.path)) return false; if(xstrcmp(p1.url,p2.url)) return false; return true; } void FileAccess::Path::ExpandTilde(const Path &home) { if(!home.path) return; if(path && path[0]=='~' && (path[1]=='/' || path[1]=='\0')) { device_prefix_len=home.device_prefix_len; if(path[1]=='\0') is_file=home.is_file; } if(url) { int pi=url::path_index(url); if(url[pi]=='/' && url[pi+1]=='~') pi++; expand_tilde(url,home.url?home.url.get():url::encode(home.path,URL_PATH_UNSAFE).get(),pi); } expand_tilde(path,home.path); } #include "DirColors.h" #include "LocalDir.h" #include "FileCopy.h" #include "modconfig.h" #ifndef MODULE_PROTO_FTP # include "ftpclass.h" # define _ftp Ftp::ClassInit() #else # define _ftp #endif #ifndef MODULE_PROTO_FILE # include "LocalAccess.h" # define _file LocalAccess::ClassInit() #else # define _file #endif #ifndef MODULE_PROTO_HTTP # include "Http.h" # define _http Http::ClassInit() #else # define _http #endif #ifndef MODULE_PROTO_FISH # include "Fish.h" # define _fish Fish::ClassInit() #else # define _fish #endif #ifndef MODULE_PROTO_SFTP # include "SFtp.h" # define _sftp SFtp::ClassInit() #else # define _sftp #endif bool FileAccess::class_inited; LsCache *FileAccess::cache; void FileAccess::ClassInit() { if(class_inited) return; class_inited=true; cache=new LsCache(); SignalHook::ClassInit(); ResMgr::ClassInit(); if(!Log::global) Log::global=new Log("debug"); _ftp; _file; _http; _fish; _sftp; // make it link in classes required by modules. LocalDirectory ld; } void FileAccess::ClassCleanup() { Protocol::ClassCleanup(); call_dynamic_hook("lftp_network_cleanup"); DirColors::DeleteInstance(); delete cache; cache=0; FileCopy::fxp_create=0; } const FileAccessRef& FileAccessRef::operator=(FileAccess *p) { reuse(); ptr=SMTask::MakeRef(p); return *this; } // hook-up gnulib... CDECL_BEGIN #include "md5.h" #include "glob.h" CDECL_END void *_md5_hook=(void*)md5_init_ctx; void *_glob_hook=(void*)glob;