/* -*- C++ -*- * Copyright (C) 2003,2004 Thiago Macieira * * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NET_IF_H #include #endif #include #include #include #include #include "kdebug.h" #include "kglobal.h" #include "kstandarddirs.h" #include "kapplication.h" #include "kresolver.h" #include "tdesocketaddress.h" #include "kresolverstandardworkers_p.h" struct hostent; struct addrinfo; using namespace KNetwork; using namespace KNetwork::Internal; static bool hasIPv6() { #ifndef AF_INET6 return false; #else if (getenv("TDE_NO_IPV6") != 0L) return false; int fd = ::socket(AF_INET6, SOCK_STREAM, 0); if (fd == -1) return false; ::close(fd); return true; #endif } // blacklist management static TQMutex blacklistMutex; // KDE4: change to a QReadWriteLock TQStringList KBlacklistWorker::blacklist; void KBlacklistWorker::init() { // HACK! // FIXME KDE4: How do I detect there is an instance, without triggering // its creation or an assertion fault? if (!TDEGlobal::_instance) return; static bool beenhere = false; if (beenhere) return; beenhere = true; loadBlacklist(); } void KBlacklistWorker::loadBlacklist() { TQMutexLocker locker(&blacklistMutex); TQStringList filelist = TDEGlobal::dirs()->findAllResources("config", "ipv6blacklist"); TQStringList::ConstIterator it = filelist.constBegin(), end = filelist.constEnd(); for ( ; it != end; ++it) { // for each file, each line is a domainname to be blacklisted TQFile f(*it); if (!f.open(IO_ReadOnly)) continue; TQTextStream stream(&f); stream.setEncoding(TQTextStream::Latin1); for (TQString line = stream.readLine(); !line.isNull(); line = stream.readLine()) { if (line.isEmpty()) continue; // make sure there are no surrounding whitespaces // and that it starts with . line = line.stripWhiteSpace(); if (line[0] != '.') line.prepend('.'); blacklist.append(line.lower()); } } } // checks the blacklist to see if the domain is listed // it matches the domain ending part bool KBlacklistWorker::isBlacklisted(const TQString& host) { KBlacklistWorker::init(); // empty hostnames cannot be blacklisted if (host.isEmpty()) return false; // KDE4: QLatin1String TQString ascii = TQString::fromLatin1(KResolver::domainToAscii(host)); TQMutexLocker locker(&blacklistMutex); // now find out if this hostname is present TQStringList::ConstIterator it = blacklist.constBegin(), end = blacklist.constEnd(); for ( ; it != end; ++it) if (ascii.endsWith(*it)) return true; // no match: return false; } bool KBlacklistWorker::preprocess() { if (isBlacklisted(nodeName())) { results.setError(KResolver::NoName); finished(); return true; } return false; } bool KBlacklistWorker::run() { results.setError(KResolver::NoName); finished(); return false; // resolution failure } namespace { /* * Note on the use of the system resolver functions: * * In all cases, we prefer to use the new getaddrinfo(3) call. That means * it will always be used if it is found. * * If it's not found, we have the option to use gethostbyname2_r, * gethostbyname_r, gethostbyname2 and gethostbyname. If gethostbyname2_r * is defined, we will use it. * * If it's not defined, we have to choose between the non-reentrant * gethostbyname2 and the reentrant but IPv4-only gethostbyname_r: * we will choose gethostbyname2 if AF_INET6 is defined. * * Lastly, gethostbyname will be used if nothing else is present. */ #ifndef HAVE_GETADDRINFO # if defined(HAVE_GETHOSTBYNAME2_R) # define USE_GETHOSTBYNAME2_R # elif defined(HAVE_GETHOSTBYNAME_R) && (!defined(AF_INET6) || !defined(HAVE_GETHOSTBYNAME2)) # define USE_GETHOSTBYNAME_R # elif defined(HAVE_GETHOSTBYNAME2) # define USE_GETHOSTBYNAME2) # else # define USE_GETHOSTBYNAME # endif class GetHostByNameThread: public KResolverWorkerBase { public: TQCString m_hostname; // might be different! TQ_UINT16 m_port; int m_scopeid; int m_af; KResolverResults& results; GetHostByNameThread(const char * hostname, TQ_UINT16 port, int scopeid, int af, KResolverResults* res) : m_hostname(hostname), m_port(port), m_scopeid(scopeid), m_af(af), results(*res) { } ~GetHostByNameThread() { } virtual bool preprocess() { return true; } virtual bool run(); void processResults(hostent* he, int my_h_errno); }; bool GetHostByNameThread::run() { hostent *resultptr; hostent my_results; unsigned buflen = 1024; int res; int my_h_errno; char *buf = 0L; // tqDebug("ResolveThread::run(): started threaded gethostbyname for %s (af = %d)", // m_hostname.data(), m_af); ResolverLocker resLock( this ); do { res = 0; my_h_errno = HOST_NOT_FOUND; // check blacklist if (m_af != AF_INET && KBlacklistWorker::isBlacklisted(TQString::fromLatin1(m_hostname))) break; # ifdef USE_GETHOSTBYNAME2_R buf = new char[buflen]; res = gethostbyname2_r(m_hostname, m_af, &my_results, buf, buflen, &resultptr, &my_h_errno); # elif defined(USE_GETHOSTBYNAME_R) if (m_af == AF_INET) { buf = new char[buflen]; res = gethostbyname_r(m_hostname, &my_results, buf, buflen, &resultptr, &my_h_errno); } else resultptr = 0; // signal error # elif defined(USE_GETHOSTBYNAME2) // must lock mutex resultptr = gethostbyname2(m_hostname, m_af); my_h_errno = h_errno; # else if (m_af == AF_INET) { // must lock mutex resultptr = gethostbyname(m_hostname); my_h_errno = h_errno; } else resultptr = 0; # endif if (resultptr != 0L) my_h_errno = 0; // tqDebug("GetHostByNameThread::run(): gethostbyname for %s (af = %d) returned: %d", // m_hostname.data(), m_af, my_h_errno); if (res == ERANGE) { // Enlarge the buffer buflen += 1024; delete [] buf; buf = new char[buflen]; } if ((res == ERANGE || my_h_errno != 0) && checkResolver()) { // resolver needs updating, so we might as well do it now resLock.openClose(); } } while (res == ERANGE); processResults(resultptr, my_h_errno); delete [] buf; finished(); return results.error() == KResolver::NoError; } void GetHostByNameThread::processResults(hostent *he, int herrno) { if (herrno) { tqDebug("TDEStandardWorker::processResults: got error %d", herrno); switch (herrno) { case HOST_NOT_FOUND: results.setError(KResolver::NoName); return; case TRY_AGAIN: results.setError(KResolver::TryAgain); return; case NO_RECOVERY: results.setError(KResolver::NonRecoverable); return; case NO_ADDRESS: results.setError(KResolver::NoName); return; default: results.setError(KResolver::UnknownError); return; } } else if (he == 0L) { results.setError(KResolver::NoName); return; // this was an error } // clear any errors setError(KResolver::NoError); results.setError(KResolver::NoError); // we process results in the reverse order // that is, we prepend each result to the list of results int proto = protocol(); int socktype = socketType(); if (socktype == 0) socktype = SOCK_STREAM; // default TQString canon = KResolver::domainToUnicode(TQString::fromLatin1(he->h_name)); KInetSocketAddress sa; sa.setPort(m_port); if (he->h_addrtype != AF_INET) sa.setScopeId(m_scopeid); // this will also change the socket into IPv6 for (int i = 0; he->h_addr_list[i]; i++) { sa.setHost(KIpAddress(he->h_addr_list[i], he->h_addrtype == AF_INET ? 4 : 6)); results.prepend(KResolverEntry(sa, socktype, proto, canon, m_hostname)); // tqDebug("TDEStandardWorker::processResults: adding %s", sa.toString().latin1()); } // tqDebug("TDEStandardWorker::processResults: added %d entries", i); } #else // HAVE_GETADDRINFO class GetAddrInfoThread: public KResolverWorkerBase { public: TQCString m_node; TQCString m_serv; int m_af; int m_flags; KResolverResults& results; GetAddrInfoThread(const char* node, const char* serv, int af, int flags, KResolverResults* res) : m_node(node), m_serv(serv), m_af(af), m_flags(flags), results(*res) { } ~GetAddrInfoThread() { } virtual bool preprocess() { return true; } virtual bool run(); void processResults(addrinfo* ai, int ret_code, KResolverResults& rr); }; bool GetAddrInfoThread::run() { // check blacklist if ((m_af != AF_INET && m_af != AF_UNSPEC) && KBlacklistWorker::isBlacklisted(TQString::fromLatin1(m_node))) { results.setError(KResolver::NoName); finished(); return false; // failed } do { ResolverLocker resLock( this ); // process hints addrinfo hint; memset(&hint, 0, sizeof(hint)); hint.ai_family = m_af; hint.ai_socktype = socketType(); hint.ai_protocol = protocol(); if (hint.ai_socktype == 0) hint.ai_socktype = SOCK_STREAM; // default if (m_flags & KResolver::Passive) hint.ai_flags |= AI_PASSIVE; if (m_flags & KResolver::CanonName) hint.ai_flags |= AI_CANONNAME; # ifdef AI_NUMERICHOST if (m_flags & KResolver::NoResolve) hint.ai_flags |= AI_NUMERICHOST; # endif # ifdef AI_ADDRCONFIG hint.ai_flags |= AI_ADDRCONFIG; # endif // now we do the blocking processing if (m_node.isEmpty()) m_node = "*"; addrinfo *result; int res = getaddrinfo(m_node, m_serv, &hint, &result); // kdDebug(179) << k_funcinfo << "getaddrinfo(\"" // << m_node << "\", \"" << m_serv << "\", af=" // << m_af << ") returned " << res << endl; if (res != 0) { if (checkResolver()) { // resolver requires reinitialisation resLock.openClose(); continue; } switch (res) { case EAI_BADFLAGS: results.setError(KResolver::BadFlags); break; #ifdef EAI_NODATA // In some systems, EAI_NODATA was #define'd to EAI_NONAME which would break this case. #if EAI_NODATA != EAI_NONAME case EAI_NODATA: // it was removed in RFC 3493 #endif #endif case EAI_NONAME: results.setError(KResolver::NoName); break; case EAI_AGAIN: results.setError(KResolver::TryAgain); break; case EAI_FAIL: results.setError(KResolver::NonRecoverable); break; case EAI_FAMILY: results.setError(KResolver::UnsupportedFamily); break; case EAI_SOCKTYPE: results.setError(KResolver::UnsupportedSocketType); break; case EAI_SERVICE: results.setError(KResolver::UnsupportedService); break; case EAI_MEMORY: results.setError(KResolver::Memory); break; case EAI_SYSTEM: results.setError(KResolver::SystemError, errno); break; default: results.setError(KResolver::UnknownError, errno); break; } finished(); return false; // failed } // if we are here, lookup succeeded TQString canon; const char *previous_canon = 0L; for (addrinfo* p = result; p; p = p->ai_next) { // cache the last canon name to avoid doing the ToUnicode processing unnecessarily if ((previous_canon && !p->ai_canonname) || (!previous_canon && p->ai_canonname) || (p->ai_canonname != previous_canon && strcmp(p->ai_canonname, previous_canon) != 0)) { canon = KResolver::domainToUnicode(TQString::fromAscii(p->ai_canonname)); previous_canon = p->ai_canonname; } results.append(KResolverEntry(p->ai_addr, p->ai_addrlen, p->ai_socktype, p->ai_protocol, canon, m_node)); } freeaddrinfo(result); results.setError(KResolver::NoError); finished(); return results.error() == KResolver::NoError; } while (true); } #endif // HAVE_GETADDRINFO } // namespace bool TDEStandardWorker::sanityCheck() { // check that the requested values are sensible if (!nodeName().isEmpty()) { TQString node = nodeName(); if (node.find('%') != -1) node.truncate(node.find('%')); if (node.isEmpty() || node == TQString::fromLatin1("*") || node == TQString::fromLatin1("localhost")) m_encodedName.truncate(0); else { m_encodedName = KResolver::domainToAscii(node); if (m_encodedName.isNull()) { tqDebug("could not encode hostname '%s' (UTF-8)", node.utf8().data()); setError(KResolver::NoName); return false; // invalid hostname! } // tqDebug("Using encoded hostname '%s' for '%s' (UTF-8)", m_encodedName.data(), // node.utf8().data()); } } else m_encodedName.truncate(0); // just to be sure, but it should be clear already if (protocol() == -1) { setError(KResolver::NonRecoverable); return false; // user passed invalid protocol name } return true; // it's sane } bool TDEStandardWorker::resolveScopeId() { // we must test the original name, not the encoded one scopeid = 0; int pos = nodeName().findRev('%'); if (pos == -1) return true; TQString scopename = nodeName().mid(pos + 1); bool ok; scopeid = scopename.toInt(&ok); if (!ok) { // it's not a number // therefore, it's an interface name #ifdef HAVE_IF_NAMETOINDEX scopeid = if_nametoindex(scopename.latin1()); #else scopeid = 0; #endif } return true; } bool TDEStandardWorker::resolveService() { // find the service first bool ok; port = serviceName().toUInt(&ok); if (!ok) { // service name does not contain a port number // must be a name if (serviceName().isEmpty() || serviceName().compare(TQString::fromLatin1("*")) == 0) port = 0; else { // it's a name. We need the protocol name in order to lookup. TQCString protoname = protocolName(); if (protoname.isEmpty() && protocol()) { protoname = KResolver::protocolName(protocol()).first(); // if it's still empty... if (protoname.isEmpty()) { // lookup failed! setError(KResolver::NoName); return false; } } else protoname = "tcp"; // it's not, so we can do a port lookup int result = KResolver::servicePort(serviceName().latin1(), protoname); if (result == -1) { // lookup failed! setError(KResolver::NoName); return false; } // it worked, we have a port number port = (TQ_UINT16)result; } } // we found a port return true; } KResolver::ErrorCodes TDEStandardWorker::addUnix() { // before trying to add, see if the user wants Unix sockets if ((familyMask() & KResolver::UnixFamily) == 0) // no, Unix sockets are not wanted return KResolver::UnsupportedFamily; // now check if the requested data are good for a Unix socket if (!m_encodedName.isEmpty()) return KResolver::AddrFamily; // non local hostname if (protocol() || !protocolName().isEmpty()) return KResolver::BadFlags; // cannot have Unix sockets with protocols TQString pathname = serviceName(); if (pathname.isEmpty()) return KResolver::NoName;; // no path? if (pathname[0] != '/') // non absolute pathname // put it in /tmp pathname.prepend("/tmp/"); // tqDebug("QNoResolveWorker::addUnix(): adding Unix socket for %s", pathname.local8Bit().data()); KUnixSocketAddress sa(pathname); int socktype = socketType(); if (socktype == 0) socktype = SOCK_STREAM; // default results.append(KResolverEntry(sa, socktype, 0)); setError(KResolver::NoError); return KResolver::NoError; } bool TDEStandardWorker::resolveNumerically() { // if the NoResolve flag is active, our result from this point forward // will always be true, even if the resolution failed. // that indicates that our result is authoritative. bool wantV4 = familyMask() & KResolver::IPv4Family, wantV6 = familyMask() & KResolver::IPv6Family; if (!wantV6 && !wantV4) // no Internet address is wanted! return (flags() & KResolver::NoResolve); // now try to find results if (!resolveScopeId() || !resolveService()) return (flags() & KResolver::NoResolve); // we have scope IDs and port numbers // now try to resolve the hostname numerically KInetSocketAddress sa; setError(KResolver::NoError); sa.setHost(KIpAddress(TQString::fromLatin1(m_encodedName))); // if it failed, the length was reset to 0 bool ok = sa.length() != 0; sa.setPort(port); if (sa.ipVersion() == 6) sa.setScopeId(scopeid); int proto = protocol(); int socktype = socketType(); if (socktype == 0) socktype = SOCK_STREAM; if (ok) { // the given hostname was successfully converted to an IP address // check if the user wanted this kind of address if ((sa.ipVersion() == 4 && wantV4) || (sa.ipVersion() == 6 && wantV6)) results.append(KResolverEntry(sa, socktype, proto)); else { // Note: the address *IS* a numeric IP // but it's not of the kind the user asked for // // that means that it cannot be a Unix socket (because it's an IP) // and that means that no resolution will tell us otherwise // // This is a failed resolution setError(KResolver::AddrFamily); return true; } } else if (m_encodedName.isEmpty()) { // user wanted localhost if (flags() & KResolver::Passive) { if (wantV6) { sa.setHost(KIpAddress::anyhostV6); results.append(KResolverEntry(sa, socktype, proto)); } if (wantV4) { sa.setHost(KIpAddress::anyhostV4); results.append(KResolverEntry(sa, socktype, proto)); } } else { if (wantV6) { sa.setHost(KIpAddress::localhostV6); results.append(KResolverEntry(sa, socktype, proto)); } if (wantV4) { sa.setHost(KIpAddress::localhostV4); results.append(KResolverEntry(sa, socktype, proto)); } } ok = true; } else { // probably bad flags, since the address is not convertible without // resolution setError(KResolver::BadFlags); ok = false; } return ok || (flags() & KResolver::NoResolve); } bool TDEStandardWorker::preprocess() { // check sanity if (!sanityCheck()) return false; // this worker class can only handle known families if (familyMask() & KResolver::UnknownFamily) { setError(KResolver::UnsupportedFamily); return false; // we don't know about this } // check the socket types if (socketType() != SOCK_STREAM && socketType() != SOCK_DGRAM && socketType() != 0) { setError(KResolver::UnsupportedSocketType); return false; } // check if we can resolve all numerically // resolveNumerically always returns true if the NoResolve flag is set if (resolveNumerically() || m_encodedName.isEmpty()) { // indeed, we have resolved numerically setError(addUnix()); if (results.count()) setError(KResolver::NoError); finished(); return true; } // check if the user wants something we know about #ifdef AF_INET6 # define mask (KResolver::IPv6Family | KResolver::IPv4Family | KResolver::UnixFamily) #else # define mask (KResolver::IPv4Family | KResolver::UnixFamily) #endif if ((familyMask() & mask) == 0) // errr... nothing we know about return false; #undef mask return true; // it's ok } bool TDEStandardWorker::run() { #ifndef HAVE_GETADDRINFO // check the scope id first // since most of the resolutions won't have a scope id, this should be fast // and we won't have wasted time on services if this fails if (!resolveScopeId()) return false; // resolve the service now, before entering the blocking operation if (!resolveService()) return false; #endif // good // now we need the hostname setError(KResolver::NoName); // these are the family types that we know of struct { KResolver::SocketFamilies mask; int af; } families[] = { { KResolver::IPv4Family, AF_INET } #ifdef AF_INET6 , { KResolver::IPv6Family, AF_INET6 } #endif }; int familyCount = sizeof(families)/sizeof(families[0]); bool skipIPv6 = !hasIPv6(); resultList.setAutoDelete(true); for (int i = 0; i < familyCount; i++) if (familyMask() & families[i].mask) { #ifdef AF_INET6 if (skipIPv6 && families[i].af == AF_INET6) continue; #endif KResolverWorkerBase *worker; KResolverResults *res = new KResolverResults; resultList.append(res); #ifdef HAVE_GETADDRINFO worker = new GetAddrInfoThread(m_encodedName, serviceName().latin1(), families[i].af, flags(), res); #else worker = new GetHostByNameThread(m_encodedName, port, scopeid, families[i].af, res); #endif enqueue(worker); } // not finished return true; } bool TDEStandardWorker::postprocess() { if (results.count()) return true; // no need // now copy over what we need from the underlying results // start backwards because IPv6 was launched later (if at all) if (resultList.isEmpty()) { results.setError(KResolver::NoName); return true; } KResolverResults *rr = resultList.last(); while (rr) { if (!rr->isEmpty()) { results.setError(KResolver::NoError); KResolverResults::Iterator it = rr->begin(); for ( ; it != rr->end(); ++it) results.append(*it); } else if (results.isEmpty()) // this generated an error // copy the error code over setError(rr->error(), rr->systemError()); rr = resultList.prev(); } resultList.clear(); return true; } #ifdef HAVE_GETADDRINFO KGetAddrinfoWorker::~KGetAddrinfoWorker() { } bool KGetAddrinfoWorker::preprocess() { // getaddrinfo(3) can always handle any kind of request that makes sense if (!sanityCheck()) return false; if (flags() & KResolver::NoResolve) // oops, numeric resolution? return run(); return true; } bool KGetAddrinfoWorker::run() { // make an AF_UNSPEC getaddrinfo(3) call GetAddrInfoThread worker(m_encodedName, serviceName().latin1(), AF_UNSPEC, flags(), &results); if (!worker.run()) { if (wantThis(AF_UNIX)) { if (addUnix() == KResolver::NoError) setError(KResolver::NoError); } else setError(worker.results.error(), worker.results.systemError()); return false; } // The worker has finished working // now copy over only what we may want // keep track of any Unix-domain sockets bool seen_unix = false; KResolverResults::Iterator it = results.begin(); for ( ; it != results.end(); ) { if ((*it).family() == AF_UNIX) seen_unix = true; if (!wantThis((*it).family())) it = results.remove(it); else ++it; } if (!seen_unix) addUnix(); finished(); return true; } bool KGetAddrinfoWorker::wantThis(int family) { // tells us if the user wants a socket of this family #ifdef AF_INET6 if (family == AF_INET6 && familyMask() & KResolver::IPv6Family) return true; #endif if (family == AF_INET && familyMask() & KResolver::IPv4Family) return true; if (family == AF_UNIX && familyMask() & KResolver::UnixFamily) return true; // it's not a family we know about... if (familyMask() & KResolver::UnknownFamily) return true; return false; } #endif void KNetwork::Internal::initStandardWorkers() { //KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory); KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory); #ifdef HAVE_GETADDRINFO KResolverWorkerFactoryBase::registerNewWorker(new KResolverWorkerFactory); #endif }