/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim:set expandtab ts=4 sw=4 sts=4 cin: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nspr.h" #include "private/pprio.h" #include "nsString.h" #include "nsCRT.h" #include "nsIServiceManager.h" #include "nsIDNSService.h" #include "nsIDNSRecord.h" #include "nsISOCKSSocketInfo.h" #include "nsISocketProvider.h" #include "nsNamedPipeIOLayer.h" #include "nsSOCKSIOLayer.h" #include "nsNetCID.h" #include "nsIDNSListener.h" #include "nsICancelable.h" #include "nsThreadUtils.h" #include "nsIFile.h" #include "nsIFileProtocolHandler.h" #include "mozilla/Logging.h" #include "mozilla/net/DNS.h" #include "mozilla/Unused.h" using mozilla::LogLevel; using namespace mozilla::net; static PRDescIdentity nsSOCKSIOLayerIdentity; static PRIOMethods nsSOCKSIOLayerMethods; static bool firstTime = true; static bool ipv6Supported = true; static mozilla::LazyLogModule gSOCKSLog("SOCKS"); #define LOGDEBUG(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Debug, args) #define LOGERROR(args) MOZ_LOG(gSOCKSLog, mozilla::LogLevel::Error , args) class nsSOCKSSocketInfo : public nsISOCKSSocketInfo , public nsIDNSListener { enum State { SOCKS_INITIAL, SOCKS_DNS_IN_PROGRESS, SOCKS_DNS_COMPLETE, SOCKS_CONNECTING_TO_PROXY, SOCKS4_WRITE_CONNECT_REQUEST, SOCKS4_READ_CONNECT_RESPONSE, SOCKS5_WRITE_AUTH_REQUEST, SOCKS5_READ_AUTH_RESPONSE, SOCKS5_WRITE_USERNAME_REQUEST, SOCKS5_READ_USERNAME_RESPONSE, SOCKS5_WRITE_CONNECT_REQUEST, SOCKS5_READ_CONNECT_RESPONSE_TOP, SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, SOCKS_CONNECTED, SOCKS_FAILED }; // A buffer of 520 bytes should be enough for any request and response // in case of SOCKS4 as well as SOCKS5 static const uint32_t BUFFER_SIZE = 520; static const uint32_t MAX_HOSTNAME_LEN = 255; static const uint32_t MAX_USERNAME_LEN = 255; static const uint32_t MAX_PASSWORD_LEN = 255; public: nsSOCKSSocketInfo(); NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSISOCKSSOCKETINFO NS_DECL_NSIDNSLISTENER void Init(int32_t version, int32_t family, nsIProxyInfo *proxy, const char *destinationHost, uint32_t flags, uint32_t tlsFlags); void SetConnectTimeout(PRIntervalTime to); PRStatus DoHandshake(PRFileDesc *fd, int16_t oflags = -1); int16_t GetPollFlags() const; bool IsConnected() const { return mState == SOCKS_CONNECTED; } void ForgetFD() { mFD = nullptr; } void SetNamedPipeFD(PRFileDesc *fd) { mFD = fd; } private: virtual ~nsSOCKSSocketInfo() { ForgetFD(); HandshakeFinished(); } void HandshakeFinished(PRErrorCode err = 0); PRStatus StartDNS(PRFileDesc *fd); PRStatus ConnectToProxy(PRFileDesc *fd); void FixupAddressFamily(PRFileDesc *fd, NetAddr *proxy); PRStatus ContinueConnectingToProxy(PRFileDesc *fd, int16_t oflags); PRStatus WriteV4ConnectRequest(); PRStatus ReadV4ConnectResponse(); PRStatus WriteV5AuthRequest(); PRStatus ReadV5AuthResponse(); PRStatus WriteV5UsernameRequest(); PRStatus ReadV5UsernameResponse(); PRStatus WriteV5ConnectRequest(); PRStatus ReadV5AddrTypeAndLength(uint8_t *type, uint32_t *len); PRStatus ReadV5ConnectResponseTop(); PRStatus ReadV5ConnectResponseBottom(); uint8_t ReadUint8(); uint16_t ReadUint16(); uint32_t ReadUint32(); void ReadNetAddr(NetAddr *addr, uint16_t fam); void ReadNetPort(NetAddr *addr); void WantRead(uint32_t sz); PRStatus ReadFromSocket(PRFileDesc *fd); PRStatus WriteToSocket(PRFileDesc *fd); bool IsLocalProxy() { nsAutoCString proxyHost; mProxy->GetHost(proxyHost); return IsHostLocalTarget(proxyHost); } nsresult SetLocalProxyPath(const nsACString& aLocalProxyPath, NetAddr* aProxyAddr) { #ifdef XP_UNIX nsresult rv; MOZ_ASSERT(aProxyAddr); nsCOMPtr protocolHandler( do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr fileHandler( do_QueryInterface(protocolHandler, &rv)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr socketFile; rv = fileHandler->GetFileFromURLSpec(aLocalProxyPath, getter_AddRefs(socketFile)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsAutoCString path; if (NS_WARN_IF(NS_FAILED(rv = socketFile->GetNativePath(path)))) { return rv; } if (sizeof(aProxyAddr->local.path) <= path.Length()) { NS_WARNING("domain socket path too long."); return NS_ERROR_FAILURE; } aProxyAddr->raw.family = AF_UNIX; strcpy(aProxyAddr->local.path, path.get()); return NS_OK; #elif defined(XP_WIN) MOZ_ASSERT(aProxyAddr); if (sizeof(aProxyAddr->local.path) <= aLocalProxyPath.Length()) { NS_WARNING("pipe path too long."); return NS_ERROR_FAILURE; } aProxyAddr->raw.family = AF_LOCAL; strcpy(aProxyAddr->local.path, PromiseFlatCString(aLocalProxyPath).get()); return NS_OK; #else mozilla::Unused << aLocalProxyPath; mozilla::Unused << aProxyAddr; return NS_ERROR_NOT_IMPLEMENTED; #endif } bool SetupNamedPipeLayer(PRFileDesc *fd) { #if defined(XP_WIN) if (IsLocalProxy()) { // nsSOCKSIOLayer handshaking only works under blocking mode // unfortunately. Remember named pipe's FD to switch between modes. SetNamedPipeFD(fd->lower); return true; } #endif return false; } private: State mState; uint8_t * mData; uint8_t * mDataIoPtr; uint32_t mDataLength; uint32_t mReadOffset; uint32_t mAmountToRead; nsCOMPtr mDnsRec; nsCOMPtr mLookup; nsresult mLookupStatus; PRFileDesc *mFD; nsCString mDestinationHost; nsCOMPtr mProxy; int32_t mVersion; // SOCKS version 4 or 5 int32_t mDestinationFamily; uint32_t mFlags; uint32_t mTlsFlags; NetAddr mInternalProxyAddr; NetAddr mExternalProxyAddr; NetAddr mDestinationAddr; PRIntervalTime mTimeout; nsCString mProxyUsername; // Cache, from mProxy }; nsSOCKSSocketInfo::nsSOCKSSocketInfo() : mState(SOCKS_INITIAL) , mDataIoPtr(nullptr) , mDataLength(0) , mReadOffset(0) , mAmountToRead(0) , mLookupStatus(NS_ERROR_NOT_INITIALIZED) , mFD(nullptr) , mVersion(-1) , mDestinationFamily(AF_INET) , mFlags(0) , mTlsFlags(0) , mTimeout(PR_INTERVAL_NO_TIMEOUT) { this->mInternalProxyAddr.inet.family = 0; this->mInternalProxyAddr.inet6.family = 0; this->mInternalProxyAddr.inet6.port = 0; this->mInternalProxyAddr.inet6.flowinfo = 0; this->mInternalProxyAddr.inet6.scope_id = 0; this->mInternalProxyAddr.local.family = 0; this->mExternalProxyAddr.inet.family = 0; this->mExternalProxyAddr.inet6.family = 0; this->mExternalProxyAddr.inet6.port = 0; this->mExternalProxyAddr.inet6.flowinfo = 0; this->mExternalProxyAddr.inet6.scope_id = 0; this->mExternalProxyAddr.local.family = 0; this->mDestinationAddr.inet.family = 0; this->mDestinationAddr.inet6.family = 0; this->mDestinationAddr.inet6.port = 0; this->mDestinationAddr.inet6.flowinfo = 0; this->mDestinationAddr.inet6.scope_id = 0; this->mDestinationAddr.local.family = 0; mData = new uint8_t[BUFFER_SIZE]; mInternalProxyAddr.raw.family = AF_INET; mInternalProxyAddr.inet.ip = htonl(INADDR_ANY); mInternalProxyAddr.inet.port = htons(0); mExternalProxyAddr.raw.family = AF_INET; mExternalProxyAddr.inet.ip = htonl(INADDR_ANY); mExternalProxyAddr.inet.port = htons(0); mDestinationAddr.raw.family = AF_INET; mDestinationAddr.inet.ip = htonl(INADDR_ANY); mDestinationAddr.inet.port = htons(0); } /* Helper template class to statically check that writes to a fixed-size * buffer are not going to overflow. * * Example usage: * uint8_t real_buf[TOTAL_SIZE]; * Buffer buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf2.WriteUint8(2); * * It is possible to chain them, to limit the number of (error-prone) * intermediate variables: * auto buf = Buffer(&real_buf) * .WriteUint16(1) * .WriteUint8(2); * * Debug builds assert when intermediate variables are reused: * Buffer buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf.WriteUint8(2); // Asserts * * Strings can be written, given an explicit maximum length. * buf.WriteString(str); * * The Written() method returns how many bytes have been written so far: * Buffer buf(&real_buf); * auto buf2 = buf.WriteUint16(1); * auto buf3 = buf2.WriteUint8(2); * buf3.Written(); // returns 3. */ template class Buffer { public: Buffer() : mBuf(nullptr), mLength(0) {} explicit Buffer(uint8_t* aBuf, size_t aLength=0) : mBuf(aBuf), mLength(aLength) {} template MOZ_IMPLICIT Buffer(const Buffer& aBuf) : mBuf(aBuf.mBuf), mLength(aBuf.mLength) { static_assert(Size2 > Size, "Cannot cast buffer"); } Buffer WriteUint8(uint8_t aValue) { return Write(aValue); } Buffer WriteUint16(uint16_t aValue) { return Write(aValue); } Buffer WriteUint32(uint32_t aValue) { return Write(aValue); } Buffer WriteNetPort(const NetAddr* aAddr) { return WriteUint16(aAddr->inet.port); } Buffer WriteNetAddr(const NetAddr* aAddr) { if (aAddr->raw.family == AF_INET) { return Write(aAddr->inet.ip); } else if (aAddr->raw.family == AF_INET6) { return Write(aAddr->inet6.ip.u8); } MOZ_ASSERT_UNREACHABLE("Unknown address family"); return *this; } template Buffer WriteString(const nsACString& aStr) { if (aStr.Length() > MaxLength) { return Buffer(nullptr); } return WritePtr(aStr.Data(), aStr.Length()); } size_t Written() { MOZ_ASSERT(mBuf); return mLength; } explicit operator bool() { return !!mBuf; } private: template friend class Buffer; template Buffer Write(T& aValue) { return WritePtr(&aValue, sizeof(T)); } template Buffer WritePtr(const T* aValue, size_t aCopyLength) { static_assert(Size >= Length, "Cannot write that much"); MOZ_ASSERT(aCopyLength <= Length); MOZ_ASSERT(mBuf); memcpy(mBuf, aValue, aCopyLength); Buffer result(mBuf + aCopyLength, mLength + aCopyLength); mBuf = nullptr; mLength = 0; return result; } uint8_t* mBuf; size_t mLength; }; void nsSOCKSSocketInfo::Init(int32_t version, int32_t family, nsIProxyInfo *proxy, const char *host, uint32_t flags, uint32_t tlsFlags) { mVersion = version; mDestinationFamily = family; mProxy = proxy; mDestinationHost = host; mFlags = flags; mTlsFlags = tlsFlags; mProxy->GetUsername(mProxyUsername); // cache } NS_IMPL_ISUPPORTS(nsSOCKSSocketInfo, nsISOCKSSocketInfo, nsIDNSListener) NS_IMETHODIMP nsSOCKSSocketInfo::GetExternalProxyAddr(NetAddr * *aExternalProxyAddr) { memcpy(*aExternalProxyAddr, &mExternalProxyAddr, sizeof(NetAddr)); return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::SetExternalProxyAddr(NetAddr *aExternalProxyAddr) { memcpy(&mExternalProxyAddr, aExternalProxyAddr, sizeof(NetAddr)); return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::GetDestinationAddr(NetAddr * *aDestinationAddr) { memcpy(*aDestinationAddr, &mDestinationAddr, sizeof(NetAddr)); return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::SetDestinationAddr(NetAddr *aDestinationAddr) { memcpy(&mDestinationAddr, aDestinationAddr, sizeof(NetAddr)); return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::GetInternalProxyAddr(NetAddr * *aInternalProxyAddr) { memcpy(*aInternalProxyAddr, &mInternalProxyAddr, sizeof(NetAddr)); return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::SetInternalProxyAddr(NetAddr *aInternalProxyAddr) { memcpy(&mInternalProxyAddr, aInternalProxyAddr, sizeof(NetAddr)); return NS_OK; } // There needs to be a means of distinguishing between connection errors // that the SOCKS server reports when it rejects a connection request, and // connection errors that happen while attempting to connect to the SOCKS // server. Otherwise, Firefox will report incorrectly that the proxy server // is refusing connections when a SOCKS request is rejected by the proxy. // When a SOCKS handshake failure occurs, the PR error is set to // PR_UNKNOWN_ERROR, and the real error code is returned via the OS error. void nsSOCKSSocketInfo::HandshakeFinished(PRErrorCode err) { if (err == 0) { mState = SOCKS_CONNECTED; #if defined(XP_WIN) // Switch back to nonblocking mode after finishing handshaking. if (IsLocalProxy() && mFD) { PRSocketOptionData opt_nonblock; opt_nonblock.option = PR_SockOpt_Nonblocking; opt_nonblock.value.non_blocking = PR_TRUE; PR_SetSocketOption(mFD, &opt_nonblock); mFD = nullptr; } #endif } else { mState = SOCKS_FAILED; PR_SetError(PR_UNKNOWN_ERROR, err); } // We don't need the buffer any longer, so free it. delete [] mData; mData = nullptr; mDataIoPtr = nullptr; mDataLength = 0; mReadOffset = 0; mAmountToRead = 0; if (mLookup) { mLookup->Cancel(NS_ERROR_FAILURE); mLookup = nullptr; } } PRStatus nsSOCKSSocketInfo::StartDNS(PRFileDesc *fd) { MOZ_ASSERT(!mDnsRec && mState == SOCKS_INITIAL, "Must be in initial state to make DNS Lookup"); nsCOMPtr dns = do_GetService(NS_DNSSERVICE_CONTRACTID); if (!dns) return PR_FAILURE; nsCString proxyHost; mProxy->GetHost(proxyHost); mozilla::OriginAttributes attrs; mFD = fd; nsresult rv = dns->AsyncResolveNative(proxyHost, 0, this, mozilla::GetCurrentThreadEventTarget(), attrs, getter_AddRefs(mLookup)); if (NS_FAILED(rv)) { LOGERROR(("socks: DNS lookup for SOCKS proxy %s failed", proxyHost.get())); return PR_FAILURE; } mState = SOCKS_DNS_IN_PROGRESS; PR_SetError(PR_IN_PROGRESS_ERROR, 0); return PR_FAILURE; } NS_IMETHODIMP nsSOCKSSocketInfo::OnLookupComplete(nsICancelable *aRequest, nsIDNSRecord *aRecord, nsresult aStatus) { MOZ_ASSERT(aRequest == mLookup, "wrong DNS query"); mLookup = nullptr; mLookupStatus = aStatus; mDnsRec = aRecord; mState = SOCKS_DNS_COMPLETE; if (mFD) { ConnectToProxy(mFD); ForgetFD(); } return NS_OK; } NS_IMETHODIMP nsSOCKSSocketInfo::OnLookupByTypeComplete(nsICancelable *aRequest, nsIDNSByTypeRecord *res, nsresult aStatus) { return NS_OK; } PRStatus nsSOCKSSocketInfo::ConnectToProxy(PRFileDesc *fd) { PRStatus status; nsresult rv; MOZ_ASSERT(mState == SOCKS_DNS_COMPLETE, "Must have DNS to make connection!"); if (NS_FAILED(mLookupStatus)) { PR_SetError(PR_BAD_ADDRESS_ERROR, 0); return PR_FAILURE; } // Try socks5 if the destination addrress is IPv6 if (mVersion == 4 && mDestinationAddr.raw.family == AF_INET6) { mVersion = 5; } nsAutoCString proxyHost; mProxy->GetHost(proxyHost); int32_t proxyPort; mProxy->GetPort(&proxyPort); int32_t addresses = 0; do { if (IsLocalProxy()) { rv = SetLocalProxyPath(proxyHost, &mInternalProxyAddr); if (NS_FAILED(rv)) { LOGERROR(("socks: unable to connect to SOCKS proxy, %s", proxyHost.get())); return PR_FAILURE; } } else { if (addresses++) { mDnsRec->ReportUnusable(proxyPort); } rv = mDnsRec->GetNextAddr(proxyPort, &mInternalProxyAddr); // No more addresses to try? If so, we'll need to bail if (NS_FAILED(rv)) { LOGERROR(("socks: unable to connect to SOCKS proxy, %s", proxyHost.get())); return PR_FAILURE; } if (MOZ_LOG_TEST(gSOCKSLog, LogLevel::Debug)) { char buf[kIPv6CStrBufSize]; NetAddrToString(&mInternalProxyAddr, buf, sizeof(buf)); LOGDEBUG(("socks: trying proxy server, %s:%hu", buf, ntohs(mInternalProxyAddr.inet.port))); } } NetAddr proxy = mInternalProxyAddr; FixupAddressFamily(fd, &proxy); PRNetAddr prProxy; NetAddrToPRNetAddr(&proxy, &prProxy); status = fd->lower->methods->connect(fd->lower, &prProxy, mTimeout); if (status != PR_SUCCESS) { PRErrorCode c = PR_GetError(); // If EINPROGRESS, return now and check back later after polling if (c == PR_WOULD_BLOCK_ERROR || c == PR_IN_PROGRESS_ERROR) { mState = SOCKS_CONNECTING_TO_PROXY; return status; } else if (IsLocalProxy()) { LOGERROR(("socks: connect to domain socket failed (%d)", c)); PR_SetError(PR_CONNECT_REFUSED_ERROR, 0); mState = SOCKS_FAILED; return status; } } } while (status != PR_SUCCESS); #if defined(XP_WIN) // Switch to blocking mode during handshaking if (IsLocalProxy() && mFD) { PRSocketOptionData opt_nonblock; opt_nonblock.option = PR_SockOpt_Nonblocking; opt_nonblock.value.non_blocking = PR_FALSE; PR_SetSocketOption(mFD, &opt_nonblock); } #endif // Connected now, start SOCKS if (mVersion == 4) return WriteV4ConnectRequest(); return WriteV5AuthRequest(); } void nsSOCKSSocketInfo::FixupAddressFamily(PRFileDesc *fd, NetAddr *proxy) { int32_t proxyFamily = mInternalProxyAddr.raw.family; // Do nothing if the address family is already matched if (proxyFamily == mDestinationFamily) { return; } // If the system does not support IPv6 and the proxy address is IPv6, // We can do nothing here. if (proxyFamily == AF_INET6 && !ipv6Supported) { return; } // If the system does not support IPv6 and the destination address is // IPv6, convert IPv4 address to IPv4-mapped IPv6 address to satisfy // the emulation layer if (mDestinationFamily == AF_INET6 && !ipv6Supported) { proxy->inet6.family = AF_INET6; proxy->inet6.port = mInternalProxyAddr.inet.port; uint8_t *proxyp = proxy->inet6.ip.u8; memset(proxyp, 0, 10); memset(proxyp + 10, 0xff, 2); memcpy(proxyp + 12,(char *) &mInternalProxyAddr.inet.ip, 4); // mDestinationFamily should not be updated return; } // There's no PR_NSPR_IO_LAYER required when using named pipe, // we simply ignore the TCP family here. if (SetupNamedPipeLayer(fd)) { return; } // Get an OS native handle from a specified FileDesc PROsfd osfd = PR_FileDesc2NativeHandle(fd); if (osfd == -1) { return; } // Create a new FileDesc with a specified family PRFileDesc *tmpfd = PR_OpenTCPSocket(proxyFamily); if (!tmpfd) { return; } PROsfd newsd = PR_FileDesc2NativeHandle(tmpfd); if (newsd == -1) { PR_Close(tmpfd); return; } // Must succeed because PR_FileDesc2NativeHandle succeeded fd = PR_GetIdentitiesLayer(fd, PR_NSPR_IO_LAYER); MOZ_ASSERT(fd); // Swap OS native handles PR_ChangeFileDescNativeHandle(fd, newsd); PR_ChangeFileDescNativeHandle(tmpfd, osfd); // Close temporary FileDesc which is now associated with // old OS native handle PR_Close(tmpfd); mDestinationFamily = proxyFamily; } PRStatus nsSOCKSSocketInfo::ContinueConnectingToProxy(PRFileDesc *fd, int16_t oflags) { PRStatus status; MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY, "Continuing connection in wrong state!"); LOGDEBUG(("socks: continuing connection to proxy")); status = fd->lower->methods->connectcontinue(fd->lower, oflags); if (status != PR_SUCCESS) { PRErrorCode c = PR_GetError(); if (c != PR_WOULD_BLOCK_ERROR && c != PR_IN_PROGRESS_ERROR) { // A connection failure occured, try another address mState = SOCKS_DNS_COMPLETE; return ConnectToProxy(fd); } // We're still connecting return PR_FAILURE; } // Connected now, start SOCKS if (mVersion == 4) return WriteV4ConnectRequest(); return WriteV5AuthRequest(); } PRStatus nsSOCKSSocketInfo::WriteV4ConnectRequest() { if (mProxyUsername.Length() > MAX_USERNAME_LEN) { LOGERROR(("socks username is too long")); HandshakeFinished(PR_UNKNOWN_ERROR); return PR_FAILURE; } NetAddr *addr = &mDestinationAddr; int32_t proxy_resolve; MOZ_ASSERT(mState == SOCKS_CONNECTING_TO_PROXY, "Invalid state!"); proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST; mDataLength = 0; mState = SOCKS4_WRITE_CONNECT_REQUEST; LOGDEBUG(("socks4: sending connection request (socks4a resolve? %s)", proxy_resolve? "yes" : "no")); // Send a SOCKS 4 connect request. auto buf = Buffer(mData) .WriteUint8(0x04) // version -- 4 .WriteUint8(0x01) // command -- connect .WriteNetPort(addr); // We don't have anything more to write after the if, so we can // use a buffer with no further writes allowed. Buffer<0> buf3; if (proxy_resolve) { // Add the full name, null-terminated, to the request // according to SOCKS 4a. A fake IP address, with the first // four bytes set to 0 and the last byte set to something other // than 0, is used to notify the proxy that this is a SOCKS 4a // request. This request type works for Tor and perhaps others. // Passwords not supported by V4. auto buf2 = buf.WriteUint32(htonl(0x00000001)) // Fake IP .WriteString(mProxyUsername) .WriteUint8(0x00) // Null-terminate username .WriteString(mDestinationHost); // Hostname if (!buf2) { LOGERROR(("socks4: destination host name is too long!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } buf3 = buf2.WriteUint8(0x00); } else if (addr->raw.family == AF_INET) { // Passwords not supported by V4. buf3 = buf.WriteNetAddr(addr) // Add the IPv4 address .WriteString(mProxyUsername) .WriteUint8(0x00); // Null-terminate username } else { LOGERROR(("socks: SOCKS 4 can only handle IPv4 addresses!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } mDataLength = buf3.Written(); return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV4ConnectResponse() { MOZ_ASSERT(mState == SOCKS4_READ_CONNECT_RESPONSE, "Handling SOCKS 4 connection reply in wrong state!"); MOZ_ASSERT(mDataLength == 8, "SOCKS 4 connection reply must be 8 bytes!"); LOGDEBUG(("socks4: checking connection reply")); if (ReadUint8() != 0x00) { LOGERROR(("socks4: wrong connection reply")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } // See if our connection request was granted if (ReadUint8() == 90) { LOGDEBUG(("socks4: connection successful!")); HandshakeFinished(); return PR_SUCCESS; } LOGERROR(("socks4: unable to connect")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } PRStatus nsSOCKSSocketInfo::WriteV5AuthRequest() { MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!"); mDataLength = 0; mState = SOCKS5_WRITE_AUTH_REQUEST; // Send an initial SOCKS 5 greeting LOGDEBUG(("socks5: sending auth methods")); mDataLength = Buffer(mData) .WriteUint8(0x05) // version -- 5 .WriteUint8(0x01) // # of auth methods -- 1 // Use authenticate iff we have a proxy username. .WriteUint8(mProxyUsername.IsEmpty() ? 0x00 : 0x02) .Written(); return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV5AuthResponse() { MOZ_ASSERT(mState == SOCKS5_READ_AUTH_RESPONSE, "Handling SOCKS 5 auth method reply in wrong state!"); MOZ_ASSERT(mDataLength == 2, "SOCKS 5 auth method reply must be 2 bytes!"); LOGDEBUG(("socks5: checking auth method reply")); // Check version number if (ReadUint8() != 0x05) { LOGERROR(("socks5: unexpected version in the reply")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } // Make sure our authentication choice was accepted, // and continue accordingly uint8_t authMethod = ReadUint8(); if (mProxyUsername.IsEmpty() && authMethod == 0x00) { // no auth LOGDEBUG(("socks5: server allows connection without authentication")); return WriteV5ConnectRequest(); } else if (!mProxyUsername.IsEmpty() && authMethod == 0x02) { // username/pw LOGDEBUG(("socks5: auth method accepted by server")); return WriteV5UsernameRequest(); } else { // 0xFF signals error LOGERROR(("socks5: server did not accept our authentication method")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } } PRStatus nsSOCKSSocketInfo::WriteV5UsernameRequest() { MOZ_ASSERT(mVersion == 5, "SOCKS version must be 5!"); if (mProxyUsername.Length() > MAX_USERNAME_LEN) { LOGERROR(("socks username is too long")); HandshakeFinished(PR_UNKNOWN_ERROR); return PR_FAILURE; } nsCString password; mProxy->GetPassword(password); if (password.Length() > MAX_PASSWORD_LEN) { LOGERROR(("socks password is too long")); HandshakeFinished(PR_UNKNOWN_ERROR); return PR_FAILURE; } mDataLength = 0; mState = SOCKS5_WRITE_USERNAME_REQUEST; // RFC 1929 Username/password auth for SOCKS 5 LOGDEBUG(("socks5: sending username and password")); mDataLength = Buffer(mData) .WriteUint8(0x01) // version 1 (not 5) .WriteUint8(mProxyUsername.Length()) // username length .WriteString(mProxyUsername) // username .WriteUint8(password.Length()) // password length .WriteString(password) // password. WARNING: Sent unencrypted! .Written(); return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV5UsernameResponse() { MOZ_ASSERT(mState == SOCKS5_READ_USERNAME_RESPONSE, "Handling SOCKS 5 username/password reply in wrong state!"); MOZ_ASSERT(mDataLength == 2, "SOCKS 5 username reply must be 2 bytes"); // Check version number, must be 1 (not 5) if (ReadUint8() != 0x01) { LOGERROR(("socks5: unexpected version in the reply")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } // Check whether username/password were accepted if (ReadUint8() != 0x00) { // 0 = success LOGERROR(("socks5: username/password not accepted")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } LOGDEBUG(("socks5: username/password accepted by server")); return WriteV5ConnectRequest(); } PRStatus nsSOCKSSocketInfo::WriteV5ConnectRequest() { // Send SOCKS 5 connect request NetAddr *addr = &mDestinationAddr; int32_t proxy_resolve; proxy_resolve = mFlags & nsISocketProvider::PROXY_RESOLVES_HOST; LOGDEBUG(("socks5: sending connection request (socks5 resolve? %s)", proxy_resolve? "yes" : "no")); mDataLength = 0; mState = SOCKS5_WRITE_CONNECT_REQUEST; auto buf = Buffer(mData) .WriteUint8(0x05) // version -- 5 .WriteUint8(0x01) // command -- connect .WriteUint8(0x00); // reserved // We're writing a net port after the if, so we need a buffer allowing // to write that much. Buffer buf2; // Add the address to the SOCKS 5 request. SOCKS 5 supports several // address types, so we pick the one that works best for us. if (proxy_resolve) { // Add the host name. Only a single byte is used to store the length, // so we must prevent long names from being used. buf2 = buf.WriteUint8(0x03) // addr type -- domainname .WriteUint8(mDestinationHost.Length()) // name length .WriteString(mDestinationHost); // Hostname if (!buf2) { LOGERROR(("socks5: destination host name is too long!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } } else if (addr->raw.family == AF_INET) { buf2 = buf.WriteUint8(0x01) // addr type -- IPv4 .WriteNetAddr(addr); } else if (addr->raw.family == AF_INET6) { buf2 = buf.WriteUint8(0x04) // addr type -- IPv6 .WriteNetAddr(addr); } else { LOGERROR(("socks5: destination address of unknown type!")); HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } auto buf3 = buf2.WriteNetPort(addr); // port mDataLength = buf3.Written(); return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV5AddrTypeAndLength(uint8_t *type, uint32_t *len) { MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP || mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, "Invalid state!"); MOZ_ASSERT(mDataLength >= 5, "SOCKS 5 connection reply must be at least 5 bytes!"); // Seek to the address location mReadOffset = 3; *type = ReadUint8(); switch (*type) { case 0x01: // ipv4 *len = 4 - 1; break; case 0x04: // ipv6 *len = 16 - 1; break; case 0x03: // fqdn *len = ReadUint8(); break; default: // wrong address type LOGERROR(("socks5: wrong address type in connection reply!")); return PR_FAILURE; } return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseTop() { uint8_t res; uint32_t len; MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_TOP, "Invalid state!"); MOZ_ASSERT(mDataLength == 5, "SOCKS 5 connection reply must be exactly 5 bytes!"); LOGDEBUG(("socks5: checking connection reply")); // Check version number if (ReadUint8() != 0x05) { LOGERROR(("socks5: unexpected version in the reply")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } // Check response res = ReadUint8(); if (res != 0x00) { PRErrorCode c = PR_CONNECT_REFUSED_ERROR; switch (res) { case 0x01: LOGERROR(("socks5: connect failed: " "01, General SOCKS server failure.")); break; case 0x02: LOGERROR(("socks5: connect failed: " "02, Connection not allowed by ruleset.")); break; case 0x03: LOGERROR(("socks5: connect failed: 03, Network unreachable.")); c = PR_NETWORK_UNREACHABLE_ERROR; break; case 0x04: LOGERROR(("socks5: connect failed: 04, Host unreachable.")); c = PR_BAD_ADDRESS_ERROR; break; case 0x05: LOGERROR(("socks5: connect failed: 05, Connection refused.")); break; case 0x06: LOGERROR(("socks5: connect failed: 06, TTL expired.")); c = PR_CONNECT_TIMEOUT_ERROR; break; case 0x07: LOGERROR(("socks5: connect failed: " "07, Command not supported.")); break; case 0x08: LOGERROR(("socks5: connect failed: " "08, Address type not supported.")); c = PR_BAD_ADDRESS_ERROR; break; default: LOGERROR(("socks5: connect failed.")); break; } HandshakeFinished(c); return PR_FAILURE; } if (ReadV5AddrTypeAndLength(&res, &len) != PR_SUCCESS) { HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } mState = SOCKS5_READ_CONNECT_RESPONSE_BOTTOM; WantRead(len + 2); return PR_SUCCESS; } PRStatus nsSOCKSSocketInfo::ReadV5ConnectResponseBottom() { uint8_t type; uint32_t len; MOZ_ASSERT(mState == SOCKS5_READ_CONNECT_RESPONSE_BOTTOM, "Invalid state!"); if (ReadV5AddrTypeAndLength(&type, &len) != PR_SUCCESS) { HandshakeFinished(PR_BAD_ADDRESS_ERROR); return PR_FAILURE; } MOZ_ASSERT(mDataLength == 7+len, "SOCKS 5 unexpected length of connection reply!"); LOGDEBUG(("socks5: loading source addr and port")); // Read what the proxy says is our source address switch (type) { case 0x01: // ipv4 ReadNetAddr(&mExternalProxyAddr, AF_INET); break; case 0x04: // ipv6 ReadNetAddr(&mExternalProxyAddr, AF_INET6); break; case 0x03: // fqdn (skip) mReadOffset += len; mExternalProxyAddr.raw.family = AF_INET; break; } ReadNetPort(&mExternalProxyAddr); LOGDEBUG(("socks5: connected!")); HandshakeFinished(); return PR_SUCCESS; } void nsSOCKSSocketInfo::SetConnectTimeout(PRIntervalTime to) { mTimeout = to; } PRStatus nsSOCKSSocketInfo::DoHandshake(PRFileDesc *fd, int16_t oflags) { LOGDEBUG(("socks: DoHandshake(), state = %d", mState)); switch (mState) { case SOCKS_INITIAL: if (IsLocalProxy()) { mState = SOCKS_DNS_COMPLETE; mLookupStatus = NS_OK; return ConnectToProxy(fd); } return StartDNS(fd); case SOCKS_DNS_IN_PROGRESS: PR_SetError(PR_IN_PROGRESS_ERROR, 0); return PR_FAILURE; case SOCKS_DNS_COMPLETE: return ConnectToProxy(fd); case SOCKS_CONNECTING_TO_PROXY: return ContinueConnectingToProxy(fd, oflags); case SOCKS4_WRITE_CONNECT_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE; WantRead(8); mState = SOCKS4_READ_CONNECT_RESPONSE; return PR_SUCCESS; case SOCKS4_READ_CONNECT_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV4ConnectResponse(); case SOCKS5_WRITE_AUTH_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE; WantRead(2); mState = SOCKS5_READ_AUTH_RESPONSE; return PR_SUCCESS; case SOCKS5_READ_AUTH_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5AuthResponse(); case SOCKS5_WRITE_USERNAME_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE; WantRead(2); mState = SOCKS5_READ_USERNAME_RESPONSE; return PR_SUCCESS; case SOCKS5_READ_USERNAME_RESPONSE: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5UsernameResponse(); case SOCKS5_WRITE_CONNECT_REQUEST: if (WriteToSocket(fd) != PR_SUCCESS) return PR_FAILURE; // The SOCKS 5 response to the connection request is variable // length. First, we'll read enough to tell how long the response // is, and will read the rest later. WantRead(5); mState = SOCKS5_READ_CONNECT_RESPONSE_TOP; return PR_SUCCESS; case SOCKS5_READ_CONNECT_RESPONSE_TOP: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5ConnectResponseTop(); case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: if (ReadFromSocket(fd) != PR_SUCCESS) return PR_FAILURE; return ReadV5ConnectResponseBottom(); case SOCKS_CONNECTED: LOGERROR(("socks: already connected")); HandshakeFinished(PR_IS_CONNECTED_ERROR); return PR_FAILURE; case SOCKS_FAILED: LOGERROR(("socks: already failed")); return PR_FAILURE; } LOGERROR(("socks: executing handshake in invalid state, %d", mState)); HandshakeFinished(PR_INVALID_STATE_ERROR); return PR_FAILURE; } int16_t nsSOCKSSocketInfo::GetPollFlags() const { switch (mState) { case SOCKS_DNS_IN_PROGRESS: case SOCKS_DNS_COMPLETE: case SOCKS_CONNECTING_TO_PROXY: return PR_POLL_EXCEPT | PR_POLL_WRITE; case SOCKS4_WRITE_CONNECT_REQUEST: case SOCKS5_WRITE_AUTH_REQUEST: case SOCKS5_WRITE_USERNAME_REQUEST: case SOCKS5_WRITE_CONNECT_REQUEST: return PR_POLL_WRITE; case SOCKS4_READ_CONNECT_RESPONSE: case SOCKS5_READ_AUTH_RESPONSE: case SOCKS5_READ_USERNAME_RESPONSE: case SOCKS5_READ_CONNECT_RESPONSE_TOP: case SOCKS5_READ_CONNECT_RESPONSE_BOTTOM: return PR_POLL_READ; default: break; } return 0; } inline uint8_t nsSOCKSSocketInfo::ReadUint8() { uint8_t rv; MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint8_t!"); rv = mData[mReadOffset]; mReadOffset += sizeof(rv); return rv; } inline uint16_t nsSOCKSSocketInfo::ReadUint16() { uint16_t rv; MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint16_t!"); memcpy(&rv, mData + mReadOffset, sizeof(rv)); mReadOffset += sizeof(rv); return rv; } inline uint32_t nsSOCKSSocketInfo::ReadUint32() { uint32_t rv; MOZ_ASSERT(mReadOffset + sizeof(rv) <= mDataLength, "Not enough space to pop a uint32_t!"); memcpy(&rv, mData + mReadOffset, sizeof(rv)); mReadOffset += sizeof(rv); return rv; } void nsSOCKSSocketInfo::ReadNetAddr(NetAddr *addr, uint16_t fam) { uint32_t amt = 0; const uint8_t *ip = mData + mReadOffset; addr->raw.family = fam; if (fam == AF_INET) { amt = sizeof(addr->inet.ip); MOZ_ASSERT(mReadOffset + amt <= mDataLength, "Not enough space to pop an ipv4 addr!"); memcpy(&addr->inet.ip, ip, amt); } else if (fam == AF_INET6) { amt = sizeof(addr->inet6.ip.u8); MOZ_ASSERT(mReadOffset + amt <= mDataLength, "Not enough space to pop an ipv6 addr!"); memcpy(addr->inet6.ip.u8, ip, amt); } mReadOffset += amt; } void nsSOCKSSocketInfo::ReadNetPort(NetAddr *addr) { addr->inet.port = ReadUint16(); } void nsSOCKSSocketInfo::WantRead(uint32_t sz) { MOZ_ASSERT(mDataIoPtr == nullptr, "WantRead() called while I/O already in progress!"); MOZ_ASSERT(mDataLength + sz <= BUFFER_SIZE, "Can't read that much data!"); mAmountToRead = sz; } PRStatus nsSOCKSSocketInfo::ReadFromSocket(PRFileDesc *fd) { int32_t rc; const uint8_t *end; if (!mAmountToRead) { LOGDEBUG(("socks: ReadFromSocket(), nothing to do")); return PR_SUCCESS; } if (!mDataIoPtr) { mDataIoPtr = mData + mDataLength; mDataLength += mAmountToRead; } end = mData + mDataLength; while (mDataIoPtr < end) { rc = PR_Read(fd, mDataIoPtr, end - mDataIoPtr); if (rc <= 0) { if (rc == 0) { LOGERROR(("socks: proxy server closed connection")); HandshakeFinished(PR_CONNECT_REFUSED_ERROR); return PR_FAILURE; } else if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { LOGDEBUG(("socks: ReadFromSocket(), want read")); } break; } mDataIoPtr += rc; } LOGDEBUG(("socks: ReadFromSocket(), have %u bytes total", unsigned(mDataIoPtr - mData))); if (mDataIoPtr == end) { mDataIoPtr = nullptr; mAmountToRead = 0; mReadOffset = 0; return PR_SUCCESS; } return PR_FAILURE; } PRStatus nsSOCKSSocketInfo::WriteToSocket(PRFileDesc *fd) { int32_t rc; const uint8_t *end; if (!mDataLength) { LOGDEBUG(("socks: WriteToSocket(), nothing to do")); return PR_SUCCESS; } if (!mDataIoPtr) mDataIoPtr = mData; end = mData + mDataLength; while (mDataIoPtr < end) { rc = PR_Write(fd, mDataIoPtr, end - mDataIoPtr); if (rc < 0) { if (PR_GetError() == PR_WOULD_BLOCK_ERROR) { LOGDEBUG(("socks: WriteToSocket(), want write")); } break; } mDataIoPtr += rc; } if (mDataIoPtr == end) { mDataIoPtr = nullptr; mDataLength = 0; mReadOffset = 0; return PR_SUCCESS; } return PR_FAILURE; } static PRStatus nsSOCKSIOLayerConnect(PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime to) { PRStatus status; NetAddr dst; nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; if (info == nullptr) return PR_FAILURE; if (addr->raw.family == PR_AF_INET6 && PR_IsNetAddrType(addr, PR_IpAddrV4Mapped)) { const uint8_t *srcp; LOGDEBUG(("socks: converting ipv4-mapped ipv6 address to ipv4")); // copied from _PR_ConvertToIpv4NetAddr() dst.raw.family = AF_INET; dst.inet.ip = htonl(INADDR_ANY); dst.inet.port = htons(0); srcp = addr->ipv6.ip.pr_s6_addr; memcpy(&dst.inet.ip, srcp + 12, 4); dst.inet.family = AF_INET; dst.inet.port = addr->ipv6.port; } else { memcpy(&dst, addr, sizeof(dst)); } info->SetDestinationAddr(&dst); info->SetConnectTimeout(to); do { status = info->DoHandshake(fd, -1); } while (status == PR_SUCCESS && !info->IsConnected()); return status; } static PRStatus nsSOCKSIOLayerConnectContinue(PRFileDesc *fd, int16_t oflags) { PRStatus status; nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; if (info == nullptr) return PR_FAILURE; do { status = info->DoHandshake(fd, oflags); } while (status == PR_SUCCESS && !info->IsConnected()); return status; } static int16_t nsSOCKSIOLayerPoll(PRFileDesc *fd, int16_t in_flags, int16_t *out_flags) { nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; if (info == nullptr) return PR_FAILURE; if (!info->IsConnected()) { *out_flags = 0; return info->GetPollFlags(); } return fd->lower->methods->poll(fd->lower, in_flags, out_flags); } static PRStatus nsSOCKSIOLayerClose(PRFileDesc *fd) { nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; PRDescIdentity id = PR_GetLayersIdentity(fd); if (info && id == nsSOCKSIOLayerIdentity) { info->ForgetFD(); NS_RELEASE(info); fd->identity = PR_INVALID_IO_LAYER; } return fd->lower->methods->close(fd->lower); } static PRFileDesc* nsSOCKSIOLayerAccept(PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout) { // TODO: implement SOCKS support for accept return fd->lower->methods->accept(fd->lower, addr, timeout); } static int32_t nsSOCKSIOLayerAcceptRead(PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr, void *buf, int32_t amount, PRIntervalTime timeout) { // TODO: implement SOCKS support for accept, then read from it return sd->lower->methods->acceptread(sd->lower, nd, raddr, buf, amount, timeout); } static PRStatus nsSOCKSIOLayerBind(PRFileDesc *fd, const PRNetAddr *addr) { // TODO: implement SOCKS support for bind (very similar to connect) return fd->lower->methods->bind(fd->lower, addr); } static PRStatus nsSOCKSIOLayerGetName(PRFileDesc *fd, PRNetAddr *addr) { nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; if (info != nullptr && addr != nullptr) { NetAddr temp; NetAddr *tempPtr = &temp; if (info->GetExternalProxyAddr(&tempPtr) == NS_OK) { NetAddrToPRNetAddr(tempPtr, addr); return PR_SUCCESS; } } return PR_FAILURE; } static PRStatus nsSOCKSIOLayerGetPeerName(PRFileDesc *fd, PRNetAddr *addr) { nsSOCKSSocketInfo * info = (nsSOCKSSocketInfo*) fd->secret; if (info != nullptr && addr != nullptr) { NetAddr temp; NetAddr *tempPtr = &temp; if (info->GetDestinationAddr(&tempPtr) == NS_OK) { NetAddrToPRNetAddr(tempPtr, addr); return PR_SUCCESS; } } return PR_FAILURE; } static PRStatus nsSOCKSIOLayerListen(PRFileDesc *fd, int backlog) { // TODO: implement SOCKS support for listen return fd->lower->methods->listen(fd->lower, backlog); } // add SOCKS IO layer to an existing socket nsresult nsSOCKSIOLayerAddToSocket(int32_t family, const char *host, int32_t port, nsIProxyInfo *proxy, int32_t socksVersion, uint32_t flags, uint32_t tlsFlags, PRFileDesc *fd, nsISupports** info) { NS_ENSURE_TRUE((socksVersion == 4) || (socksVersion == 5), NS_ERROR_NOT_INITIALIZED); if (firstTime) { //XXX hack until NSPR provides an official way to detect system IPv6 // support (bug 388519) PRFileDesc *tmpfd = PR_OpenTCPSocket(PR_AF_INET6); if (!tmpfd) { ipv6Supported = false; } else { // If the system does not support IPv6, NSPR will push // IPv6-to-IPv4 emulation layer onto the native layer ipv6Supported = PR_GetIdentitiesLayer(tmpfd, PR_NSPR_IO_LAYER) == tmpfd; PR_Close(tmpfd); } nsSOCKSIOLayerIdentity = PR_GetUniqueIdentity("SOCKS layer"); nsSOCKSIOLayerMethods = *PR_GetDefaultIOMethods(); nsSOCKSIOLayerMethods.connect = nsSOCKSIOLayerConnect; nsSOCKSIOLayerMethods.connectcontinue = nsSOCKSIOLayerConnectContinue; nsSOCKSIOLayerMethods.poll = nsSOCKSIOLayerPoll; nsSOCKSIOLayerMethods.bind = nsSOCKSIOLayerBind; nsSOCKSIOLayerMethods.acceptread = nsSOCKSIOLayerAcceptRead; nsSOCKSIOLayerMethods.getsockname = nsSOCKSIOLayerGetName; nsSOCKSIOLayerMethods.getpeername = nsSOCKSIOLayerGetPeerName; nsSOCKSIOLayerMethods.accept = nsSOCKSIOLayerAccept; nsSOCKSIOLayerMethods.listen = nsSOCKSIOLayerListen; nsSOCKSIOLayerMethods.close = nsSOCKSIOLayerClose; firstTime = false; } LOGDEBUG(("Entering nsSOCKSIOLayerAddToSocket().")); PRFileDesc *layer; PRStatus rv; layer = PR_CreateIOLayerStub(nsSOCKSIOLayerIdentity, &nsSOCKSIOLayerMethods); if (! layer) { LOGERROR(("PR_CreateIOLayerStub() failed.")); return NS_ERROR_FAILURE; } nsSOCKSSocketInfo * infoObject = new nsSOCKSSocketInfo(); if (!infoObject) { // clean up IOLayerStub LOGERROR(("Failed to create nsSOCKSSocketInfo().")); PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). return NS_ERROR_FAILURE; } NS_ADDREF(infoObject); infoObject->Init(socksVersion, family, proxy, host, flags, tlsFlags); layer->secret = (PRFilePrivate*) infoObject; PRDescIdentity fdIdentity = PR_GetLayersIdentity(fd); #if defined(XP_WIN) if (fdIdentity == mozilla::net::nsNamedPipeLayerIdentity) { // remember named pipe fd on the info object so that we can switch // blocking and non-blocking mode on the pipe later. infoObject->SetNamedPipeFD(fd); } #endif rv = PR_PushIOLayer(fd, fdIdentity, layer); if (rv == PR_FAILURE) { LOGERROR(("PR_PushIOLayer() failed. rv = %x.", rv)); NS_RELEASE(infoObject); PR_Free(layer); // PR_CreateIOLayerStub() uses PR_Malloc(). return NS_ERROR_FAILURE; } *info = static_cast(infoObject); NS_ADDREF(*info); return NS_OK; } bool IsHostLocalTarget(const nsACString& aHost) { #if defined(XP_UNIX) return StringBeginsWith(aHost, NS_LITERAL_CSTRING("file:")); #elif defined(XP_WIN) return IsNamedPipePath(aHost); #else return false; #endif // XP_UNIX }