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