/* * 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 "Resolver.h" #include "SignalHook.h" #include #include #include "trio.h" #include #include #include #include #include #include #include #ifdef HAVE_ARPA_NAMESER_H # define class _class // workaround for FreeBSD 3.2. # include # undef class #endif #ifdef HAVE_RESOLV_H # include #endif #if LIBIDN2 # include #endif #ifdef DNSSEC_LOCAL_VALIDATION # include "validator/validator.h" #endif #include "xstring.h" #include "ResMgr.h" #include "log.h" #include "plural.h" #ifndef C_IN # define C_IN 1 #endif #ifndef T_SRV # define T_SRV 33 #endif #if !HAVE_DECL_HSTRERROR extern "C" { const char *hstrerror(int); } #endif #ifdef HAVE_H_ERRNO #if !HAVE_DECL_H_ERRNO CDECL int h_errno; #endif #endif #if HAVE_RES_SEARCH && !HAVE_DECL_RES_SEARCH CDECL int res_search(const char*,int,int,unsigned char*,int); #endif #if INET6 # define DEFAULT_ORDER "inet inet6" #else # define DEFAULT_ORDER "inet" #endif struct address_family { int number; const char *name; }; static const address_family af_list[]= { { AF_INET, "inet" }, #if INET6 { AF_INET6, "inet6" }, #endif { -1, 0 } }; ResolverCache *Resolver::cache; Resolver::Resolver(const char *h,const char *p,const char *defp, const char *ser,const char *pr) : hostname(h), portname(p), service(ser), proto(pr), defport(defp) { port_number=0; pipe_to_child[0]=pipe_to_child[1]=-1; done=false; timeout_timer.SetResource("dns:fatal-timeout",hostname); Reconfig(); use_fork=ResMgr::QueryBool("dns:use-fork",0); error=0; no_cache=false; } Resolver::~Resolver() { if(pipe_to_child[0]!=-1) close(pipe_to_child[0]); if(pipe_to_child[1]!=-1) close(pipe_to_child[1]); if(w) { w->Kill(SIGKILL); w.borrow()->Auto(); } } int Resolver::Do() { if(done) return STALL; int m=STALL; if(!no_cache && cache) { const sockaddr_u *a; int n; cache->Find(hostname,portname,defport,service,proto,&a,&n); if(a && n>0) { LogNote(10,"dns cache hit"); addr.nset(a,n); done=true; return MOVED; } no_cache=true; } if(use_fork) { if(pipe_to_child[0]==-1) { int res=pipe(pipe_to_child); if(res==-1) { if(NonFatalError(errno)) return m; MakeErrMsg("pipe()"); return MOVED; } fcntl(pipe_to_child[0],F_SETFL,O_NONBLOCK); fcntl(pipe_to_child[0],F_SETFD,FD_CLOEXEC); fcntl(pipe_to_child[1],F_SETFD,FD_CLOEXEC); m=MOVED; LogNote(4,_("Resolving host address...")); } if(!w && !buf) { pid_t proc=fork(); if(proc==-1) { TimeoutS(1); return m; } if(proc==0) { // child SignalHook::Ignore(SIGINT); SignalHook::Ignore(SIGTSTP); SignalHook::Ignore(SIGQUIT); SignalHook::Ignore(SIGHUP); close(0); // no input will be needed. close(pipe_to_child[0]); pipe_to_child[0]=-1; buf=new IOBufferFDStream(new FDStream(pipe_to_child[1],""),IOBuffer::PUT); DoGethostbyname(); buf->PutEOF(); while(buf->Size()>0 && !buf->Error() && !buf->Broken()) buf->Roll(); // should flush quickly. _exit(0); } // parent close(pipe_to_child[1]); pipe_to_child[1]=-1; w=new ProcWait(proc); m=MOVED; } if(!buf) { buf=new IOBufferFDStream(new FDStream(pipe_to_child[0],""),IOBuffer::GET); // Roll(buf); m=MOVED; } } else /* !use_fork */ { if(!buf) { LogNote(4,_("Resolving host address...")); buf=new IOBuffer(IOBuffer::GET); DoGethostbyname(); if(Deleted()) return MOVED; } } if(buf->Error()) { err_msg.set(buf->ErrorText()); done=true; return MOVED; } if(!buf->Eof()) // wait for all data to arrive (not too much) { if(timeout_timer.Stopped()) { err_msg.set(_("host name resolve timeout")); done=true; return MOVED; } return m; } const char *s; char c; int n; buf->Get(&s,&n); if(n<1) goto proto_error; c=*s; buf->Skip(1); buf->Get(&s,&n); if(c=='E' || c=='P') // error { const char *tport=portname?portname.get():defport.get(); err_msg.vset(c=='E'?hostname.get():tport,": ",s,NULL); done=true; return MOVED; } if((unsigned)nAdd(hostname,portname,defport,service,proto,addr.get(),addr.count()); xstring report; report.set(xstring::format(plural("%d address$|es$ found",addr.count()),addr.count())); if(addr.count()>0) { report.append(": "); for(int i=0; i0 && addr.last()==add) return; addr.append(add); } int Resolver::FindAddressFamily(const char *name) { for(const address_family *f=af_list; f->name; f++) { if(!strcasecmp(name,f->name)) return f->number; } return -1; } void Resolver::ParseOrder(const char *s,int *o) { const char * const delim="\t "; char *s1=alloca_strdup(s); int idx=0; for(s1=strtok(s1,delim); s1; s1=strtok(0,delim)) { int af=FindAddressFamily(s1); if(af!=-1 && idx<15) { if(o) o[idx]=af; idx++; } } if(o) o[idx]=-1; } #if defined(HAVE_DN_EXPAND) && !HAVE_DECL_DN_EXPAND CDECL int dn_expand(const unsigned char *msg,const unsigned char *eomorig,const unsigned char *comp_dn,char *exp_dn,int length); CDECL int dn_skipname(const unsigned char *msg,const unsigned char *eomorig); #endif #ifdef HAVE_RES_SEARCH static int extract_domain(const unsigned char *answer,const unsigned char *scan,int len, char *store,int store_len) { #ifdef HAVE_DN_EXPAND // newer resolver versions have dn_expand and dn_skipname if(store) dn_expand(answer,scan+len,scan,store,store_len); return dn_skipname(scan,scan+len); #else // ...older don't. int count=1; // reserve space for \0 int refs=0; int consumed=0; const unsigned char *start=scan; for(;;) { if(len<=0) break; int label_len=*scan; scan++; len--; if((label_len & 0xC0) == 0xC0) // compression { if(len<=0) break; int offset=((label_len&0x3F)<<8) + *scan; scan++; len--; if(refs==0) consumed=scan-start; if(answer+offset>=scan+len) break; // error len=scan+len-answer+offset; scan=answer+offset; if(++refs > 256) break; // too many hops. continue; } if(label_len==0) break; while(label_len>0) { if(len<=0) break; if(store && countpriority < sb->priority) return -1; if(sa->priority > sb->priority) return 1; if(sa->order < sb->order) return -1; if(sa->order > sb->order) return 1; if(sa->weight > sb->weight) return -1; if(sa->weight < sb->weight) return 1; return 0; } #endif // RES_SEARCH void Resolver::LookupSRV_RR() { if(!ResMgr::QueryBool("dns:SRV-query",hostname)) return; #ifdef HAVE_RES_SEARCH const char *tproto=proto?proto.get():"tcp"; time_t try_time; unsigned char answer[0x1000]; const char *srv_name=xstring::format("_%s._%s.%s",service.get(),tproto,hostname.get()); srv_name=alloca_strdup(srv_name); int retries=0; int max_retries=ResMgr::Query("dns:max-retries",hostname); int len; for(;;) { if(!use_fork) { Schedule(); if(Deleted()) return; } time(&try_time); #ifndef DNSSEC_LOCAL_VALIDATION len=res_search(srv_name, C_IN, T_SRV, answer, sizeof(answer)); if(len>=0) break; #else val_status_t val_status; bool require_trust = ResMgr::QueryBool("dns:strict-dnssec",hostname); len=val_res_search(NULL, srv_name, C_IN, T_SRV, answer, sizeof(answer), &val_status); if(len>=0) { if(require_trust && !val_istrusted(val_status)) return; else break; } #endif #ifdef HAVE_H_ERRNO if(h_errno!=TRY_AGAIN) return; if(++retries>=max_retries && max_retries) return; time_t t=time(0); if(t-try_time<5) sleep(5-(t-try_time)); #else // no h_errno return; #endif } if(len>(int)sizeof(answer)) len=sizeof(answer); if(len0; question_count--) { int dom_len=extract_domain(answer,scan,len,0,0); if(dom_len<0) return; scan+=dom_len; len-=dom_len; if(len<4) return; scan+=4; len-=4; } xarray SRVs; // now process answers section for( ; answer_count>0; answer_count--) { int dom_len=extract_domain(answer,scan,len,0,0); if(dom_len<0) return; scan+=dom_len; len-=dom_len; if(len<8) return; scan+=8; len-=8; // skip type,class,ttl if(len<2) return; int data_len=(scan[0]<<8)+scan[1]; scan+=2; len-=2; if(len0) { int r=int(rand()/(RAND_MAX+1.0)*weight_sum); if(r>=weight_sum) r=weight_sum-1; int w=0; for(s=base; sai_next) { if(a_res->ai_family!=af) continue; sockname = a_res->ai_addr; addr_scope = 0; switch(a_res->ai_family) { case AF_INET: inet_addr = (sockaddr_in *)sockname; addr_data = (const char *)&(inet_addr->sin_addr.s_addr); addr_len = sizeof(inet_addr->sin_addr.s_addr); break; case AF_INET6: inet6_addr = (sockaddr_in6 *)sockname; addr_data = (const char *)&(inet6_addr->sin6_addr.s6_addr); #ifdef HAVE_STRUCT_SOCKADDR_IN6_SIN6_SCOPE_ID addr_scope = inet6_addr->sin6_scope_id; #endif addr_len = sizeof(inet6_addr->sin6_addr.s6_addr); break; default: continue; } AddAddress(a_res->ai_family, addr_data, addr_len, addr_scope); } } freeaddrinfo(ainfo); break; } if(ainfo_res != EAI_AGAIN || (++retries>=max_retries && max_retries)) { error = gai_strerror(ainfo_res); break; } #else // !HAVE_GETADDRINFO int af=af_order[af_index]; if(af==-1) break; struct hostent *ha; # if defined(HAVE_GETIPNODEBYNAME) # ifndef HAVE_H_ERRNO # define HAVE_H_ERRNO 1 # endif # undef h_errno // it could be a macro, but we want it to be local variable. int h_errno=0; ha=getipnodebyname(name,af,0,&h_errno); # elif defined(HAVE_GETHOSTBYNAME2) ha=gethostbyname2(name,af); # else if(af==AF_INET) ha=gethostbyname(name); else { retries=0; af_index++; continue; } # endif if(ha) { const char * const *a; for(a=ha->h_addr_list; *a; a++) AddAddress(ha->h_addrtype, *a, ha->h_length, 0); retries=0; af_index++; # if defined(HAVE_GETIPNODEBYNAME) freehostent(ha); # endif continue; } # ifdef HAVE_H_ERRNO if(h_errno!=TRY_AGAIN || (++retries>=max_retries && max_retries)) # endif { if(error==0) { # ifdef HAVE_H_ERRNO error=hstrerror(h_errno); # else error=_("Host name lookup failure"); # endif } retries=0; af_index++; continue; // try other address families } #endif /* HAVE_GETADDRINFO */ time_t t; if((t=time(0))-try_time<5) sleep(5-(t-try_time)); } } void Resolver::DoGethostbyname() { if(port_number==0) { const char *tproto=proto?proto.get():"tcp"; const char *tport=portname?portname.get():defport.get(); if(isdigit((unsigned char)tport[0])) port_number=htons(atoi(tport)); else { struct servent *se=getservbyname(tport,tproto); if(se) port_number=se->s_port; else { buf->Put("P"); buf->Format(_("no such %s service"),tproto); return; } } } if(service && !portname && !isdigit((unsigned char)hostname[0])) LookupSRV_RR(); if(!use_fork && Deleted()) return; const char *h=ResMgr::Query("dns:name",hostname); if(!h || !*h) h=hostname; char *hs=alloca_strdup(h); char *tok; for(hs=strtok_r(hs,",",&tok); hs; hs=strtok_r(NULL,",",&tok)) LookupOne(hs); if(!use_fork && Deleted()) return; if(addr.count()==0) { buf->Put("E"); if(error==0) error=_("No address found"); buf->Put(error); return; } buf->Put("O"); buf->Put((const char*)addr.get(),addr.count()*addr.get_element_size()); addr.unset(); } void Resolver::Reconfig(const char *name) { if(!name || strncmp(name,"dns:",4)) return; } ResolverCache::ResolverCache() : Cache(ResMgr::FindRes("dns:cache-size"),ResMgr::FindRes("dns:cache-enable")) { } void ResolverCache::Reconfig(const char *r) { if(!xstrcmp(r,"dns:SRV-query") || !xstrcmp(r,"dns:order")) Flush(); } ResolverCacheEntry *ResolverCache::Find(const char *h,const char *p,const char *defp,const char *ser,const char *pr) { for(ResolverCacheEntry *c=IterateFirst(); c; c=IterateNext()) { if(c->Matches(h,p,defp,ser,pr)) return c; } return 0; } void ResolverCache::Add(const char *h,const char *p,const char *defp, const char *ser,const char *pr,const sockaddr_u *a,int n) { Trim(); ResolverCacheEntry *c=Find(h,p,defp,ser,pr); if(c) c->SetData(a,n); else { if(!IsEnabled(h)) return; AddCacheEntry(new ResolverCacheEntry(h,p,defp,ser,pr,a,n)); } } bool ResolverCacheEntryLoc::Matches(const char *h,const char *p, const char *defp,const char *ser,const char *pr) { return (!xstrcasecmp(hostname,h) && !xstrcmp(portname,p) && !xstrcmp(defport,defp) && !xstrcmp(service,ser) && !xstrcmp(proto,pr)); } void ResolverCache::Find(const char *h,const char *p,const char *defp, const char *ser,const char *pr,const sockaddr_u **a,int *n) { *a=0; *n=0; // if cache is disabled for this host, return nothing. if(!IsEnabled(h)) return; ResolverCacheEntry *c=Find(h,p,defp,ser,pr); if(c) { if(c->Stopped()) { Trim(); return; } c->GetData(a,n); } }