mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 22:01:30 +00:00
1622 lines
48 KiB
C++
1622 lines
48 KiB
C++
/* -*- 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<nsIProtocolHandler> protocolHandler(
|
|
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFileProtocolHandler> fileHandler(
|
|
do_QueryInterface(protocolHandler, &rv));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> 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<nsIDNSRecord> mDnsRec;
|
|
nsCOMPtr<nsICancelable> mLookup;
|
|
nsresult mLookupStatus;
|
|
PRFileDesc *mFD;
|
|
|
|
nsCString mDestinationHost;
|
|
nsCOMPtr<nsIProxyInfo> 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)
|
|
, mFD(nullptr)
|
|
, mVersion(-1)
|
|
, mDestinationFamily(AF_INET)
|
|
, mFlags(0)
|
|
, mTlsFlags(0)
|
|
, mTimeout(PR_INTERVAL_NO_TIMEOUT)
|
|
{
|
|
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<TOTAL_SIZE> 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<TOTAL_SIZE>(&real_buf)
|
|
* .WriteUint16(1)
|
|
* .WriteUint8(2);
|
|
*
|
|
* Debug builds assert when intermediate variables are reused:
|
|
* Buffer<TOTAL_SIZE> 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<MAX_STRING_LENGTH>(str);
|
|
*
|
|
* The Written() method returns how many bytes have been written so far:
|
|
* Buffer<TOTAL_SIZE> buf(&real_buf);
|
|
* auto buf2 = buf.WriteUint16(1);
|
|
* auto buf3 = buf2.WriteUint8(2);
|
|
* buf3.Written(); // returns 3.
|
|
*/
|
|
template <size_t Size>
|
|
class Buffer
|
|
{
|
|
public:
|
|
Buffer() : mBuf(nullptr), mLength(0) {}
|
|
|
|
explicit Buffer(uint8_t* aBuf, size_t aLength=0)
|
|
: mBuf(aBuf), mLength(aLength) {}
|
|
|
|
template <size_t Size2>
|
|
MOZ_IMPLICIT Buffer(const Buffer<Size2>& aBuf) : mBuf(aBuf.mBuf), mLength(aBuf.mLength) {
|
|
static_assert(Size2 > Size, "Cannot cast buffer");
|
|
}
|
|
|
|
Buffer<Size - sizeof(uint8_t)> WriteUint8(uint8_t aValue) {
|
|
return Write(aValue);
|
|
}
|
|
|
|
Buffer<Size - sizeof(uint16_t)> WriteUint16(uint16_t aValue) {
|
|
return Write(aValue);
|
|
}
|
|
|
|
Buffer<Size - sizeof(uint32_t)> WriteUint32(uint32_t aValue) {
|
|
return Write(aValue);
|
|
}
|
|
|
|
Buffer<Size - sizeof(uint16_t)> WriteNetPort(const NetAddr* aAddr) {
|
|
return WriteUint16(aAddr->inet.port);
|
|
}
|
|
|
|
Buffer<Size - sizeof(IPv6Addr)> 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);
|
|
}
|
|
NS_NOTREACHED("Unknown address family");
|
|
return *this;
|
|
}
|
|
|
|
template <size_t MaxLength>
|
|
Buffer<Size - MaxLength> WriteString(const nsACString& aStr) {
|
|
if (aStr.Length() > MaxLength) {
|
|
return Buffer<Size - MaxLength>(nullptr);
|
|
}
|
|
return WritePtr<char, MaxLength>(aStr.Data(), aStr.Length());
|
|
}
|
|
|
|
size_t Written() {
|
|
MOZ_ASSERT(mBuf);
|
|
return mLength;
|
|
}
|
|
|
|
explicit operator bool() { return !!mBuf; }
|
|
private:
|
|
template <size_t Size2>
|
|
friend class Buffer;
|
|
|
|
template <typename T>
|
|
Buffer<Size - sizeof(T)> Write(T& aValue) {
|
|
return WritePtr<T, sizeof(T)>(&aValue, sizeof(T));
|
|
}
|
|
|
|
template <typename T, size_t Length>
|
|
Buffer<Size - Length> 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<Size - Length> 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<nsIDNSService> 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;
|
|
}
|
|
|
|
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<BUFFER_SIZE>(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<MAX_USERNAME_LEN>(mProxyUsername)
|
|
.WriteUint8(0x00) // Null-terminate username
|
|
.WriteString<MAX_HOSTNAME_LEN>(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<MAX_USERNAME_LEN>(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<BUFFER_SIZE>(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<BUFFER_SIZE>(mData)
|
|
.WriteUint8(0x01) // version 1 (not 5)
|
|
.WriteUint8(mProxyUsername.Length()) // username length
|
|
.WriteString<MAX_USERNAME_LEN>(mProxyUsername) // username
|
|
.WriteUint8(password.Length()) // password length
|
|
.WriteString<MAX_PASSWORD_LEN>(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<BUFFER_SIZE>(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<sizeof(uint16_t)> 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<MAX_HOSTNAME_LEN>(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<nsISOCKSSocketInfo*>(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
|
|
}
|