3731 lines
102 KiB
C++
3731 lines
102 KiB
C++
/**
|
|
* ysockschan.cpp
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* SOCKS channel
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2006 Null Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <yatephone.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace TelEngine;
|
|
namespace { // anonymous
|
|
|
|
class SOCKSEndpointDef; // A SOCKS endpoint definition
|
|
class SOCKSPacket; // A SOCKS protocol packet
|
|
class SOCKSConn; // SOCKS TCP connection
|
|
class SOCKSListener; // A socket listener
|
|
class SOCKSEngine; // The SOCKS engine
|
|
|
|
|
|
class YSocksEngine;
|
|
class YSocksWrapper; // A link between a data source and/or
|
|
// consumer and a SOCKS connection
|
|
class YSocksWrapperWorker; // Worker thread for an YSocksWrapper
|
|
class YSocksSource; // A data source
|
|
class YSocksConsumer; // A data consumer
|
|
class YSocksListenerThread; // A socket listener thread
|
|
class YSocksProcessThread; // A connection processor thread
|
|
class YSocksPlugin;
|
|
|
|
/*
|
|
SOCKS packet formats:
|
|
|
|
AuthMethods - RFC 1928 Section 3
|
|
|VER | NMETHODS | METHODS |
|
|
| 1 | 1 | 1 to 255 |
|
|
AuthReply - RFC 1928 Section 3
|
|
|VER | METHOD |
|
|
| 1 | 1 |
|
|
UnamePwdRequest - RFC 1929 Section 2
|
|
|VER | ULEN | UNAME | PLEN | PASSWD |
|
|
| 1 | 1 | 1 to 255 | 1 | 1 to 255 |
|
|
UnamePwdReply - RFC 1929 Section 2
|
|
|VER | STATUS |
|
|
| 1 | 1 |
|
|
Request - RFC 1928 Section 4
|
|
|VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT |
|
|
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
|
Reply - RFC 1928 Section 6
|
|
|VER | REP | RSV | ATYP | BND.ADDR | BND.PORT |
|
|
| 1 | 1 | X'00' | 1 | Variable | 2 |
|
|
DST.ADDR and BND.ADDR - RFC 1928 Section 5
|
|
For Domain type, the first byte is the field length
|
|
*/
|
|
|
|
/**
|
|
* This class holds data describing a SOCKS endpoint such as
|
|
* type, address, port, authentication
|
|
* @short A SOCKS endpoint definition
|
|
*/
|
|
class SOCKSEndpointDef : public RefObject
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
* @param name Endpoint name (id)
|
|
* @param proxy True if this is a proxy, false if it is a server
|
|
* @param address The address used by the endpoint
|
|
* @param port The port used by the endpoint
|
|
* @param external Exetrnal (public) address of the endpoint
|
|
* @param uname Username used to authenticate
|
|
* @param pwd Username used to authenticate
|
|
*/
|
|
SOCKSEndpointDef(const char* name, bool proxy, const char* address, int port,
|
|
const char* external = 0, const char* uname = 0, const char* pwd = 0);
|
|
|
|
/**
|
|
* Constructor
|
|
* @param params The list of parameters
|
|
*/
|
|
SOCKSEndpointDef(NamedList& params);
|
|
|
|
/**
|
|
* Get the endpoint definition name (id)
|
|
* @return The endpoint definition name
|
|
*/
|
|
virtual const String& toString() const;
|
|
|
|
/**
|
|
* Check if this is a proxy endpoint
|
|
* @return True if this is a proxy endpoint
|
|
*/
|
|
inline bool proxy() const
|
|
{ return m_proxy; }
|
|
|
|
/**
|
|
* Get the endpoint definition name (id)
|
|
* @return The endpoint definition name
|
|
*/
|
|
inline const String& name() const
|
|
{ return m_name; }
|
|
|
|
/**
|
|
* Get the address used by this endpoint
|
|
* @return The address used by this endpoint
|
|
*/
|
|
inline const String& address() const
|
|
{ return m_address; }
|
|
|
|
/**
|
|
* Get the external (public) address used by this endpoint (when applicable)
|
|
* @return The public address used by this endpoint
|
|
*/
|
|
inline const String& externalAddr() const
|
|
{ return m_externalAddr; }
|
|
|
|
/**
|
|
* Get the port used by this endpoint
|
|
* @return The port used by this endpoint
|
|
*/
|
|
inline int port() const
|
|
{ return m_port; }
|
|
|
|
/**
|
|
* Get the port used by this endpoint
|
|
* @return The port used by this endpoint
|
|
*/
|
|
inline bool authRequired() const
|
|
{ return m_authRequired; }
|
|
|
|
/**
|
|
* Get the username used to authenticate
|
|
* @return The username used to authenticate
|
|
*/
|
|
inline const String& username() const
|
|
{ return m_username; }
|
|
|
|
/**
|
|
* Get the password used to authenticate
|
|
* @return The password used to authenticate
|
|
*/
|
|
inline const String& password() const
|
|
{ return m_password; }
|
|
|
|
private:
|
|
bool m_local;
|
|
bool m_proxy;
|
|
String m_name;
|
|
String m_address;
|
|
String m_externalAddr;
|
|
int m_port;
|
|
bool m_authRequired;
|
|
String m_username;
|
|
String m_password;
|
|
};
|
|
|
|
/**
|
|
* This class holds a packet sent or received during SOCKS negotiation
|
|
* @short A SOCKS protocol packet
|
|
*/
|
|
class SOCKSPacket : public GenObject
|
|
{
|
|
friend class SOCKSEngine;
|
|
public:
|
|
/**
|
|
* Message type enumeration
|
|
*/
|
|
enum Type {
|
|
AuthMethods = 1,
|
|
AuthReply,
|
|
UnamePwdRequest,
|
|
UnamePwdReply,
|
|
Request,
|
|
Reply,
|
|
Unknown
|
|
};
|
|
|
|
/**
|
|
* Command type enumeration - RFC 1928, section 4
|
|
*/
|
|
enum CmdType {
|
|
Connect = 0x01,
|
|
Bind = 0x02,
|
|
UdpAssociate = 0x03,
|
|
CmdUnknown
|
|
};
|
|
|
|
/**
|
|
* Address type enumeration - RFC 1928, section 4
|
|
*/
|
|
enum AddrType {
|
|
IPv4 = 0x01,
|
|
Domain = 0x03,
|
|
IPv6 = 0x04,
|
|
AddrUnknown
|
|
};
|
|
|
|
/**
|
|
* Authentication methods enumeration - RFC 1928, section 3
|
|
* 0x03 - 0x7f: IANA assigned
|
|
* 0x80 - 0xfe: Private methods
|
|
*/
|
|
enum AuthMethod {
|
|
AuthNone = 0x00, // Authentication not required
|
|
GSSAPI = 0x01, // GSSAPI
|
|
UnamePwd = 0x02, // Username/password
|
|
NotAuth = 0xff // Not acceptable
|
|
};
|
|
|
|
/**
|
|
* Error enumeration (usually received with Reply) - RFC 1928 Section 6
|
|
*/
|
|
enum Error {
|
|
EOk = 0x00, // Succeeded
|
|
EFailure = 0x01, // General SOCKS server failure
|
|
ENotAllowed = 0x02, // Connection not allowed by ruleset
|
|
ENoConn = 0x03, // Network unreachable
|
|
EHostGone = 0x04, // Host unreachable
|
|
EConnRefused = 0x05, // Connection refused
|
|
ETimeout = 0x06, // TTL expired
|
|
EUnsuppCmd = 0x07, // Command not supported
|
|
EUnsuppAddrType = 0x08, // Address type not supported
|
|
};
|
|
|
|
/**
|
|
* Parser result enumeration
|
|
*/
|
|
enum ParseResult {
|
|
ParseOk,
|
|
ParseError,
|
|
ParseIncomplete
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
* @param t The message type
|
|
* @param conn The connection sending or receiving this packet
|
|
*/
|
|
inline SOCKSPacket(Type t, SOCKSConn* conn)
|
|
: m_type(t), m_conn(conn)
|
|
{}
|
|
|
|
/**
|
|
* Get the message type
|
|
* @return The message type
|
|
*/
|
|
inline Type type() const
|
|
{ return m_type; }
|
|
|
|
/**
|
|
* Get the message name
|
|
* @return The message name
|
|
*/
|
|
inline const char* msgName() const
|
|
{ return token(m_type,s_msgName); }
|
|
|
|
/**
|
|
* Get the connection sending or receiving this packet
|
|
* @return The connection sending or receiving this packet
|
|
*/
|
|
inline SOCKSConn* conn() const
|
|
{ return m_conn; }
|
|
|
|
/**
|
|
* Parse received data
|
|
* @param buf The buffer
|
|
* @param len The buffer length
|
|
* @return The result as enumeration
|
|
*/
|
|
ParseResult parse(unsigned char* buf, unsigned int len);
|
|
|
|
/**
|
|
* Build a string with the message content for debug purposes
|
|
* @param buf The destination string
|
|
* @param extended True to add names and binary packet representation
|
|
*/
|
|
void toString(String& buf, bool extended) const;
|
|
|
|
/**
|
|
* Build a SOCKS request/reply message
|
|
* @param conn The connection sending this packet
|
|
* @param request True if this is a request, false if this is a reply
|
|
* @param cmdRsp CMD/RSP value
|
|
* @param addrType Address type as enumeration
|
|
* @param addr The address
|
|
* @param port The port
|
|
* @return Valid SOCKSPacket pointer on success
|
|
*/
|
|
static SOCKSPacket* buildSocks(SOCKSConn* conn, bool request,
|
|
unsigned char cmdRsp, unsigned char addrType, const String& addr, int port);
|
|
|
|
/**
|
|
* Build an auth methods message
|
|
* @param conn The connection sending this packet
|
|
* @param methods The list of methods
|
|
* @param count The number of methods in the list (must be at least 1)
|
|
* @return Valid SOCKSPacket pointer on success
|
|
*/
|
|
static SOCKSPacket* buildAuthMethods(SOCKSConn* conn, const void* methods,
|
|
unsigned char count);
|
|
|
|
/**
|
|
* Build an auth reply message
|
|
* @param conn The connection sending this packet
|
|
* @param method The method
|
|
* @return Valid SOCKSPacket pointer on success
|
|
*/
|
|
static SOCKSPacket* buildAuthReply(SOCKSConn* conn, unsigned char method);
|
|
|
|
/**
|
|
* Build an username/password auth request
|
|
* @param conn The connection sending this packet
|
|
* @param uname The username (length must be between 0 and 255)
|
|
* @param pwd The password (length must be between 0 and 255)
|
|
* @return Valid SOCKSPacket pointer on success
|
|
*/
|
|
static SOCKSPacket* buildUnamePwdReq(SOCKSConn* conn, const String& uname, const String& pwd);
|
|
|
|
/**
|
|
* Build an username/password auth reply
|
|
* @param conn The connection sending this packet
|
|
* @param ok The result (0 for success)
|
|
* @return Valid SOCKSPacket pointer on success
|
|
*/
|
|
static SOCKSPacket* buildUnamePwdReply(SOCKSConn* conn, unsigned char ok = 0);
|
|
|
|
/**
|
|
* Get a token from a dictionary
|
|
* @param what The token's id
|
|
* @param dict The dictionary
|
|
* @param def Default value to return if not found
|
|
* @return The message name or the default value if not found
|
|
*/
|
|
static inline const char* token(int what, TokenDict* dict, const char* def = "Unknown")
|
|
{ return TelEngine::lookup(what,dict,def); }
|
|
|
|
/**
|
|
* Message names
|
|
*/
|
|
static TokenDict s_msgName[];
|
|
|
|
/**
|
|
* Auth method names
|
|
*/
|
|
static TokenDict s_authName[];
|
|
|
|
/**
|
|
* Command names
|
|
*/
|
|
static TokenDict s_cmdName[];
|
|
|
|
/**
|
|
* Address type names
|
|
*/
|
|
static TokenDict s_addrTypeName[];
|
|
|
|
/**
|
|
* Reply texts
|
|
*/
|
|
static TokenDict s_replyText[];
|
|
|
|
// Data used when encoding/decoding and when printed to output
|
|
unsigned char m_cmdRsp;
|
|
unsigned char m_addrType;
|
|
String m_addr;
|
|
int m_port;
|
|
DataBlock m_auth;
|
|
String m_username;
|
|
String m_password;
|
|
|
|
private:
|
|
Type m_type;
|
|
DataBlock m_buffer; // The message buffer
|
|
SOCKSConn* m_conn;
|
|
};
|
|
|
|
/**
|
|
* This class holds a TCP connection used to transfer SOCKS packets
|
|
* and user data
|
|
* @short A SOCKS TCP connection
|
|
*/
|
|
class SOCKSConn : public RefObject, public Mutex
|
|
{
|
|
friend class SOCKSEngine;
|
|
public:
|
|
/**
|
|
* Enumerate connection status
|
|
*/
|
|
enum Status {
|
|
Idle = 1, // Waiting for a message to be sent
|
|
Connecting, // Outgoing connection is connecting the socket
|
|
WaitMsg, // The connection is waiting for a SOCKS message
|
|
Running, // SOCKS negotiation completed: data can be transferred
|
|
Terminated, // Terminated: no traffic allowed
|
|
};
|
|
|
|
/**
|
|
* Enumerate data direction
|
|
*/
|
|
enum Direction {
|
|
None = 0x00, // None
|
|
Send = 0x01, // Send only
|
|
Recv = 0x02, // Receive only
|
|
Both = 0x03, // Send/receive
|
|
};
|
|
|
|
/**
|
|
* Constructor. Build an incoming connection
|
|
* @param engine The SOCKS engine used for debug purposes
|
|
* @param sock The connected socket
|
|
* @param epDef Endpoint definion used by this connection
|
|
*/
|
|
SOCKSConn(SOCKSEngine* engine, Socket* sock, SOCKSEndpointDef* epDef);
|
|
|
|
/**
|
|
* Constructor. Build an outgoing connection
|
|
* @param engine The SOCKS engine used for debug purposes
|
|
* @param epDef Endpoint definion used by this connection
|
|
* @param cmd Command to send in request
|
|
* @param addrType Address type to send in request
|
|
* @param addr The address to send in request
|
|
* @param port The port to send in request
|
|
*/
|
|
SOCKSConn(SOCKSEngine* engine, SOCKSEndpointDef* epDef, unsigned char cmd,
|
|
unsigned char addrType, const String& addr, int port);
|
|
|
|
/**
|
|
* Destructor. Terminate the socket
|
|
*/
|
|
~SOCKSConn();
|
|
|
|
/**
|
|
* Get the connection status
|
|
* @return The connection status
|
|
*/
|
|
inline Status status() const
|
|
{ return m_status; }
|
|
|
|
/**
|
|
* Get connection direction
|
|
* @return True if the connection is an outgoing one
|
|
*/
|
|
inline bool outgoing() const
|
|
{ return m_outgoing; }
|
|
|
|
/**
|
|
* Check if this connection can transfer data (SOCKS negotiation terminated)
|
|
* @return True if this connection can transfer data
|
|
*/
|
|
inline bool canTransferData() const
|
|
{ return m_status == Running; }
|
|
|
|
/**
|
|
* Check if the socket is valid
|
|
*/
|
|
inline bool valid() const
|
|
{ return m_socket && m_socket->valid(); }
|
|
|
|
/**
|
|
* Get this connection's engine
|
|
* @return This connection's engine
|
|
*/
|
|
inline SOCKSEngine* engine() const
|
|
{ return m_engine; }
|
|
|
|
/**
|
|
* Get this connection's endpoint definition
|
|
* @return This connection's endpoint definition
|
|
*/
|
|
inline const SOCKSEndpointDef* epDef() const
|
|
{ return m_epDef; }
|
|
|
|
/**
|
|
* Get the CMD value stored by this connection
|
|
* @return The CMD value stored by this connection
|
|
*/
|
|
inline unsigned char reqCmd() const
|
|
{ return m_reqCmd; }
|
|
|
|
/**
|
|
* Get the request address type stored by this connection
|
|
* @return The request address type stored by this connection
|
|
*/
|
|
inline unsigned char reqAddrType() const
|
|
{ return m_reqAddrType; }
|
|
|
|
/**
|
|
* Get the request address stored by this connection
|
|
* @return The request address stored by this connection
|
|
*/
|
|
inline const String& reqAddr() const
|
|
{ return m_reqAddr; }
|
|
|
|
/**
|
|
* Get the request port stored by this connection
|
|
* @return The request port stored by this connection
|
|
*/
|
|
inline int reqPort() const
|
|
{ return m_reqPort; }
|
|
|
|
/**
|
|
* Get the RSP value stored by this connection
|
|
* @return The RSP value stored by this connection
|
|
*/
|
|
inline unsigned char replyRsp() const
|
|
{ return m_replyRsp; }
|
|
|
|
/**
|
|
* Get the reply address type stored by this connection
|
|
* @return The reply address type stored by this connection
|
|
*/
|
|
inline unsigned char replyAddrType() const
|
|
{ return m_replyAddrType; }
|
|
|
|
/**
|
|
* Get the reply address stored by this connection
|
|
* @return The reply address stored by this connection
|
|
*/
|
|
inline const String& replyAddr() const
|
|
{ return m_replyAddr; }
|
|
|
|
/**
|
|
* Get the reply port stored by this connection
|
|
* @return The reply port stored by this connection
|
|
*/
|
|
inline int replyPort() const
|
|
{ return m_replyPort; }
|
|
|
|
/**
|
|
* Get this connection's id
|
|
* @return This connection's id
|
|
*/
|
|
virtual const String& toString() const;
|
|
|
|
/**
|
|
* Process connection while waiting for a message
|
|
* Read data from socket and process the received message
|
|
* @param now The current time
|
|
* @param error Flag set on exit to indicate failure
|
|
* @param timeout Failure reason: timeout or invalid message
|
|
* @return Valid SOCKSPacket pointer when a valid packet is received.
|
|
* Check 'error' if 0 is returned
|
|
*/
|
|
SOCKSPacket* processSocks(const Time& now, bool& error, bool& timeout);
|
|
|
|
/**
|
|
* Build and send a SOCKS reply
|
|
* @param addrType Address type to send in reply
|
|
* @param addr The address to send in reply
|
|
* @param port The port to send in reply
|
|
* @param rsp Reply value (0 for success)
|
|
* @return True on success
|
|
*/
|
|
bool sendReply(unsigned char addrType, const String& addr, int port,
|
|
unsigned char rsp = SOCKSPacket::EOk);
|
|
|
|
/**
|
|
* Enable data transfer after succesfully negotiating SOCKS
|
|
* @return True on success
|
|
*/
|
|
bool enableDataTransfer();
|
|
|
|
/**
|
|
* Get connection address
|
|
* @param local True to get local address, false to get the remote one
|
|
* @param addr Destination
|
|
* @return True on success (valid socket)
|
|
*/
|
|
inline bool getAddr(bool local, SocketAddr& addr) {
|
|
if (!m_socket)
|
|
return false;
|
|
return local ? m_socket->getSockName(addr) : m_socket->getPeerName(addr);
|
|
}
|
|
|
|
/**
|
|
* Build socket and connect it (outgoing only)
|
|
* @param sendAuthMeth True to send auth methods after succesfully connected
|
|
* @return True on success
|
|
*/
|
|
bool connect(bool sendAuthMeth = true);
|
|
|
|
/**
|
|
* Terminate and delete the socket
|
|
*/
|
|
void terminate();
|
|
|
|
/**
|
|
* Send data through the socket
|
|
* @param buf The buffer to send
|
|
* @param len The number of bytes to send. On succesfully return it
|
|
* will contain the number of bytes actually sent
|
|
* @return True on success
|
|
*/
|
|
bool send(const void* buf, unsigned int& len);
|
|
|
|
/**
|
|
* Read data from socket
|
|
* @param buf The destination buffer
|
|
* @param len The number of bytes to read. On succesfully return it
|
|
* will contain the number of bytes actually read
|
|
* @return True on success
|
|
*/
|
|
bool recv(void* buf, unsigned int& len);
|
|
|
|
/**
|
|
* Get a status name
|
|
* @param stat The status to find
|
|
* @param def Default value to return if not found
|
|
* @return The status name or the default value if not found
|
|
*/
|
|
static inline const char* statusName(int stat, const char* def = "Unknown")
|
|
{ return TelEngine::lookup(stat,s_statusName,def); }
|
|
|
|
/**
|
|
* Status names
|
|
*/
|
|
static TokenDict s_statusName[];
|
|
|
|
protected:
|
|
/**
|
|
* Build and send a SOCKS request
|
|
* @return True on success
|
|
*/
|
|
inline bool sendRequest() {
|
|
return sendProtocolMsg(
|
|
SOCKSPacket::buildSocks(this,true,m_reqCmd,m_reqAddrType,m_reqAddr,m_reqPort),
|
|
false,SOCKSPacket::Reply);
|
|
}
|
|
|
|
/**
|
|
* Build and send an auth methods message
|
|
* @return True on success
|
|
*/
|
|
bool sendAuthMethods();
|
|
|
|
/**
|
|
* Build and send an auth reply message
|
|
* @param method The method to send
|
|
* @return True on success
|
|
*/
|
|
bool sendAuthReply(unsigned char method);
|
|
|
|
/**
|
|
* Build and send an username/password request
|
|
* @return True on success
|
|
*/
|
|
inline bool sendUnamePwd() {
|
|
if (!m_epDef)
|
|
return false;
|
|
return sendProtocolMsg(
|
|
SOCKSPacket::buildUnamePwdReq(this,m_epDef->username(),m_epDef->password()),
|
|
false,SOCKSPacket::UnamePwdReply);
|
|
}
|
|
|
|
/**
|
|
* Build and send an username/password reply
|
|
* @param ok Reply code (0 for success)
|
|
* @return True on success
|
|
*/
|
|
inline bool sendUnamePwdReply(unsigned char ok = 0) {
|
|
return sendProtocolMsg(SOCKSPacket::buildUnamePwdReply(this,ok),
|
|
0 != ok,SOCKSPacket::Request);
|
|
}
|
|
|
|
/**
|
|
* Terminate the socket. Release memory
|
|
*/
|
|
virtual void destroyed();
|
|
|
|
/**
|
|
* Send protocol messages through the socket. Change connection status on success
|
|
* @param packet The message to send
|
|
* @param terminate True to indicate connection termination
|
|
* @param wait The message to wait for
|
|
* @return True on success
|
|
*/
|
|
bool sendProtocolMsg(SOCKSPacket* packet, bool terminate,
|
|
SOCKSPacket::Type wait = SOCKSPacket::Unknown);
|
|
|
|
/**
|
|
* Build connection id from socket local and remote data
|
|
*/
|
|
void buildId();
|
|
|
|
/**
|
|
* Change connection status
|
|
* @param stat The new connection status
|
|
* @return True if connection status changed
|
|
*/
|
|
bool changeStatus(Status stat);
|
|
|
|
/**
|
|
* Set/reset the timeout when negotiating SOCKS
|
|
* @param now The starting point (0 to reset)
|
|
* @param auth True if authenticating, false if negotiating a request
|
|
*/
|
|
void setSocksTimeout(u_int64_t now = Time::msecNow(), bool auth = true);
|
|
|
|
private:
|
|
// Message processors. Return false to terminate the connection
|
|
bool processAuthMethods(const SOCKSPacket& packet);
|
|
bool processAuthReply(const SOCKSPacket& packet);
|
|
bool processUnamePwdRequest(const SOCKSPacket& packet);
|
|
bool processUnamePwdReply(const SOCKSPacket& packet);
|
|
bool processRequest(const SOCKSPacket& packet);
|
|
bool processReply(const SOCKSPacket& packet);
|
|
|
|
String m_id;
|
|
Status m_status;
|
|
bool m_outgoing;
|
|
SOCKSPacket* m_waitMsg; // Indicates the next packet to receive
|
|
SOCKSEngine* m_engine;
|
|
Socket* m_socket;
|
|
int m_sendError; // Avoid repeating non fatal send error messages
|
|
u_int64_t m_socksTimeoutMs; // The timeout value when negotiating SOCKS
|
|
SOCKSEndpointDef* m_epDef;
|
|
// SOCKS request data
|
|
unsigned char m_reqCmd;
|
|
unsigned char m_reqAddrType;
|
|
String m_reqAddr;
|
|
int m_reqPort;
|
|
// SOCKS reply data
|
|
unsigned char m_replyRsp;
|
|
unsigned char m_replyAddrType;
|
|
String m_replyAddr;
|
|
int m_replyPort;
|
|
};
|
|
|
|
|
|
/**
|
|
* Socket listener. Notify the engine when an incoming connection is created
|
|
* @short A socket listener
|
|
*/
|
|
class SOCKSListener
|
|
{
|
|
friend class SOCKSEngine; // Reset the engine when stopped
|
|
public:
|
|
enum Status {
|
|
Created,
|
|
Initializing,
|
|
Bind,
|
|
Listening,
|
|
Accepting,
|
|
Terminated
|
|
};
|
|
|
|
/**
|
|
* Constructor
|
|
* @param engine The engine using this listener's services
|
|
* @param epDef The endpoint definition used by the listener
|
|
* @param backlog Maximum length of the queue of pending connections, 0 for system maximum
|
|
*/
|
|
SOCKSListener(SOCKSEngine* engine, SOCKSEndpointDef* epDef, unsigned int backlog = 5);
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
virtual ~SOCKSListener();
|
|
|
|
/**
|
|
* Get the endpoint definition used by this listener
|
|
* @return The endpoint definition used by this listener
|
|
*/
|
|
inline SOCKSEndpointDef* epDef()
|
|
{ return m_epDef; }
|
|
|
|
/**
|
|
* Get the engine using this listener's services
|
|
* @return The engine using this listener's services
|
|
*/
|
|
inline SOCKSEngine* engine() const
|
|
{ return m_engine; }
|
|
|
|
/**
|
|
* Get socket address
|
|
* @param addr Destination address
|
|
* @return True on success (valid socket)
|
|
*/
|
|
inline bool getAddr(SocketAddr& addr)
|
|
{ return m_socket && m_socket->getSockName(addr); }
|
|
|
|
/**
|
|
* Get the listener status
|
|
* @return The listener status
|
|
*/
|
|
inline int status() const
|
|
{ return m_status; }
|
|
|
|
/**
|
|
* Create and bind the socket
|
|
* @return True on success
|
|
*/
|
|
bool init();
|
|
|
|
/**
|
|
* Start listening
|
|
* @return True on success
|
|
*/
|
|
bool startListen();
|
|
|
|
/**
|
|
* Check for incoming connections
|
|
* @param addr Address to be filled when a connection was created
|
|
* @return Valid Socket pointer if an incoming connection was created
|
|
*/
|
|
Socket* accept(SocketAddr& addr);
|
|
|
|
/**
|
|
* Terminate the socket
|
|
*/
|
|
void terminate();
|
|
|
|
/**
|
|
* Init, start listening and call accept() in a loop.
|
|
* Notify the engine when a connection is created
|
|
*/
|
|
virtual void run();
|
|
|
|
/**
|
|
* Stop this listener
|
|
* Thread descendants should re-implement this method to cancel the thread
|
|
* @param hard True to cancel a descendant thread in the hard way
|
|
*/
|
|
virtual void stop(bool hard);
|
|
|
|
/**
|
|
* Listener status name
|
|
*/
|
|
static TokenDict s_statusName[];
|
|
|
|
protected:
|
|
SOCKSEndpointDef* m_epDef;
|
|
String m_id;
|
|
unsigned int m_backlog;
|
|
Socket* m_socket;
|
|
bool m_listenError;
|
|
SOCKSEngine* m_engine;
|
|
Status m_status;
|
|
};
|
|
|
|
/**
|
|
* This class holds the socket listeners, endpoint descriptions, and connection
|
|
* negotiating the SOCKS protocol
|
|
* @short SOCKS protocol processor
|
|
*/
|
|
class SOCKSEngine : public DebugEnabler, public Mutex
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
* @param params The engine's parameter list
|
|
*/
|
|
SOCKSEngine(NamedList& params);
|
|
|
|
/**
|
|
* Destructor
|
|
*/
|
|
virtual ~SOCKSEngine()
|
|
{ }
|
|
|
|
/**
|
|
* Check if the engine is exiting
|
|
* @return True if the engine is exiting
|
|
*/
|
|
inline bool exiting() const
|
|
{ return m_exiting; }
|
|
|
|
/**
|
|
* Set the exiting flag
|
|
*/
|
|
inline void setExiting()
|
|
{ m_exiting = true; }
|
|
|
|
/**
|
|
* Get the timeout interval of a connection waiting for a message,
|
|
* before the SOCKS request was sent/received
|
|
* @return The timeout interval in miljiseconds
|
|
*/
|
|
inline u_int64_t waitMsgAuthInterval() const
|
|
{ return m_waitMsgAuthInterval; }
|
|
|
|
/**
|
|
* Get the timeout interval of a connection waiting for a message,
|
|
* after the SOCKS request was sent/received
|
|
* @return The timeout interval in miljiseconds
|
|
*/
|
|
inline u_int64_t waitMsgReplyInterval() const
|
|
{ return m_waitMsgReplyInterval; }
|
|
|
|
/**
|
|
* Initialize engine's parameters
|
|
* @param params The engine's parameter list
|
|
*/
|
|
virtual void initialize(NamedList& params);
|
|
|
|
/**
|
|
* Cleanup the engine. Stop listeners
|
|
*/
|
|
virtual void cleanup();
|
|
|
|
/**
|
|
* Connect a connection, increase its reference counter, add it to the
|
|
* list and start negotisting SOCKS
|
|
* Note that this method should be called from its own thread, since
|
|
* connect() might take some time
|
|
* @param conn The connection
|
|
* @return True on success
|
|
*/
|
|
virtual bool addConnection(SOCKSConn* conn);
|
|
|
|
/**
|
|
* Incoming connection notification. Build a connection and add
|
|
* it to the incoming connections list
|
|
* @param listener The notifier
|
|
* @param sock The created socket
|
|
* @param addr The remote address requesting the connection
|
|
* @return True if the connection was accepted
|
|
*/
|
|
virtual bool incomingConnection(SOCKSListener* listener, Socket* sock,
|
|
SocketAddr& addr);
|
|
|
|
/**
|
|
* Process connections negotiating SOCKS
|
|
* @return True if at least one connection processed a message
|
|
*/
|
|
bool process();
|
|
|
|
/**
|
|
* Process a connection negotiating the SOCKS protocol.
|
|
* @param conn The connection to process
|
|
* @param now The time of the call
|
|
* @return True if the connection processed a message
|
|
*/
|
|
virtual bool processSocksConnection(SOCKSConn* conn, const Time& now);
|
|
|
|
/**
|
|
* Send a packet through a connection
|
|
* The packet is consumed (the pointer will be deleted)
|
|
* @param packet The packet to send
|
|
* @return True on success
|
|
*/
|
|
virtual bool sendPacket(SOCKSPacket* packet);
|
|
|
|
/**
|
|
* Print a debug message when a connections received a packet
|
|
* @param packet The packet to print
|
|
*/
|
|
virtual void receivedPacket(const SOCKSPacket& packet);
|
|
|
|
/**
|
|
* Add an endpoint definition. Replace an existing one with the same name.
|
|
* The pointer will be owned by the engine (append to list without
|
|
* increasing its reference counter)
|
|
* @param epDef The endpoint definition to add
|
|
*/
|
|
void addEpDef(SOCKSEndpointDef* epDef);
|
|
|
|
/**
|
|
* Remove an endpoint definition
|
|
* @param name The endpoint definition name
|
|
*/
|
|
void removeEpDef(const String& name);
|
|
|
|
/**
|
|
* Find an endpoint definition by its name
|
|
* @param name The endpoint definition name
|
|
* @return Referenced SOCKSEndpointDef pointer or 0 if not found
|
|
*/
|
|
SOCKSEndpointDef* findEpDef(const String& name);
|
|
|
|
/**
|
|
* Add a socket listener. The engine doesn't own the pointer
|
|
* @param listener The listener to add
|
|
*/
|
|
virtual void addListener(SOCKSListener* listener);
|
|
|
|
/**
|
|
* Remove a socket listener
|
|
* @param listener The listener to add
|
|
*/
|
|
virtual void removeListener(SOCKSListener* listener);
|
|
|
|
/**
|
|
* Check if a listener exists
|
|
* @param listener The listener to find
|
|
* @param status Set to listener status on exit, if found
|
|
* @return False if not found
|
|
*/
|
|
virtual bool hasListener(SOCKSListener* listener, int& status);
|
|
|
|
/**
|
|
* Stop socket listeners
|
|
* @param wait True to wait for all listeners to remove themselves from the list
|
|
* @param hard Parameter to be passed to listener's stop() method
|
|
*/
|
|
virtual void stopListeners(bool wait, bool hard);
|
|
|
|
/**
|
|
* Remove and delete a connection from SOCKS list
|
|
* @param conn The connection to remove
|
|
* @param reason The reason
|
|
*/
|
|
virtual void removeSocksConn(SOCKSConn* conn, const char* reason);
|
|
|
|
/**
|
|
* Terminate and delete a socket
|
|
* @param sock The socket to terminate
|
|
*/
|
|
static void destroySocket(Socket*& sock);
|
|
|
|
protected:
|
|
/**
|
|
* Process a SOCKS request
|
|
* @param packet The received packet
|
|
* @param conn The receiving connection
|
|
* @return SOCKSPacket error (EOk to indicate success)
|
|
*/
|
|
virtual SOCKSPacket::Error processSOCKSRequest(const SOCKSPacket& packet, SOCKSConn* conn);
|
|
|
|
/**
|
|
* Default SOCKS request. Sends a reply with 'rsp' non 0 (error).
|
|
* This method is called by the engine if request processor returns an error
|
|
* @param conn The receiving connection
|
|
* @param err Error to send
|
|
*/
|
|
virtual void defaultRequestHandler(SOCKSConn* conn,
|
|
SOCKSPacket::Error err = SOCKSPacket::EUnsuppCmd);
|
|
|
|
/**
|
|
* Process a SOCKS reply
|
|
* @param packet The received packet
|
|
* @param conn The receiving connection
|
|
* @return False to remove the connection from the list
|
|
*/
|
|
virtual bool processSOCKSReply(const SOCKSPacket& packet, SOCKSConn* conn);
|
|
|
|
/**
|
|
* Notify descentants when an error occured in a processing connection.
|
|
* The connection will be removed from list after notification
|
|
* @param conn The connection
|
|
* @param timeout True if the connection timed out
|
|
*/
|
|
virtual void socksConnError(SOCKSConn* conn, bool timeout)
|
|
{}
|
|
|
|
bool m_exiting;
|
|
u_int64_t m_waitMsgAuthInterval;
|
|
u_int64_t m_waitMsgReplyInterval;
|
|
bool m_showMsg; // Print message on output
|
|
bool m_dumpExtended; // Dump names and binary msg if printed
|
|
ObjList m_epDef; // The endpoint definition list
|
|
ObjList m_listeners; // The list of listeners
|
|
ObjList m_socksConn; // The list of connections negotiating the SOCKS protocol
|
|
};
|
|
|
|
// The SOCKS engine
|
|
class YSocksEngine : public SOCKSEngine
|
|
{
|
|
friend class YSocksPlugin;
|
|
public:
|
|
YSocksEngine(NamedList& params);
|
|
virtual void initialize(NamedList& params);
|
|
virtual void cleanup();
|
|
// Find a wrapper with a given DST ADDR/PORT
|
|
// Return a referenced object if found
|
|
YSocksWrapper* findWrapper(bool client, const String& dstAddr, int dstPort);
|
|
// Find a wrapper with a given connection
|
|
YSocksWrapper* findWrapper(SOCKSConn* conn);
|
|
// Remove a wrapper from list
|
|
void removeWrapper(YSocksWrapper* w, bool delObj);
|
|
// Add a wrapper
|
|
void addWrapper(YSocksWrapper* w);
|
|
protected:
|
|
virtual SOCKSPacket::Error processSOCKSRequest(const SOCKSPacket& packet, SOCKSConn* conn);
|
|
virtual bool processSOCKSReply(const SOCKSPacket& packet, SOCKSConn* conn);
|
|
virtual void socksConnError(SOCKSConn* conn, bool timeout);
|
|
inline ObjList& listeners()
|
|
{ return m_listeners; }
|
|
private:
|
|
ObjList m_wrappers;
|
|
};
|
|
|
|
// A link between a data source and/or consumer and a SOCKS connection
|
|
class YSocksWrapper : public RefObject, public Mutex, public DebugEnabler
|
|
{
|
|
friend class YSocksConsumer;
|
|
friend class YSocksSource;
|
|
friend class YSocksWrapperWorker;
|
|
public:
|
|
enum State {
|
|
Pending,
|
|
WaitStart,
|
|
Running,
|
|
Terminated
|
|
};
|
|
// Build a wrapper (client if epDef is non 0)
|
|
YSocksWrapper(const char* id, YSocksEngine* engine, CallEndpoint* cp,
|
|
NamedList& params, const char* notify, SOCKSEndpointDef* epDef = 0);
|
|
inline State state() const
|
|
{ return m_state; }
|
|
inline bool client() const
|
|
{ return m_client; }
|
|
inline bool canRecv() const
|
|
{ return 0 != (m_dir & SOCKSConn::Recv); }
|
|
inline bool canSend() const
|
|
{ return m_dir & SOCKSConn::Send; }
|
|
inline const String& media() const
|
|
{ return m_media; }
|
|
inline const String& dstAddr() const
|
|
{ return m_dstAddr; }
|
|
inline const String& notify() const
|
|
{ return m_notify; }
|
|
inline int dstPort() const
|
|
{ return m_dstPort; }
|
|
inline SOCKSConn* conn() const
|
|
{ return m_conn; }
|
|
inline bool autoStart() const
|
|
{ return m_autoStart; }
|
|
inline const String& srvAddr() const
|
|
{ return m_srvAddr; }
|
|
inline int srvPort() const
|
|
{ return m_srvPort; }
|
|
// Connect socket if client
|
|
bool connect();
|
|
// Client connection got reply
|
|
void connRecvReply();
|
|
// Connection error while negotiating the protocol
|
|
void connError(bool timeout);
|
|
// Set connection with valid request for server wrapper
|
|
bool setConn(SOCKSConn* conn);
|
|
// Build source/consumer
|
|
YSocksSource* getSource();
|
|
YSocksConsumer* getConsumer();
|
|
// Build and start or stop worker thread
|
|
bool startWorker();
|
|
void stopWorker();
|
|
// Read data from conn and forward it
|
|
bool recvData();
|
|
// Enable data transfer. Change state, set source/consumer format
|
|
void enableDataTransfer(const char* format = 0);
|
|
// Get the wrapper id
|
|
virtual const String& toString() const;
|
|
// Notify status in chan.notify
|
|
void notify(const char* status) const;
|
|
protected:
|
|
// Release memory
|
|
virtual void destroyed();
|
|
private:
|
|
State m_state;
|
|
bool m_client; // Client or server connection
|
|
int m_dir; // Data direction
|
|
bool m_autoStart; // Start automatically
|
|
String m_id; // Wrapper id
|
|
String m_notify; // Channel to notify
|
|
String m_media; //
|
|
String m_format; //
|
|
CallEndpoint* m_callEp;
|
|
String m_dstAddr; // SOCKS request Destination address field
|
|
int m_dstPort; // SOCKS request Destination port field
|
|
String m_srvAddr; // Server wrapper address
|
|
int m_srvPort; // Server wrapper port
|
|
DataBlock m_recvBuffer;
|
|
YSocksEngine* m_engine;
|
|
YSocksSource* m_source;
|
|
YSocksConsumer* m_consumer;
|
|
SOCKSConn* m_conn;
|
|
YSocksWrapperWorker* m_thread;
|
|
};
|
|
|
|
// Worker thread for a wrapper
|
|
class YSocksWrapperWorker: public Thread
|
|
{
|
|
public:
|
|
inline YSocksWrapperWorker(YSocksWrapper* w, Thread::Priority prio = Thread::Normal)
|
|
: Thread("SOCKS Wrapper",prio),
|
|
m_wrapper(w)
|
|
{}
|
|
// Check if the thread should terminate
|
|
inline bool invalid() {
|
|
return Thread::check(false) || !m_wrapper ||
|
|
m_wrapper->state() == YSocksWrapper::Terminated;
|
|
}
|
|
void run();
|
|
private:
|
|
YSocksWrapper* m_wrapper;
|
|
};
|
|
|
|
// Socks data source
|
|
class YSocksSource : public DataSource
|
|
{
|
|
friend class YSocksWrapper;
|
|
public:
|
|
YSocksSource(YSocksWrapper* w);
|
|
inline void busy(bool isBusy)
|
|
{ m_busy = isBusy; }
|
|
protected:
|
|
// Remove from wrapper. Release memory
|
|
virtual void destroyed();
|
|
private:
|
|
YSocksWrapper* m_wrapper;
|
|
volatile bool m_busy;
|
|
};
|
|
|
|
// Socks data consumer
|
|
class YSocksConsumer : public DataConsumer
|
|
{
|
|
friend class YSocksWrapper;
|
|
public:
|
|
YSocksConsumer(YSocksWrapper* w);
|
|
virtual unsigned long Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags);
|
|
protected:
|
|
// Remove from wrapper. Release memory
|
|
virtual void destroyed();
|
|
private:
|
|
YSocksWrapper* m_wrapper;
|
|
};
|
|
|
|
// A socket listener thread
|
|
class YSocksListenerThread : public SOCKSListener, public Thread
|
|
{
|
|
public:
|
|
// @param engine The engine using this listener's services
|
|
// @param address The local address to bind to
|
|
// @param port The local port to bind to
|
|
// @param backlog Maximum length of the queue of pending connections, 0 for system maximum
|
|
// @param prio Thread priority
|
|
inline YSocksListenerThread(SOCKSEngine* engine, SOCKSEndpointDef* proxy,
|
|
unsigned int backlog, Thread::Priority prio = Thread::Normal)
|
|
: SOCKSListener(engine,proxy,backlog),
|
|
Thread("SOCKS Listener",prio)
|
|
{}
|
|
|
|
// Add the listener to engine and start it
|
|
inline bool addAndStart() {
|
|
if (m_engine)
|
|
m_engine->addListener(this);
|
|
return Thread::startup();
|
|
}
|
|
|
|
virtual void run()
|
|
{ SOCKSListener::run(); }
|
|
|
|
// Stop this listener
|
|
virtual void stop(bool hard)
|
|
{ Thread::cancel(hard); }
|
|
};
|
|
|
|
// A connection processor thread
|
|
class YSocksProcessThread : public Thread
|
|
{
|
|
public:
|
|
inline YSocksProcessThread(Thread::Priority prio = Thread::Normal)
|
|
: Thread("SOCKS Processor",prio)
|
|
{}
|
|
void run();
|
|
};
|
|
|
|
// The plugin
|
|
class YSocksPlugin : public Module
|
|
{
|
|
public:
|
|
enum SocksRelays {
|
|
ChanSocks = Private
|
|
};
|
|
YSocksPlugin();
|
|
virtual ~YSocksPlugin();
|
|
virtual void initialize();
|
|
virtual bool received(Message& msg, int id);
|
|
virtual void statusParams(String& str);
|
|
virtual void statusDetail(String& str);
|
|
// 'chan.socks' message handler
|
|
bool handleChanSocks(Message& msg);
|
|
// Uninstall the relays
|
|
bool unload();
|
|
// Build a wrapper id
|
|
inline void buildWrapperId(String& buf) {
|
|
Lock lock(this);
|
|
buf << name() << "/" << ++m_wrapperId;
|
|
}
|
|
protected:
|
|
// Handle command complete requests
|
|
virtual bool commandComplete(Message& msg, const String& partLine,
|
|
const String& partWord);
|
|
private:
|
|
unsigned int m_wrapperId;
|
|
bool m_init;
|
|
};
|
|
|
|
|
|
// Local data and functions
|
|
INIT_PLUGIN(YSocksPlugin);
|
|
static YSocksEngine* s_engine = 0;
|
|
static unsigned int s_bufLen = 4096; // Read buffer length
|
|
static int s_minPort = 16384; // Min port value used to create temporary listeners
|
|
static int s_maxPort = 32768; // Max port value used to create temporary listeners
|
|
static Mutex s_srcMutex(true,"YSocksChan::source"); // Protect source/wrapper association
|
|
|
|
// Data transfer directions
|
|
static TokenDict dict_conn_dir[] = {
|
|
{ "receive", SOCKSConn::Recv },
|
|
{ "send", SOCKSConn::Send },
|
|
{ "bidir", SOCKSConn::Both },
|
|
{ 0, 0 },
|
|
};
|
|
|
|
static String s_statusCmd;
|
|
// Status commands handled by this module
|
|
static String s_statusCmds[] = {
|
|
"listeners", // Show listeners
|
|
""
|
|
};
|
|
|
|
UNLOAD_PLUGIN(unloadNow)
|
|
{
|
|
if (unloadNow && !__plugin.unload())
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
// The SOCKS protocol version
|
|
#define SOCKS_VERSION 0x05
|
|
|
|
// The USERNAME/PASSWORD authentication version (RFC 1929)
|
|
#define UNAMEPWD_VERSION 0x01
|
|
|
|
// Message names
|
|
TokenDict SOCKSPacket::s_msgName[] = {
|
|
{ "AuthMethods", AuthMethods },
|
|
{ "AuthReply", AuthReply },
|
|
{ "UnamePwdRequest", UnamePwdRequest },
|
|
{ "UnamePwdReply", UnamePwdReply },
|
|
{ "Request", Request },
|
|
{ "Reply", Reply },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Command names
|
|
TokenDict SOCKSPacket::s_cmdName[] = {
|
|
{ "Connect", Connect },
|
|
{ "Bind", Bind },
|
|
{ "UdpAssociate", UdpAssociate },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Address type names
|
|
TokenDict SOCKSPacket::s_addrTypeName[] = {
|
|
{ "IPv4", IPv4 },
|
|
{ "Domain", Domain },
|
|
{ "IPv6", IPv6 },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Auth method names
|
|
TokenDict SOCKSPacket::s_authName[] = {
|
|
{ "None", AuthNone },
|
|
{ "GSSAPI", GSSAPI },
|
|
{ "Username/Password", UnamePwd },
|
|
{ "NotAuth", NotAuth },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Reply texts
|
|
TokenDict SOCKSPacket::s_replyText[] = {
|
|
{ "Succeeded", EOk },
|
|
{ "General SOCKS server failure", EFailure },
|
|
{ "Connection not allowed by ruleset", ENotAllowed },
|
|
{ "Network unreachable", ENoConn },
|
|
{ "Host unreachable", EHostGone },
|
|
{ "Connection refused", EConnRefused },
|
|
{ "TTL expired", ETimeout },
|
|
{ "Command not supported", EUnsuppCmd },
|
|
{ "Address type not supported", EUnsuppAddrType },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Message names
|
|
TokenDict SOCKSConn::s_statusName[] = {
|
|
{ "Idle", Idle },
|
|
{ "Connecting", Connecting },
|
|
{ "WaitMsg", WaitMsg },
|
|
{ "Running", Running },
|
|
{ "Terminated", Terminated },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Listener status names
|
|
TokenDict SOCKSListener::s_statusName[] = {
|
|
{ "Created", Created },
|
|
{ "Initializing", Initializing },
|
|
{ "Bind", Bind },
|
|
{ "Listening", Listening },
|
|
{ "Accepting", Accepting },
|
|
{ "Terminated", Terminated },
|
|
{ 0, 0 }
|
|
};
|
|
|
|
// Check the version of a SOCKS packet
|
|
inline bool validSocksVersion(SOCKSPacket& packet, unsigned char ver)
|
|
{
|
|
if (ver == SOCKS_VERSION)
|
|
return true;
|
|
Debug(packet.conn() ? packet.conn()->engine() : 0,DebugNote,
|
|
"SOCKSConn(%s) received message %s with invalid version %u (supported: %u) [%p]",
|
|
packet.conn() ? packet.conn()->toString().c_str() : "",packet.msgName(),
|
|
ver,SOCKS_VERSION,packet.conn());
|
|
return false;
|
|
}
|
|
|
|
// Check the version of a Username/password authentication packet
|
|
inline bool validUnamePwdVersion(SOCKSPacket& packet, unsigned char ver)
|
|
{
|
|
if (ver == UNAMEPWD_VERSION)
|
|
return true;
|
|
Debug(packet.conn() ? packet.conn()->engine() : 0,DebugNote,
|
|
"SOCKSConn(%s) received message %s with invalid version %u (supported: %u) [%p]",
|
|
packet.conn() ? packet.conn()->toString().c_str() : "",packet.msgName(),
|
|
ver,UNAMEPWD_VERSION,packet.conn());
|
|
return false;
|
|
}
|
|
|
|
// Check the message length
|
|
inline bool validSocksMsgLen(SOCKSPacket& packet, unsigned int expected, unsigned char len)
|
|
{
|
|
if (expected == len)
|
|
return true;
|
|
Debug(packet.conn() ? packet.conn()->engine() : 0,DebugNote,
|
|
"SOCKSConn(%s) received message %s with invalid length %u (expected: %u) [%p]",
|
|
packet.conn() ? packet.conn()->toString().c_str() : "",packet.msgName(),
|
|
len,expected,packet.conn());
|
|
return false;
|
|
}
|
|
|
|
|
|
// Parse received data
|
|
SOCKSPacket::ParseResult SOCKSPacket::parse(unsigned char* buf, unsigned int len)
|
|
{
|
|
if (!(buf && len))
|
|
return ParseIncomplete;
|
|
|
|
m_buffer.append(buf,len);
|
|
unsigned char* d = (unsigned char*)m_buffer.data();
|
|
len = m_buffer.length();
|
|
if (m_type == AuthMethods) {
|
|
if (!validSocksVersion(*this,d[0]))
|
|
return ParseError;
|
|
if (len < 2 || (len < ((unsigned int)d[1] + 2)))
|
|
return ParseIncomplete;
|
|
if (!validSocksMsgLen(*this,d[1] + 2,len))
|
|
return ParseError;
|
|
m_auth.append(d + 2,d[1]);
|
|
return ParseOk;
|
|
}
|
|
if (m_type == AuthReply) {
|
|
if (!validSocksVersion(*this,d[0]))
|
|
return ParseError;
|
|
if (len < 2)
|
|
return ParseIncomplete;
|
|
if (!validSocksMsgLen(*this,2,len))
|
|
return ParseError;
|
|
m_auth.append(d + 1,1);
|
|
return ParseOk;
|
|
}
|
|
if (m_type == UnamePwdRequest) {
|
|
if (!validUnamePwdVersion(*this,d[0]))
|
|
return ParseError;
|
|
if (len < 4)
|
|
return ParseIncomplete;
|
|
// Check username
|
|
if (((unsigned int)d[1] + 2) > len)
|
|
return ParseIncomplete;
|
|
// Check password
|
|
unsigned int pwdLenPos = d[d[1] + 2];
|
|
if (pwdLenPos >= len || d[pwdLenPos] + pwdLenPos > len)
|
|
return ParseIncomplete;
|
|
if (!validSocksMsgLen(*this,3 + d[1] + d[pwdLenPos],len))
|
|
return ParseError;
|
|
m_username.assign((char*)d + 2,d[1]);
|
|
m_password.assign((char*)d + pwdLenPos + 1,d[pwdLenPos]);
|
|
return ParseOk;
|
|
}
|
|
if (m_type == UnamePwdReply) {
|
|
if (!validUnamePwdVersion(*this,d[0]))
|
|
return ParseError;
|
|
if (len < 2)
|
|
return ParseIncomplete;
|
|
if (!validSocksMsgLen(*this,2,len))
|
|
return ParseError;
|
|
m_auth.append(d + 1,1);
|
|
return ParseOk;
|
|
}
|
|
if (m_type == Request || m_type == Reply) {
|
|
if (!validSocksVersion(*this,d[0]))
|
|
return ParseError;
|
|
// Min len: 10
|
|
if (len < 10)
|
|
return ParseIncomplete;
|
|
m_cmdRsp = d[1];
|
|
// Start check with index 3: address type
|
|
unsigned int domainLen = 0;
|
|
unsigned int expected = 6; // Msg len without address/domain
|
|
m_addrType = d[3];
|
|
d = d + 4;
|
|
if (m_addrType == Domain) {
|
|
domainLen = *d++;
|
|
expected += domainLen + 1;
|
|
}
|
|
else if (m_addrType == IPv4)
|
|
expected += 4;
|
|
else if (m_addrType == IPv6)
|
|
expected += 16;
|
|
else {
|
|
Debug(m_conn ? m_conn->engine() : 0,DebugMild,
|
|
"SOCKSConn(%s) received %s with invalid address type %u [%p]",
|
|
m_conn ? m_conn->toString().c_str() : "",msgName(),m_addrType,m_conn);
|
|
return ParseError;
|
|
}
|
|
// Check len
|
|
if (expected < len)
|
|
return ParseIncomplete;
|
|
if (!validSocksMsgLen(*this,expected,len))
|
|
return ParseError;
|
|
// Decode addr
|
|
m_addr = "";
|
|
if (m_addrType == IPv4) {
|
|
m_addr << d[0] << "." << d[1] << "." << d[2] << "." << d[3];
|
|
d += 4;
|
|
}
|
|
else if (m_addrType == Domain) {
|
|
m_addr.assign((const char*)d,domainLen);
|
|
d = d + domainLen;
|
|
}
|
|
else if (m_addrType == IPv6) {
|
|
for (int i = 0; i < 8; i++, d += 2) {
|
|
String tmp;
|
|
tmp.hexify(d,2);
|
|
m_addr.append(tmp,":");
|
|
}
|
|
}
|
|
m_port = (d[0] << 8) | d[1];
|
|
return ParseOk;
|
|
}
|
|
Debug(DebugStub,"Request to parse unhandled message type %u: '%s'",
|
|
m_type,msgName());
|
|
return ParseError;
|
|
}
|
|
|
|
// Utility function used in SOCKSPacket::toString()
|
|
inline void addExtended(String& buf, bool extended, unsigned char value,
|
|
TokenDict* dict)
|
|
{
|
|
if (!extended)
|
|
return;
|
|
buf << "(" << SOCKSPacket::token(value,dict) << ")";
|
|
}
|
|
|
|
// Build a string with the message content for debug purposes
|
|
void SOCKSPacket::toString(String& buf, bool extended) const
|
|
{
|
|
buf << "Type=" << msgName();
|
|
unsigned char* d = (unsigned char*)m_buffer.data();
|
|
unsigned int len = m_buffer.length();
|
|
switch (m_type) {
|
|
case AuthMethods:
|
|
if (len) {
|
|
buf << " VER=" << d[0];
|
|
buf << " METHODS=" << m_auth.length() << " [";
|
|
unsigned char* a = (unsigned char*)m_auth.data();
|
|
for (unsigned int i = 0; i < m_auth.length(); i++, a++) {
|
|
buf << (i ? " " : "") << *a;
|
|
addExtended(buf,extended,*a,s_authName);
|
|
}
|
|
buf << "]";
|
|
}
|
|
break;
|
|
case AuthReply:
|
|
if (len) {
|
|
buf << " VER=" << d[0];
|
|
unsigned char* a = (unsigned char*)m_auth.data();
|
|
if (a) {
|
|
buf << " METHOD=" << *a;
|
|
addExtended(buf,extended,*a,s_authName);
|
|
}
|
|
}
|
|
break;
|
|
case UnamePwdRequest:
|
|
if (len) {
|
|
buf << " VER=" << d[0];
|
|
buf << " UNAME=" << m_username;
|
|
buf << " PASSWD=" << m_password;
|
|
}
|
|
break;
|
|
case UnamePwdReply:
|
|
if (len) {
|
|
buf << " VER=" << d[0];
|
|
if (m_auth.length()) {
|
|
unsigned char stat = *(unsigned char*)m_auth.data();
|
|
buf << " STATUS=" << stat;
|
|
if (extended)
|
|
buf << (!stat ? "(OK)" : "(Failure)");
|
|
}
|
|
}
|
|
break;
|
|
case Request:
|
|
case Reply:
|
|
if (len) {
|
|
buf << " VER=" << d[0];
|
|
bool req = (m_type == Request);
|
|
buf << (req ? " CMD=" : " RSP=") << m_cmdRsp;
|
|
addExtended(buf,extended,m_cmdRsp,req ? s_cmdName : s_replyText);
|
|
buf << " ATYP=" << m_addrType;
|
|
addExtended(buf,extended,m_addrType,s_addrTypeName);
|
|
buf << " ADDR=" << m_addr;
|
|
buf << " PORT=" << m_port;
|
|
}
|
|
break;
|
|
case Unknown:
|
|
extended = true;
|
|
}
|
|
if (extended) {
|
|
String tmp;
|
|
tmp.hexify(m_buffer.data(),m_buffer.length(),' ');
|
|
buf << " Hex: " << tmp;
|
|
}
|
|
}
|
|
|
|
// Build a SOCKS request/reply message
|
|
SOCKSPacket* SOCKSPacket::buildSocks(SOCKSConn* conn, bool request,
|
|
unsigned char cmdRsp, unsigned char addrType, const String& addr, int port)
|
|
{
|
|
SOCKSPacket::Type type = request ? SOCKSPacket::Request : SOCKSPacket::Reply;
|
|
const char* error = 0;
|
|
unsigned char ip[4];
|
|
// Check addr
|
|
if (addrType == Domain) {
|
|
if (!addr)
|
|
error = "empty address";
|
|
if (addr.length() > 255)
|
|
error = "address too long";
|
|
}
|
|
else if (addrType == IPv4) {
|
|
if (addr) {
|
|
ObjList* list = addr.split('.');
|
|
int i = 0;
|
|
for (ObjList* o = list->skipNull(); o; o = o->skipNext(), i++) {
|
|
int tmp = o->get()->toString().toInteger(-1);
|
|
if (i > 3 || tmp < 0 || tmp > 255) {
|
|
error = "invalid address";
|
|
break;
|
|
}
|
|
ip[i] = (unsigned char)tmp;
|
|
}
|
|
TelEngine::destruct(list);
|
|
}
|
|
else
|
|
error = "empty address";
|
|
}
|
|
else
|
|
error = "unsupported type";
|
|
if (error) {
|
|
Debug(conn ? conn->engine() : 0,DebugMild,
|
|
"SOCKSConn(%s) can't build %s with address=%s type=%u(%s) '%s' [%p]",
|
|
conn ? conn->toString().c_str() : "",lookup(type,SOCKSPacket::s_msgName),
|
|
addr.c_str(),addrType,lookup(addrType,SOCKSPacket::s_addrTypeName),error,conn);
|
|
return 0;
|
|
}
|
|
|
|
SOCKSPacket* packet = new SOCKSPacket(type,conn);
|
|
packet->m_cmdRsp = cmdRsp;
|
|
packet->m_addrType = addrType;
|
|
packet->m_addr = addr;
|
|
packet->m_port = port;
|
|
unsigned char buf[4] = {SOCKS_VERSION,cmdRsp,0,addrType};
|
|
packet->m_buffer.append(buf,4);
|
|
if (addrType == Domain) {
|
|
unsigned char l = addr.length();
|
|
packet->m_buffer.append(&l,1);
|
|
packet->m_buffer += addr;
|
|
}
|
|
else if (addrType == IPv4)
|
|
packet->m_buffer.append(ip,4);
|
|
unsigned char p[2] = {port >> 8,port};
|
|
packet->m_buffer.append(p,2);
|
|
return packet;
|
|
}
|
|
|
|
// Build an "auth methods" message
|
|
SOCKSPacket* SOCKSPacket::buildAuthMethods(SOCKSConn* conn, const void* methods,
|
|
unsigned char count)
|
|
{
|
|
if (!(methods && count))
|
|
return 0;
|
|
unsigned char buf[2] = {SOCKS_VERSION,count};
|
|
SOCKSPacket* packet = new SOCKSPacket(AuthMethods,conn);
|
|
packet->m_buffer.append(buf,2);
|
|
packet->m_auth.append((void*)methods,count);
|
|
packet->m_buffer += packet->m_auth;
|
|
return packet;
|
|
}
|
|
|
|
// Build an "auth reply" message
|
|
SOCKSPacket* SOCKSPacket::buildAuthReply(SOCKSConn* conn, unsigned char method)
|
|
{
|
|
unsigned char buf[2] = {SOCKS_VERSION,method};
|
|
SOCKSPacket* packet = new SOCKSPacket(AuthReply,conn);
|
|
packet->m_buffer.append(buf,2);
|
|
packet->m_auth.append(&method,1);
|
|
return packet;
|
|
}
|
|
|
|
|
|
// Build an username/password auth request
|
|
SOCKSPacket* SOCKSPacket::buildUnamePwdReq(SOCKSConn* conn, const String& uname,
|
|
const String& pwd)
|
|
{
|
|
if (uname.null() || pwd.null() || uname.length() > 255 || pwd.length() > 255)
|
|
return 0;
|
|
SOCKSPacket* packet = new SOCKSPacket(UnamePwdRequest,conn);
|
|
packet->m_username = uname;
|
|
packet->m_password = pwd;
|
|
unsigned char tmp = UNAMEPWD_VERSION;
|
|
packet->m_buffer.append(&tmp,1);
|
|
tmp = (unsigned char)uname.length();
|
|
packet->m_buffer.append(&tmp,1);
|
|
packet->m_buffer += uname;
|
|
tmp = (unsigned char)pwd.length();
|
|
packet->m_buffer.append(&tmp,1);
|
|
packet->m_buffer += pwd;
|
|
return packet;
|
|
}
|
|
|
|
// Build an username/password auth reply
|
|
SOCKSPacket* SOCKSPacket::buildUnamePwdReply(SOCKSConn* conn, unsigned char ok)
|
|
{
|
|
SOCKSPacket* packet = new SOCKSPacket(UnamePwdReply,conn);
|
|
packet->m_auth.append(&ok,1);
|
|
unsigned char buf[2] = {UNAMEPWD_VERSION,ok};
|
|
packet->m_buffer.append(buf,2);
|
|
return packet;
|
|
}
|
|
|
|
|
|
// Incoming connection constructor
|
|
SOCKSConn::SOCKSConn(SOCKSEngine* engine, Socket* sock, SOCKSEndpointDef* epDef)
|
|
: Mutex(true,"SOCKSConn"),
|
|
m_status(Idle), m_outgoing(false),
|
|
m_waitMsg(0), m_engine(engine), m_socket(sock), m_sendError(0),
|
|
m_socksTimeoutMs(0), m_epDef(epDef),
|
|
m_reqCmd(SOCKSPacket::CmdUnknown), m_reqAddrType(SOCKSPacket::AddrUnknown),
|
|
m_reqPort(0),
|
|
m_replyRsp(SOCKSPacket::EOk), m_replyAddrType(SOCKSPacket::AddrUnknown),
|
|
m_replyPort(0)
|
|
{
|
|
buildId();
|
|
changeStatus(WaitMsg);
|
|
m_waitMsg = new SOCKSPacket(SOCKSPacket::AuthMethods,this);
|
|
setSocksTimeout(Time::msecNow(),true);
|
|
}
|
|
|
|
// Outgoing connection constructor
|
|
SOCKSConn::SOCKSConn(SOCKSEngine* engine, SOCKSEndpointDef* epDef, unsigned char cmd,
|
|
unsigned char addrType, const String& addr, int port)
|
|
: Mutex(true,"SOCKSConn"),
|
|
m_status(Idle), m_outgoing(true),
|
|
m_waitMsg(0), m_engine(engine), m_socket(0), m_sendError(0),
|
|
m_socksTimeoutMs(0), m_epDef(epDef),
|
|
m_reqCmd(cmd), m_reqAddrType(addrType), m_reqAddr(addr), m_reqPort(port),
|
|
m_replyRsp(SOCKSPacket::EOk), m_replyAddrType(SOCKSPacket::AddrUnknown),
|
|
m_replyPort(0)
|
|
{
|
|
}
|
|
|
|
SOCKSConn::~SOCKSConn()
|
|
{
|
|
TelEngine::destruct(m_epDef);
|
|
terminate();
|
|
}
|
|
|
|
// Terminate the socket. Release memory
|
|
void SOCKSConn::destroyed()
|
|
{
|
|
DDebug(m_engine,DebugAll,"SOCKSConn(%s) destroyed [%p]",m_id.c_str(),this);
|
|
TelEngine::destruct(m_epDef);
|
|
terminate();
|
|
RefObject::destroyed();
|
|
}
|
|
|
|
// Get this connection's id
|
|
const String& SOCKSConn::toString() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
// Process connection while waiting for a message
|
|
SOCKSPacket* SOCKSConn::processSocks(const Time& now, bool& error, bool& timeout)
|
|
{
|
|
error = timeout = false;
|
|
Lock lock(this);
|
|
if (m_status == Terminated) {
|
|
error = true;
|
|
return 0;
|
|
}
|
|
if (m_status != WaitMsg || !m_socket)
|
|
return 0;
|
|
// Sanity check
|
|
if (!m_waitMsg) {
|
|
Debug(m_engine,DebugGoOn,
|
|
"SOCKSConn(%s) inconsistent status (no msg in %s status) [%p]",
|
|
m_id.c_str(),statusName(m_status),this);
|
|
error = true;
|
|
return 0;
|
|
}
|
|
// Check received message
|
|
// Max msg: UnamePwdRequest: 513 bytes
|
|
unsigned char buf[528];
|
|
unsigned int read = sizeof(buf);
|
|
if (!recv(buf,read)) {
|
|
changeStatus(Terminated);
|
|
error = true;
|
|
return 0;
|
|
}
|
|
// Use a while to break
|
|
while (read) {
|
|
SOCKSPacket::ParseResult res = m_waitMsg->parse(buf,read);
|
|
if (res == SOCKSPacket::ParseIncomplete)
|
|
break;
|
|
setSocksTimeout(0);
|
|
if (m_engine)
|
|
m_engine->receivedPacket(*m_waitMsg);
|
|
if (res == SOCKSPacket::ParseError) {
|
|
Debug(m_engine,DebugNote,
|
|
"SOCKSConn(%s) received invalid message '%s' [%p]",
|
|
m_id.c_str(),m_waitMsg->msgName(),this);
|
|
TelEngine::destruct(m_waitMsg);
|
|
changeStatus(Terminated);
|
|
error = true;
|
|
return 0;
|
|
}
|
|
// OK
|
|
SOCKSPacket* ret = m_waitMsg;
|
|
m_waitMsg = 0;
|
|
changeStatus(Idle);
|
|
switch (ret->type()) {
|
|
case SOCKSPacket::AuthMethods:
|
|
error = !processAuthMethods(*ret);
|
|
break;
|
|
case SOCKSPacket::AuthReply:
|
|
error = !processAuthReply(*ret);
|
|
break;
|
|
case SOCKSPacket::UnamePwdRequest:
|
|
error = !processUnamePwdRequest(*ret);
|
|
break;
|
|
case SOCKSPacket::UnamePwdReply:
|
|
error = !processUnamePwdReply(*ret);
|
|
break;
|
|
case SOCKSPacket::Request:
|
|
error = !processRequest(*ret);
|
|
if (!error)
|
|
setSocksTimeout(now.msec(),false);
|
|
break;
|
|
case SOCKSPacket::Reply:
|
|
error = !processReply(*ret);
|
|
break;
|
|
default:
|
|
error = true;
|
|
Debug(m_engine,DebugNote,
|
|
"SOCKSConn(%s) received unhandled message '%s' [%p]",
|
|
m_id.c_str(),m_waitMsg->msgName(),this);
|
|
}
|
|
|
|
if (error) {
|
|
changeStatus(Terminated);
|
|
TelEngine::destruct(ret);
|
|
}
|
|
return ret;
|
|
}
|
|
// Check timeout
|
|
if (m_socksTimeoutMs && m_socksTimeoutMs < now.msec()) {
|
|
Debug(m_engine,DebugNote,
|
|
"SOCKSConn(%s) timed out while waiting for '%s' [%p]",
|
|
m_id.c_str(),m_waitMsg->msgName(),this);
|
|
TelEngine::destruct(m_waitMsg);
|
|
changeStatus(Terminated);
|
|
error = true;
|
|
timeout = true;
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Build and send a SOCKS reply
|
|
bool SOCKSConn::sendReply(unsigned char addrType, const String& addr, int port,
|
|
unsigned char rsp)
|
|
{
|
|
m_replyRsp = rsp;
|
|
m_replyAddrType = addrType;
|
|
m_replyAddr = addr;
|
|
m_replyPort = port;
|
|
SOCKSPacket* packet = SOCKSPacket::buildSocks(this,false,rsp,addrType,addr,port);
|
|
if (!packet) {
|
|
changeStatus(Terminated);
|
|
return false;
|
|
}
|
|
bool terminate = (rsp != SOCKSPacket::EOk);
|
|
return sendProtocolMsg(packet,terminate,SOCKSPacket::Unknown);
|
|
}
|
|
|
|
// Enable data transfer after succesfully negotiating SOCKS
|
|
bool SOCKSConn::enableDataTransfer()
|
|
{
|
|
Lock lock(this);
|
|
if (m_status == Terminated)
|
|
return false;
|
|
setSocksTimeout(0);
|
|
changeStatus(Running);
|
|
return true;
|
|
}
|
|
|
|
// Build socket and connect it (outgoing only)
|
|
bool SOCKSConn::connect(bool sendAuthMeth)
|
|
{
|
|
Lock lock(this);
|
|
if (!outgoing() || m_status == Connecting || m_status == Terminated)
|
|
return false;
|
|
if (m_socket && m_socket->valid())
|
|
return true;
|
|
if (m_socket)
|
|
terminate();
|
|
|
|
if (!m_epDef)
|
|
return false;
|
|
|
|
changeStatus(Connecting);
|
|
String address = m_epDef->address();
|
|
int port = m_epDef->port();
|
|
m_socket = new Socket;
|
|
bool ok = m_socket->create(PF_INET,SOCK_STREAM);
|
|
const char* result = "connect";
|
|
// Avoid resolving address and connect with mutex locked
|
|
lock.drop();
|
|
if (ok) {
|
|
SocketAddr addr(PF_INET);
|
|
addr.host(address);
|
|
addr.port(port);
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) connecting to '%s:%d' [%p]",
|
|
m_id.c_str(),addr.host().c_str(),addr.port(),this);
|
|
ok = m_socket->connect(addr);
|
|
}
|
|
else
|
|
result = "create";
|
|
|
|
Lock lock2(this);
|
|
buildId();
|
|
if (ok) {
|
|
if (!valid())
|
|
return false;
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) connected to '%s:%d' [%p]",
|
|
m_id.c_str(),address.c_str(),port,this);
|
|
m_socket->setBlocking(false);
|
|
changeStatus(Idle);
|
|
if (sendAuthMeth)
|
|
sendAuthMethods();
|
|
}
|
|
else {
|
|
if (valid()) {
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
Debug(m_engine,DebugWarn,
|
|
"SOCKSConn(%s) failed to %s socket '%s:%d'. %d: '%s' [%p]",
|
|
m_id.c_str(),result,address.c_str(),port,
|
|
m_socket->error(),s.c_str(),this);
|
|
}
|
|
terminate();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// Terminate and delete the socket
|
|
void SOCKSConn::terminate()
|
|
{
|
|
TelEngine::destruct(m_waitMsg);
|
|
changeStatus(Terminated);
|
|
if (!m_socket)
|
|
return;
|
|
DDebug(m_engine,DebugAll,"SOCKSConn(%s) terminating socket [%p]",m_id.c_str(),this);
|
|
SOCKSEngine::destroySocket(m_socket);
|
|
m_sendError = 0;
|
|
}
|
|
|
|
// Build and send an auth methods message
|
|
bool SOCKSConn::sendAuthMethods()
|
|
{
|
|
lock();
|
|
unsigned char n = 1;
|
|
unsigned char meth[2] = {SOCKSPacket::AuthNone,0};
|
|
if (m_epDef && m_epDef->authRequired()) {
|
|
meth[1] = SOCKSPacket::UnamePwd;
|
|
n++;
|
|
}
|
|
unlock();
|
|
return sendProtocolMsg(SOCKSPacket::buildAuthMethods(this,meth,n),false,
|
|
SOCKSPacket::AuthReply);
|
|
}
|
|
|
|
// Build and send an auth reply message
|
|
bool SOCKSConn::sendAuthReply(unsigned char method)
|
|
{
|
|
switch (method) {
|
|
case SOCKSPacket::AuthNone:
|
|
return sendProtocolMsg(SOCKSPacket::buildAuthReply(this,method),false,
|
|
SOCKSPacket::Request);
|
|
case SOCKSPacket::UnamePwd:
|
|
return sendProtocolMsg(SOCKSPacket::buildAuthReply(this,method),false,
|
|
SOCKSPacket::UnamePwdRequest);
|
|
case SOCKSPacket::NotAuth:
|
|
return sendProtocolMsg(SOCKSPacket::buildAuthReply(this,method),true);
|
|
}
|
|
Debug(m_engine,DebugStub,
|
|
"SOCKSConn(%s) request to send auth reply with unhandled method %u [%p]",
|
|
m_id.c_str(),method,this);
|
|
return false;
|
|
}
|
|
|
|
// Send protocol messages through the socket
|
|
bool SOCKSConn::sendProtocolMsg(SOCKSPacket* packet, bool terminate,
|
|
SOCKSPacket::Type wait)
|
|
{
|
|
if (!packet)
|
|
return false;
|
|
Lock lock(this);
|
|
if (!m_engine || m_status != Idle) {
|
|
Debug(m_engine,DebugMild,"SOCKSConn(%s) can't send %s in state %s [%p]",
|
|
m_id.c_str(),packet->msgName(),statusName(m_status),this);
|
|
TelEngine::destruct(packet);
|
|
return false;
|
|
}
|
|
if (!m_engine->sendPacket(packet)) {
|
|
changeStatus(Terminated);
|
|
return false;
|
|
}
|
|
TelEngine::destruct(m_waitMsg);
|
|
if (terminate)
|
|
changeStatus(Terminated);
|
|
else {
|
|
if (wait != SOCKSPacket::Unknown) {
|
|
m_waitMsg = new SOCKSPacket(wait,this);
|
|
changeStatus(WaitMsg);
|
|
setSocksTimeout(Time::msecNow(),wait != SOCKSPacket::Reply);
|
|
}
|
|
else
|
|
changeStatus(Idle);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Build connection id
|
|
void SOCKSConn::buildId()
|
|
{
|
|
Lock lock(this);
|
|
if (!m_socket)
|
|
return;
|
|
m_id = "";
|
|
SocketAddr local;
|
|
m_socket->getSockName(local);
|
|
m_id << local.host() << ":" << local.port();
|
|
SocketAddr remote;
|
|
m_socket->getPeerName(remote);
|
|
m_id << "-" << remote.host() << ":" << remote.port();
|
|
}
|
|
|
|
// Send data
|
|
bool SOCKSConn::send(const void* buf, unsigned int& len)
|
|
{
|
|
if (!(len && valid()))
|
|
return false;
|
|
|
|
int c = m_socket->writeData(buf,len);
|
|
if (c != Socket::socketError()) {
|
|
#ifdef XDEBUG
|
|
if (len) {
|
|
String s;
|
|
s.hexify((void*)buf,len,' ');
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) sent %d/%u bytes '%s' [%p]",
|
|
m_id.c_str(),c,len,s.c_str(),this);
|
|
}
|
|
#endif
|
|
len = c;
|
|
m_sendError = 0;
|
|
return true;
|
|
}
|
|
len = 0;
|
|
if (m_socket->canRetry()) {
|
|
if (m_sendError != m_socket->error()) {
|
|
m_sendError = m_socket->error();
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
DDebug(m_engine,DebugMild,
|
|
"SOCKSConn(%s) socket temporary unavailable to send. %d: '%s' [%p]",
|
|
m_id.c_str(),m_socket->error(),s.c_str(),this);
|
|
}
|
|
return true;
|
|
}
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
Debug(m_engine,DebugWarn,"SOCKSConn(%s) socket send error. %d: '%s' [%p]",
|
|
m_id.c_str(),m_socket->error(),s.c_str(),this);
|
|
return false;
|
|
}
|
|
|
|
// Read data from socket
|
|
bool SOCKSConn::recv(void* buf, unsigned int& len)
|
|
{
|
|
if (!valid())
|
|
return false;
|
|
|
|
int read = m_socket->readData(buf,len);
|
|
if (read != Socket::socketError()) {
|
|
#ifdef XDEBUG
|
|
if (len) {
|
|
String s;
|
|
s.hexify(buf,read,' ');
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) recv %d bytes '%s' [%p]",
|
|
m_id.c_str(),read,s.c_str(),this);
|
|
}
|
|
#endif
|
|
len = read;
|
|
return true;
|
|
}
|
|
|
|
len = 0;
|
|
if (m_socket->canRetry())
|
|
return true;
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
Debug(m_engine,DebugWarn,"SOCKSConn(%s) socket read error. %d: '%s' [%p]",
|
|
m_id.c_str(),m_socket->error(),s.c_str(),this);
|
|
return false;
|
|
}
|
|
|
|
// Changed connection status
|
|
bool SOCKSConn::changeStatus(Status stat)
|
|
{
|
|
if (m_status == stat || m_status == Terminated)
|
|
return false;
|
|
Debug(m_engine,DebugInfo,"SOCKSConn(%s) changed status from '%s' to '%s' [%p]",
|
|
m_id.c_str(),statusName(m_status),statusName(stat),this);
|
|
m_status = stat;
|
|
return true;
|
|
}
|
|
|
|
// Set/reset the timeout when negotiating SOCKS
|
|
void SOCKSConn::setSocksTimeout(u_int64_t now, bool auth)
|
|
{
|
|
if (!now) {
|
|
if (!m_socksTimeoutMs)
|
|
Debug(m_engine,DebugInfo,"SOCKSConn(%s) stopping timer [%p]",
|
|
m_id.c_str(),this);
|
|
m_socksTimeoutMs = 0;
|
|
return;
|
|
}
|
|
u_int64_t interval = 0;
|
|
if (m_engine) {
|
|
if (auth)
|
|
interval = m_engine->waitMsgAuthInterval();
|
|
else
|
|
interval = m_engine->waitMsgReplyInterval();
|
|
}
|
|
m_socksTimeoutMs = now;
|
|
Debug(m_engine,DebugInfo,
|
|
"SOCKSConn(%s) starting timer now=" FMT64U " interval=" FMT64U " [%p]",
|
|
m_id.c_str(),m_socksTimeoutMs,interval,this);
|
|
m_socksTimeoutMs += interval;
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processAuthMethods(const SOCKSPacket& packet)
|
|
{
|
|
if (!packet.m_auth.length()) {
|
|
Debug(m_engine,DebugMild,"SOCKSConn(%s) received '%s' with no methods [%p]",
|
|
m_id.c_str(),packet.msgName(),this);
|
|
return false;
|
|
}
|
|
|
|
unsigned char* d = (unsigned char*)packet.m_auth.data();
|
|
unsigned char auth = SOCKSPacket::NotAuth;
|
|
if (m_epDef && m_epDef->authRequired()) {
|
|
for (unsigned int i = 0; i < packet.m_auth.length(); i++) {
|
|
if (d[i] == SOCKSPacket::UnamePwd) {
|
|
auth = SOCKSPacket::UnamePwd;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (auth == SOCKSPacket::NotAuth)
|
|
for (unsigned int i = 0; i < packet.m_auth.length(); i++) {
|
|
if (d[i] == SOCKSPacket::AuthNone) {
|
|
auth = SOCKSPacket::AuthNone;
|
|
break;
|
|
}
|
|
}
|
|
if (auth != SOCKSPacket::NotAuth)
|
|
return sendAuthReply(auth);
|
|
Debug(m_engine,DebugMild,
|
|
"SOCKSConn(%s) received '%s' with unsupported methods [%p]",
|
|
m_id.c_str(),packet.msgName(),this);
|
|
sendAuthReply(SOCKSPacket::NotAuth);
|
|
return false;
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processAuthReply(const SOCKSPacket& packet)
|
|
{
|
|
if (!packet.m_auth.length()) {
|
|
Debug(m_engine,DebugMild,"SOCKSConn(%s) received '%s' with no method [%p]",
|
|
m_id.c_str(),packet.msgName(),this);
|
|
return false;
|
|
}
|
|
unsigned char auth = *(unsigned char*)packet.m_auth.data();
|
|
if (auth == SOCKSPacket::AuthNone)
|
|
return sendRequest();
|
|
if (auth == SOCKSPacket::UnamePwd)
|
|
return sendUnamePwd();
|
|
Debug(m_engine,DebugNote,
|
|
"SOCKSConn(%s) received unsupported authentication method %u [%p]",
|
|
m_id.c_str(),auth,this);
|
|
return false;
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processUnamePwdRequest(const SOCKSPacket& packet)
|
|
{
|
|
if (m_epDef && packet.m_username == m_epDef->username() &&
|
|
packet.m_password == m_epDef->password()) {
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) authenticated [%p]",m_id.c_str(),this);
|
|
return sendUnamePwdReply(0);
|
|
}
|
|
Debug(m_engine,DebugNote,"SOCKSConn(%s) remote has incorrect credentials [%p]",
|
|
m_id.c_str(),this);
|
|
return sendUnamePwdReply(0xff);
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processUnamePwdReply(const SOCKSPacket& packet)
|
|
{
|
|
if (!packet.m_auth.length()) {
|
|
Debug(m_engine,DebugMild,"SOCKSConn(%s) received '%s' with no status [%p]",
|
|
m_id.c_str(),packet.msgName(),this);
|
|
return false;
|
|
}
|
|
unsigned char auth = *(unsigned char*)packet.m_auth.data();
|
|
// 0: authenticated
|
|
if (!auth) {
|
|
Debug(m_engine,DebugAll,"SOCKSConn(%s) authenticated [%p]",m_id.c_str(),this);
|
|
return sendRequest();
|
|
}
|
|
Debug(m_engine,DebugNote,
|
|
"SOCKSConn(%s) remote denyed authentication (code=%u) [%p]",
|
|
m_id.c_str(),auth,this);
|
|
return false;
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processRequest(const SOCKSPacket& packet)
|
|
{
|
|
// Update data
|
|
m_reqCmd = packet.m_cmdRsp;
|
|
m_reqAddrType = packet.m_addrType;
|
|
m_reqAddr = packet.m_addr;
|
|
m_reqPort = packet.m_port;
|
|
return true;
|
|
}
|
|
|
|
// Message processor. Return false to terminate the connection
|
|
bool SOCKSConn::processReply(const SOCKSPacket& packet)
|
|
{
|
|
// Update data
|
|
m_replyRsp = packet.m_cmdRsp;
|
|
m_replyAddrType = packet.m_addrType;
|
|
m_replyAddr = packet.m_addr;
|
|
m_replyPort = packet.m_port;
|
|
// OK
|
|
if (m_replyRsp == SOCKSPacket::EOk) {
|
|
DDebug(m_engine,DebugAll,"SOCKSConn(%s) processed %s [%p]",
|
|
m_id.c_str(),packet.msgName(),this);
|
|
return true;
|
|
}
|
|
// Error
|
|
Debug(m_engine,DebugNote,"SOCKSConn(%s) received %s with rsp %u: '%s' [%p]",
|
|
m_id.c_str(),packet.msgName(),m_replyRsp,
|
|
lookup(m_replyRsp,SOCKSPacket::s_replyText),this);
|
|
return false;
|
|
}
|
|
|
|
|
|
typedef GenPointer<SOCKSListener> ListenerPointer;
|
|
|
|
/*
|
|
* SOCKSEndpointDef
|
|
*/
|
|
SOCKSEndpointDef::SOCKSEndpointDef(const char* name, bool proxy,
|
|
const char* address, int port, const char* external,
|
|
const char* uname, const char* pwd)
|
|
: m_proxy(proxy), m_name(name),
|
|
m_address(address), m_externalAddr(external), m_port(port),
|
|
m_authRequired(false),
|
|
m_username(uname), m_password(pwd)
|
|
{
|
|
m_authRequired = !(m_username.null() || m_password.null());
|
|
}
|
|
|
|
SOCKSEndpointDef::SOCKSEndpointDef(NamedList& params)
|
|
{
|
|
m_name = params;
|
|
m_proxy = params.getBoolValue("proxy");
|
|
m_address = params.getValue("address");
|
|
m_port = params.getIntValue("port");
|
|
m_externalAddr = params.getValue("external_address");
|
|
m_username = params.getValue("username");
|
|
m_password = params.getValue("password");
|
|
m_authRequired = !(m_username.null() || m_password.null());
|
|
}
|
|
|
|
// Get the endpoint definiton name (id)
|
|
const String& SOCKSEndpointDef::toString() const
|
|
{
|
|
return name();
|
|
}
|
|
|
|
|
|
/*
|
|
* SOCKSListener
|
|
*/
|
|
SOCKSListener::SOCKSListener(SOCKSEngine* engine, SOCKSEndpointDef* epDef,
|
|
unsigned int backlog)
|
|
: m_epDef(epDef),
|
|
m_backlog(backlog), m_socket(0), m_listenError(false),
|
|
m_engine(engine), m_status(Created)
|
|
{
|
|
if (m_epDef)
|
|
m_id << m_epDef->address() << ":" << m_epDef->port();
|
|
DDebug(m_engine,DebugAll,"SOCKSListener(%s) created [%p]",m_id.c_str(),this);
|
|
}
|
|
|
|
SOCKSListener::~SOCKSListener()
|
|
{
|
|
terminate();
|
|
if (m_engine)
|
|
m_engine->removeListener(this);
|
|
DDebug(m_engine,DebugAll,"SOCKSListener(%s) destroyed [%p]",m_id.c_str(),this);
|
|
}
|
|
|
|
// Create and bind the socket
|
|
bool SOCKSListener::init()
|
|
{
|
|
if (m_socket)
|
|
terminate();
|
|
if (!m_epDef)
|
|
return false;
|
|
|
|
m_status = Initializing;
|
|
SocketAddr addr(PF_INET);
|
|
addr.host(m_epDef->address());
|
|
addr.port(m_epDef->port());
|
|
m_socket = new Socket;
|
|
bool ok = m_socket->create(PF_INET,SOCK_STREAM);
|
|
const char* op = 0;
|
|
if (ok) {
|
|
m_socket->setReuse();
|
|
ok = m_socket->bind(addr);
|
|
if (ok) {
|
|
ok = m_socket->setBlocking(false);
|
|
if (!ok)
|
|
op = "set blocking (false)";
|
|
}
|
|
else
|
|
op = "bind";
|
|
}
|
|
else
|
|
op = "create";
|
|
if (ok) {
|
|
m_status = Bind;
|
|
Debug(m_engine,DebugAll,"Listener(%s) bind succeeded [%p]",m_id.c_str(),this);
|
|
}
|
|
else {
|
|
if (!m_listenError) {
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
Debug(m_engine,DebugWarn,"Listener(%s) failed to %s socket. %d: '%s' [%p]",
|
|
m_id.c_str(),op,m_socket->error(),s.c_str(),this);
|
|
m_listenError = true;
|
|
}
|
|
terminate();
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// Start listening
|
|
bool SOCKSListener::startListen()
|
|
{
|
|
if (!(m_socket && m_socket->valid()))
|
|
return false;
|
|
if (m_socket->listen(m_backlog)) {
|
|
Debug(m_engine,DebugAll,"Listener(%s) started [%p]",m_id.c_str(),this);
|
|
m_listenError = false;
|
|
m_status = Listening;
|
|
return true;
|
|
}
|
|
if (!m_listenError) {
|
|
String s;
|
|
Thread::errorString(s,m_socket->error());
|
|
Debug(m_engine,DebugWarn,"Listener(%s) failed to start. %d: '%s' [%p]",
|
|
m_id.c_str(),m_socket->error(),s.c_str(),this);
|
|
m_listenError = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Check for incoming connections
|
|
Socket* SOCKSListener::accept(SocketAddr& addr)
|
|
{
|
|
if (!(m_socket && m_socket->valid()))
|
|
return 0;
|
|
Socket* sock = m_socket->accept(addr);
|
|
if (sock)
|
|
Debug(m_engine,DebugAll,"Listener(%s) got conn from '%s:%d' [%p]",
|
|
m_id.c_str(),addr.host().c_str(),addr.port(),this);
|
|
return sock;
|
|
}
|
|
|
|
// Terminate the socket
|
|
void SOCKSListener::terminate()
|
|
{
|
|
if (!m_socket)
|
|
return;
|
|
m_status = Terminated;
|
|
DDebug(m_engine,DebugAll,"Listener(%s) terminating socket [%p]",
|
|
m_id.c_str(),this);
|
|
SOCKSEngine::destroySocket(m_socket);
|
|
}
|
|
|
|
// Init, start listening and call accept() in a loop
|
|
void SOCKSListener::run()
|
|
{
|
|
Debug(m_engine,DebugAll,"Listener(%s) start running [%p]",m_id.c_str(),this);
|
|
if (init() && startListen())
|
|
while (true) {
|
|
if (Thread::check(false) || !m_engine || m_engine->exiting())
|
|
break;
|
|
SocketAddr addr(PF_INET);
|
|
Socket* sock = accept(addr);
|
|
bool processed = false;
|
|
if (sock) {
|
|
if (sock->setBlocking(false)) {
|
|
m_status = Accepting;
|
|
if (m_engine)
|
|
processed = m_engine->incomingConnection(this,sock,addr);
|
|
else
|
|
delete sock;
|
|
}
|
|
else {
|
|
String tmp;
|
|
Thread::errorString(tmp,sock->error());
|
|
Debug(m_engine,DebugNote,
|
|
"Listener(%s) failed to reset blocking for incoming conn from '%s:%d'. %d: %s [%p]",
|
|
m_id.c_str(),addr.host().c_str(),addr.port(),sock->error(),tmp.c_str(),this);
|
|
delete sock;
|
|
}
|
|
}
|
|
m_status = Listening;
|
|
if (processed)
|
|
Thread::yield(false);
|
|
else
|
|
Thread::idle(false);
|
|
}
|
|
terminate();
|
|
if (m_engine)
|
|
m_engine->removeListener(this);
|
|
Debug(m_engine,DebugAll,"Listener(%s) stopped [%p]",m_id.c_str(),this);
|
|
}
|
|
|
|
// Stop this listener. Terminate the socket
|
|
void SOCKSListener::stop(bool hard)
|
|
{
|
|
Debug(m_engine,DebugStub,"SOCKSListener(%s) stop() [%p]",m_id.c_str(),this);
|
|
}
|
|
|
|
|
|
/*
|
|
* SOCKSEngine
|
|
*/
|
|
SOCKSEngine::SOCKSEngine(NamedList& params)
|
|
: Mutex(true,"SOCKSEngine"),
|
|
m_exiting(false),
|
|
m_waitMsgAuthInterval(10000), m_waitMsgReplyInterval(15000),
|
|
m_showMsg(false), m_dumpExtended(false)
|
|
{
|
|
debugName(params.getValue("debugname","socks"));
|
|
DDebug(this,DebugAll,"SocksEngine created [%p]",this);
|
|
}
|
|
|
|
// Initialize engine's parameters
|
|
void SOCKSEngine::initialize(NamedList& params)
|
|
{
|
|
m_showMsg = params.getBoolValue("print-msg",false);
|
|
m_dumpExtended = params.getBoolValue("print-extended",false);
|
|
int tmp = params.getIntValue("auth-timeout",10000);
|
|
if (tmp < 3000)
|
|
tmp = 3000;
|
|
else if (tmp > 30000)
|
|
tmp = 30000;
|
|
m_waitMsgAuthInterval = tmp;
|
|
tmp = params.getIntValue("reply-timeout",30000);
|
|
if (tmp < 5000)
|
|
tmp = 5000;
|
|
else if (tmp > 120000)
|
|
tmp = 120000;
|
|
m_waitMsgReplyInterval = tmp;
|
|
}
|
|
|
|
// Cleanup the engine. Stop listeners
|
|
void SOCKSEngine::cleanup()
|
|
{
|
|
stopListeners(true,false);
|
|
m_socksConn.clear();
|
|
XDebug(this,DebugAll,"SOCKSEngine::cleanup()");
|
|
}
|
|
|
|
// Connect a connection, increase its reference counter, add it to the
|
|
// list and start negotisting SOCKS
|
|
bool SOCKSEngine::addConnection(SOCKSConn* conn)
|
|
{
|
|
if (!(conn && conn->connect()))
|
|
return false;
|
|
if (!conn->ref()) {
|
|
conn->terminate();
|
|
return false;
|
|
}
|
|
Lock lock(this);
|
|
m_socksConn.append(conn);
|
|
Debug(this,DebugAll,"Added outgoing connection (%p,'%s')",
|
|
conn,conn->toString().c_str());
|
|
return true;
|
|
}
|
|
|
|
// Incoming connection notification
|
|
bool SOCKSEngine::incomingConnection(SOCKSListener* listener, Socket* sock,
|
|
SocketAddr& addr)
|
|
{
|
|
if (!(listener && sock)) {
|
|
destroySocket(sock);
|
|
return false;
|
|
}
|
|
|
|
SOCKSConn* conn = 0;
|
|
if (listener->epDef() && listener->epDef()->ref()) {
|
|
if (!listener->epDef()->proxy())
|
|
conn = new SOCKSConn(this,sock,listener->epDef());
|
|
else {
|
|
// TODO: implement
|
|
Debug(this,DebugStub,"Please implement incomingConnection() for proxy");
|
|
}
|
|
}
|
|
if (!conn) {
|
|
destroySocket(sock);
|
|
return false;
|
|
}
|
|
Lock lock(this);
|
|
m_socksConn.append(conn);
|
|
Debug(this,DebugAll,"Added incoming connection (%p,'%s')",
|
|
conn,conn->toString().c_str());
|
|
return true;
|
|
}
|
|
|
|
// Process connections negotiating SOCKS
|
|
bool SOCKSEngine::process()
|
|
{
|
|
bool processed = false;
|
|
lock();
|
|
ListIterator iter(m_socksConn);
|
|
Time now;
|
|
for (;;) {
|
|
if (Thread::check(false) || exiting())
|
|
break;
|
|
SOCKSConn* conn = static_cast<SOCKSConn*>(iter.get());
|
|
// End of iteration?
|
|
if (!conn)
|
|
break;
|
|
RefPointer<SOCKSConn> connRef = conn;
|
|
// Dead pointer?
|
|
if (!connRef)
|
|
continue;
|
|
unlock();
|
|
processed = processSocksConnection(connRef,now) || processed;
|
|
lock();
|
|
// Destroy now the reference
|
|
connRef = 0;
|
|
}
|
|
unlock();
|
|
return processed;
|
|
}
|
|
|
|
// Process a connection negotiating the SOCKS protocol
|
|
bool SOCKSEngine::processSocksConnection(SOCKSConn* conn, const Time& now)
|
|
{
|
|
if (!conn)
|
|
return false;
|
|
Lock lock(conn);
|
|
if (conn->status() == SOCKSConn::Terminated) {
|
|
lock.drop();
|
|
removeSocksConn(conn,"terminated");
|
|
return false;
|
|
}
|
|
|
|
bool error = false;
|
|
bool timeout = false;
|
|
SOCKSPacket* packet = conn->processSocks(now,error,timeout);
|
|
const char* reason = 0;
|
|
if (packet) {
|
|
if (packet->type() == SOCKSPacket::Request) {
|
|
SOCKSPacket::Error err = processSOCKSRequest(*packet,conn);
|
|
if (err != SOCKSPacket::EOk) {
|
|
error = true;
|
|
defaultRequestHandler(conn,err);
|
|
}
|
|
}
|
|
else if (packet->type() == SOCKSPacket::Reply)
|
|
error = !processSOCKSReply(*packet,conn);
|
|
TelEngine::destruct(packet);
|
|
}
|
|
else if (!error)
|
|
return false;
|
|
else
|
|
reason = timeout ? "timeout" : "received invalid packet";
|
|
if (error) {
|
|
lock.drop();
|
|
socksConnError(conn,timeout);
|
|
removeSocksConn(conn,reason);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Send a packet through a connection
|
|
bool SOCKSEngine::sendPacket(SOCKSPacket* packet)
|
|
{
|
|
if (!(packet && packet->conn())) {
|
|
TelEngine::destruct(packet);
|
|
return false;
|
|
}
|
|
|
|
if (m_showMsg && debugAt(DebugInfo)) {
|
|
String tmp;
|
|
packet->toString(tmp,m_dumpExtended);
|
|
Debug(this,DebugInfo,"SOCKSConn(%s) sending message %s [%p]",
|
|
packet->conn()->toString().c_str(),tmp.c_str(),packet->conn());
|
|
}
|
|
|
|
unsigned int sent = packet->m_buffer.length();
|
|
bool ok = packet->conn()->send(packet->m_buffer.data(),sent) &&
|
|
sent == packet->m_buffer.length();
|
|
if (!ok)
|
|
Debug(this,DebugNote,"SOCKSConn(%s) failed to send message '%s' [%p]",
|
|
packet->conn()->toString().c_str(),packet->msgName(),packet->conn());
|
|
TelEngine::destruct(packet);
|
|
return ok;
|
|
}
|
|
|
|
// Print a debug message when a connections received a packet
|
|
void SOCKSEngine::receivedPacket(const SOCKSPacket& packet)
|
|
{
|
|
if (!(m_showMsg && debugAt(DebugInfo)))
|
|
return;
|
|
String tmp;
|
|
packet.toString(tmp,m_dumpExtended);
|
|
Debug(this,DebugInfo,"SOCKSConn(%s) receiving message %s [%p]",
|
|
packet.conn() ? packet.conn()->toString().c_str() : "",
|
|
tmp.c_str(),packet.conn());
|
|
}
|
|
|
|
// Add an endpoint definition. Replace an existing one with the same name
|
|
void SOCKSEngine::addEpDef(SOCKSEndpointDef* epDef)
|
|
{
|
|
if (!epDef)
|
|
return;
|
|
Lock lock(this);
|
|
if (m_epDef.find(epDef))
|
|
return;
|
|
ObjList* o = m_epDef.find(epDef->toString());
|
|
if (!o)
|
|
m_epDef.append(epDef);
|
|
else
|
|
o->set(epDef);
|
|
return;
|
|
}
|
|
|
|
// Remove an endpoint definition
|
|
void SOCKSEngine::removeEpDef(const String& name)
|
|
{
|
|
Lock lock(this);
|
|
ObjList* o = m_epDef.find(name);
|
|
if (o) {
|
|
// Remove listener
|
|
SOCKSEndpointDef* ep = static_cast<SOCKSEndpointDef*>(o->get());
|
|
for (ObjList* l = m_listeners.skipNull(); l; l = l->skipNext()) {
|
|
ListenerPointer* p = static_cast<ListenerPointer*>(l->get());
|
|
if ((*p)->epDef() == ep) {
|
|
(*p)->stop(false);
|
|
break;
|
|
}
|
|
}
|
|
// Remove object
|
|
o->remove();
|
|
}
|
|
}
|
|
|
|
// Find an endpoint definition by its name
|
|
SOCKSEndpointDef* SOCKSEngine::findEpDef(const String& name)
|
|
{
|
|
Lock lock(this);
|
|
ObjList* o = m_epDef.find(name);
|
|
if (!o)
|
|
return 0;
|
|
SOCKSEndpointDef* tmp = static_cast<SOCKSEndpointDef*>(o->get());
|
|
return tmp->ref() ? tmp : 0;
|
|
}
|
|
|
|
// Add a socket listener
|
|
void SOCKSEngine::addListener(SOCKSListener* listener)
|
|
{
|
|
if (!listener)
|
|
return;
|
|
Lock lock(this);
|
|
m_listeners.append(new ListenerPointer(listener))->setDelete(false);
|
|
Debug(this,DebugAll,"Added listener (%p)",listener);
|
|
}
|
|
|
|
// Remove a socket listener
|
|
void SOCKSEngine::removeListener(SOCKSListener* listener)
|
|
{
|
|
if (!listener)
|
|
return;
|
|
Lock lock(this);
|
|
for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) {
|
|
ListenerPointer* p = static_cast<ListenerPointer*>(o->get());
|
|
if (*p != listener)
|
|
continue;
|
|
o->remove(false);
|
|
Debug(this,DebugAll,"Removed listener (%p)",listener);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if a listener exists
|
|
bool SOCKSEngine::hasListener(SOCKSListener* listener, int& status)
|
|
{
|
|
if (!listener)
|
|
return false;
|
|
Lock lock(this);
|
|
for (ObjList* o = m_listeners.skipNull(); o; o = o->skipNext()) {
|
|
ListenerPointer* p = static_cast<ListenerPointer*>(o->get());
|
|
if (*p != listener)
|
|
continue;
|
|
status = listener->status();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Stop socket listeners
|
|
void SOCKSEngine::stopListeners(bool wait, bool hard)
|
|
{
|
|
Lock lock(this);
|
|
ObjList* o = m_listeners.skipNull();
|
|
if (!o)
|
|
return;
|
|
Debug(this,DebugAll,"Stopping socket listeners wait=%s hard=%s",
|
|
String::boolText(wait),String::boolText(hard));
|
|
for (; o; o = o->skipNext()) {
|
|
ListenerPointer* p = static_cast<ListenerPointer*>(o->get());
|
|
(*p)->stop(hard);
|
|
}
|
|
if (!wait) {
|
|
m_listeners.clear();
|
|
return;
|
|
}
|
|
lock.drop();
|
|
while (m_listeners.skipNull())
|
|
Thread::yield(true);
|
|
Debug(this,DebugAll,"Stopped all socket listeners");
|
|
}
|
|
|
|
// Remove and delete a connection from SOCKS list
|
|
void SOCKSEngine::removeSocksConn(SOCKSConn* conn, const char* reason)
|
|
{
|
|
if (!conn)
|
|
return;
|
|
Lock lock(this);
|
|
ObjList* o = m_socksConn.find(conn);
|
|
if (!o)
|
|
return;
|
|
Debug(this,DebugAll,"Removing connection (%p,'%s') reason=%s",
|
|
conn,conn->toString().c_str(),reason);
|
|
o->remove();
|
|
}
|
|
|
|
// Terminate and delete a socket
|
|
void SOCKSEngine::destroySocket(Socket*& sock)
|
|
{
|
|
if (!sock)
|
|
return;
|
|
Socket* tmp = sock;
|
|
sock = 0;
|
|
tmp->setLinger(-1);
|
|
tmp->terminate();
|
|
delete tmp;
|
|
}
|
|
|
|
// Process a SOCKS request
|
|
SOCKSPacket::Error SOCKSEngine::processSOCKSRequest(const SOCKSPacket& packet,
|
|
SOCKSConn* conn)
|
|
{
|
|
Debug(this,DebugStub,"processSOCKSRequest() conn (%p,%s)",
|
|
conn,conn ? conn->toString().c_str() : "");
|
|
return SOCKSPacket::EUnsuppCmd;
|
|
}
|
|
|
|
// Default SOCKS request. Sends a reply with 'rsp' non 0 (error).
|
|
// This method is called by the engine if request processor returns an error
|
|
void SOCKSEngine::defaultRequestHandler(SOCKSConn* conn, SOCKSPacket::Error err)
|
|
{
|
|
DDebug(this,DebugAll,"defaultRequestHandler(%u) conn (%p,%s)",
|
|
err,conn,conn ? conn->toString().c_str() : "");
|
|
if (!conn || err == SOCKSPacket::EOk)
|
|
return;
|
|
conn->sendReply(conn->reqAddrType(),conn->reqAddr(),conn->reqPort(),err);
|
|
}
|
|
|
|
// Process a SOCKS reply
|
|
bool SOCKSEngine::processSOCKSReply(const SOCKSPacket& packet, SOCKSConn* conn)
|
|
{
|
|
Debug(this,DebugStub,"processSOCKSReply() conn (%p,%s)",
|
|
conn,conn ? conn->toString().c_str() : "");
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksEngine
|
|
*/
|
|
YSocksEngine::YSocksEngine(NamedList& params)
|
|
: SOCKSEngine(params)
|
|
{
|
|
debugChain(&__plugin);
|
|
}
|
|
|
|
void YSocksEngine::initialize(NamedList& params)
|
|
{
|
|
SOCKSEngine::initialize(params);
|
|
if (debugAt(DebugInfo)) {
|
|
String tmp;
|
|
tmp << "auth-timeout=" << (unsigned int)m_waitMsgAuthInterval << "ms";
|
|
tmp << " reply-timeout=" << (unsigned int)m_waitMsgReplyInterval << "ms";
|
|
tmp << " print-msg=" << String::boolText(m_showMsg);
|
|
tmp << " print-extended=" << String::boolText(m_dumpExtended);
|
|
Debug(this,DebugInfo,"Initialized %s",tmp.c_str());
|
|
}
|
|
}
|
|
|
|
void YSocksEngine::cleanup()
|
|
{
|
|
SOCKSEngine::cleanup();
|
|
ListIterator iter(m_wrappers);
|
|
for (GenObject* o = 0; 0 != (o = iter.get());)
|
|
removeWrapper(static_cast<YSocksWrapper*>(o),false);
|
|
XDebug(this,DebugAll,"YSocksEngine::cleanup()");
|
|
}
|
|
|
|
// Find a wrapper with a given DST ADDR/PORT
|
|
// Return a referenced object if found
|
|
YSocksWrapper* YSocksEngine::findWrapper(bool client, const String& dstAddr, int dstPort)
|
|
{
|
|
Lock lock(this);
|
|
for (ObjList* o = m_wrappers.skipNull(); o; o = o->skipNext()) {
|
|
YSocksWrapper* w = static_cast<YSocksWrapper*>(o->get());
|
|
if (w->client() == client && w->dstPort() == dstPort && w->dstAddr() == dstAddr)
|
|
return w->ref() ? w : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Find a wrapper with a given connection
|
|
YSocksWrapper* YSocksEngine::findWrapper(SOCKSConn* conn)
|
|
{
|
|
Lock lock(this);
|
|
for (ObjList* o = m_wrappers.skipNull(); o; o = o->skipNext()) {
|
|
YSocksWrapper* w = static_cast<YSocksWrapper*>(o->get());
|
|
if (w->conn() == conn)
|
|
return w->ref() ? w : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Remove a wrapper from list
|
|
void YSocksEngine::removeWrapper(YSocksWrapper* w, bool delObj)
|
|
{
|
|
if (!w)
|
|
return;
|
|
Lock lock(this);
|
|
ObjList* o = m_wrappers.find(w);
|
|
if (!o)
|
|
return;
|
|
Debug(this,DebugAll,"Removing wrapper (%p,'%s')",w,w->toString().c_str());
|
|
o->remove(delObj);
|
|
}
|
|
|
|
// Add a wrapper
|
|
void YSocksEngine::addWrapper(YSocksWrapper* w)
|
|
{
|
|
if (!w)
|
|
return;
|
|
Lock lock(this);
|
|
m_wrappers.append(w)->setDelete(false);
|
|
Debug(this,DebugAll,"Added wrapper (%p,'%s')",w,w->toString().c_str());
|
|
}
|
|
|
|
SOCKSPacket::Error YSocksEngine::processSOCKSRequest(const SOCKSPacket& packet,
|
|
SOCKSConn* conn)
|
|
{
|
|
if (!conn)
|
|
return SOCKSPacket::EFailure;
|
|
|
|
if (conn->reqCmd() != SOCKSPacket::Connect) {
|
|
Debug(this,DebugNote,
|
|
"SOCKSConn(%s) %s with unsupported cmd %u (%s) [%p]",
|
|
conn->toString().c_str(),packet.msgName(),conn->reqCmd(),
|
|
SOCKSPacket::token(conn->reqCmd(),SOCKSPacket::s_cmdName),conn);
|
|
return SOCKSPacket::EUnsuppCmd;
|
|
}
|
|
|
|
if (conn->reqAddrType() != SOCKSPacket::Domain) {
|
|
Debug(this,DebugNote,
|
|
"SOCKSConn(%s) %s with unsupported address type %u (%s) [%p]",
|
|
conn->toString().c_str(),packet.msgName(),conn->reqAddrType(),
|
|
SOCKSPacket::token(conn->reqAddrType(),SOCKSPacket::s_addrTypeName),conn);
|
|
return SOCKSPacket::EUnsuppAddrType;
|
|
}
|
|
|
|
// Find a wrapper for the connection
|
|
YSocksWrapper* w = findWrapper(false,conn->reqAddr(),conn->reqPort());
|
|
if (w) {
|
|
// Set wrapper connection and remove it from engine on success
|
|
// Return error to deny the request and remove connection from engine
|
|
SOCKSPacket::Error result = SOCKSPacket::EOk;
|
|
if (w->setConn(conn))
|
|
removeSocksConn(conn,"accepted");
|
|
else
|
|
result = SOCKSPacket::EFailure;
|
|
TelEngine::destruct(w);
|
|
return result;
|
|
}
|
|
|
|
Debug(this,DebugNote,"SOCKSConn(%s) %s for unknown connection [%p]",
|
|
conn->toString().c_str(),packet.msgName(),conn);
|
|
return SOCKSPacket::EHostGone;
|
|
}
|
|
|
|
// Process SOCKS reply
|
|
// Always return false to remove the connection from engine's list
|
|
bool YSocksEngine::processSOCKSReply(const SOCKSPacket& packet, SOCKSConn* conn)
|
|
{
|
|
if (!conn)
|
|
return false;
|
|
YSocksWrapper* w = findWrapper(conn);
|
|
bool ok = (w && w->client());
|
|
if (ok) {
|
|
w->connRecvReply();
|
|
removeSocksConn(conn,"accepted");
|
|
}
|
|
TelEngine::destruct(w);
|
|
return ok;
|
|
}
|
|
|
|
// Connection error
|
|
void YSocksEngine::socksConnError(SOCKSConn* conn, bool timeout)
|
|
{
|
|
YSocksWrapper* w = findWrapper(conn);
|
|
if (!w)
|
|
return;
|
|
w->connError(timeout);
|
|
TelEngine::destruct(w);
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksWrapper
|
|
*/
|
|
// Build a wrapper (client if epDef is non 0)
|
|
YSocksWrapper::YSocksWrapper(const char* id, YSocksEngine* engine, CallEndpoint* cp,
|
|
NamedList& params, const char* notify, SOCKSEndpointDef* epDef)
|
|
: Mutex(true,"YSocksWrapper"),
|
|
m_state(Pending), m_client(epDef != 0), m_dir(0), m_autoStart(true),
|
|
m_id(id), m_notify(notify), m_callEp(cp), m_dstPort(0), m_srvPort(-1),
|
|
m_engine(engine), m_source(0), m_consumer(0), m_conn(0),
|
|
m_thread(0)
|
|
{
|
|
debugName(m_id);
|
|
debugChain(&__plugin);
|
|
m_media = params.getValue("media","data");
|
|
m_dstAddr = params.getValue("dst_addr_domain");
|
|
m_dstPort = params.getIntValue("dst_port",0);
|
|
m_dir = lookup(params.getValue("direction"),dict_conn_dir,SOCKSConn::Both);
|
|
m_autoStart = params.getBoolValue("autostart",false);
|
|
if (m_client)
|
|
m_conn = new SOCKSConn(engine,epDef,SOCKSPacket::Connect,
|
|
SOCKSPacket::Domain,m_dstAddr,m_dstPort);
|
|
else if (m_engine) {
|
|
SOCKSEndpointDef* srv = m_engine->findEpDef("server");
|
|
if (!srv) {
|
|
const char* lip = params.getValue("localip");
|
|
int attempts = lip ? 10 : 0;
|
|
// Try to build our own listener
|
|
for (int i = 0; i < attempts; i++) {
|
|
int port = (s_minPort + (Random::random() % (s_maxPort - s_minPort))) & 0xfffe;
|
|
srv = new SOCKSEndpointDef(m_id,false,lip,port,0,
|
|
params.getValue("username"),params.getValue("password"));
|
|
YSocksListenerThread* th = new YSocksListenerThread(m_engine,srv,1);
|
|
th->addAndStart();
|
|
// Wait for the thread to init and start or terminate
|
|
bool ok = false;
|
|
int status = SOCKSListener::Created;
|
|
while (m_engine->hasListener(th,status)) {
|
|
if (status < SOCKSListener::Listening) {
|
|
Thread::yield();
|
|
continue;
|
|
}
|
|
ok = status < SOCKSListener::Terminated;
|
|
break;
|
|
}
|
|
if (ok) {
|
|
srv->ref();
|
|
m_engine->addEpDef(srv);
|
|
break;
|
|
}
|
|
TelEngine::destruct(srv);
|
|
}
|
|
}
|
|
if (srv) {
|
|
m_srvAddr = srv->externalAddr() ? srv->externalAddr() : srv->address();
|
|
m_srvPort = srv->port();
|
|
TelEngine::destruct(srv);
|
|
}
|
|
}
|
|
if (canRecv())
|
|
m_recvBuffer.assign(0,s_bufLen);
|
|
Debug(this,DebugAll,"Created client=%s dst=%s:%d dir=%s autostart=%s [%p]",
|
|
String::boolText(m_client),m_dstAddr.c_str(),m_dstPort,
|
|
lookup(m_dir,dict_conn_dir),String::boolText(m_autoStart),this);
|
|
}
|
|
|
|
// Connect socket if client
|
|
bool YSocksWrapper::connect()
|
|
{
|
|
if (!(m_engine && m_client && m_conn))
|
|
return false;
|
|
bool ok = m_engine->addConnection(m_conn);
|
|
if (!ok)
|
|
Debug(this,DebugMild,"Failed to connect [%p]",this);
|
|
return ok;
|
|
}
|
|
|
|
// Client connection got reply
|
|
void YSocksWrapper::connRecvReply()
|
|
{
|
|
if (!m_conn)
|
|
return;
|
|
if (m_state != Pending) {
|
|
Debug(this,DebugNote,"Got reply in non-Pending state [%p]",this);
|
|
return;
|
|
}
|
|
|
|
if (m_conn->replyRsp() != SOCKSPacket::EOk) {
|
|
Lock lock(this);
|
|
Debug(this,DebugNote,"Got reply error %u '%s' [%p]",m_conn->replyRsp(),
|
|
SOCKSPacket::token(m_conn->replyRsp(),SOCKSPacket::s_replyText),this);
|
|
m_state = Terminated;
|
|
m_conn->terminate();
|
|
return;
|
|
}
|
|
|
|
DDebug(this,DebugInfo,"Got reply (connection accepted) [%p]",this);
|
|
m_state = WaitStart;
|
|
if (m_autoStart)
|
|
enableDataTransfer();
|
|
}
|
|
|
|
// Connection error while negotiating the protocol
|
|
void YSocksWrapper::connError(bool timeout)
|
|
{
|
|
Debug(this,DebugNote,"Connection got error while negotiating timeout=%s [%p]",
|
|
String::boolText(timeout),this);
|
|
Lock lock(this);
|
|
m_state = Terminated;
|
|
m_conn->terminate();
|
|
// TODO:
|
|
Debug(this,DebugStub,"Possible incomplete YSocksWrapper::connError() [%p]",this);
|
|
}
|
|
|
|
// Set connection with valid request for server wrapper
|
|
bool YSocksWrapper::setConn(SOCKSConn* conn)
|
|
{
|
|
if (!conn || m_client)
|
|
return false;
|
|
|
|
Lock lock(this);
|
|
if (m_conn) {
|
|
Debug(this,DebugNote,"Received duplicate request conn=%s [%p]",
|
|
conn->toString().c_str(),this);
|
|
return false;
|
|
}
|
|
if (!conn->ref())
|
|
return false;
|
|
Debug(this,DebugAll,"Received valid request conn=%s [%p]",
|
|
conn->toString().c_str(),this);
|
|
m_conn = conn;
|
|
m_state = WaitStart;
|
|
m_conn->sendReply(m_conn->reqAddrType(),m_dstAddr,m_dstPort);
|
|
if (m_autoStart)
|
|
enableDataTransfer();
|
|
lock.drop();
|
|
// Stop listener
|
|
if (m_engine)
|
|
m_engine->removeEpDef(m_id);
|
|
return true;
|
|
}
|
|
|
|
// Read data from conn and forward it
|
|
bool YSocksWrapper::recvData()
|
|
{
|
|
if (m_state != Running || !m_conn)
|
|
return false;
|
|
// Get source. Set its busy flag
|
|
s_srcMutex.lock();
|
|
YSocksSource* source = m_source;
|
|
if (source && source->alive())
|
|
source->busy(true);
|
|
else
|
|
source = 0;
|
|
s_srcMutex.unlock();
|
|
if (!source)
|
|
return false;
|
|
// The source will not be destroyed until we reset the busy flag
|
|
unsigned int len = m_recvBuffer.length();
|
|
m_conn->recv(m_recvBuffer.data(),len);
|
|
if (!len)
|
|
return false;
|
|
DataBlock block;
|
|
block.assign(m_recvBuffer.data(),len,false);
|
|
XDebug(this,DebugAll,"Forwarding %u bytes [%p]",len,this);
|
|
source->Forward(block);
|
|
block.clear(false);
|
|
source->busy(false);
|
|
return true;
|
|
}
|
|
|
|
// Enable data transfer. Change state, set source/consumer format
|
|
void YSocksWrapper::enableDataTransfer(const char* format)
|
|
{
|
|
Lock lock(this);
|
|
m_format = format;
|
|
if (m_state != WaitStart) {
|
|
m_autoStart = true;
|
|
return;
|
|
}
|
|
Debug(this,DebugInfo,"Enabling data transfer src=%p cons=%p format=%s [%p]",
|
|
m_source,m_consumer,m_format.c_str(),this);
|
|
if (m_conn)
|
|
m_conn->enableDataTransfer();
|
|
// Change format of source and/or consumer,
|
|
// reinstall them to rebuild codec chains
|
|
if (m_source) {
|
|
if (m_callEp) {
|
|
m_source->ref();
|
|
m_callEp->setSource(0,m_media);
|
|
}
|
|
m_source->m_format = m_format;
|
|
if (m_callEp) {
|
|
m_callEp->setSource(m_source,m_media);
|
|
m_source->deref();
|
|
}
|
|
}
|
|
if (m_consumer) {
|
|
if (m_callEp) {
|
|
m_consumer->ref();
|
|
m_callEp->setConsumer(0,m_media);
|
|
}
|
|
m_consumer->m_format = m_format;
|
|
if (m_callEp) {
|
|
m_callEp->setConsumer(m_consumer,m_media);
|
|
m_consumer->deref();
|
|
}
|
|
}
|
|
m_state = Running;
|
|
}
|
|
|
|
// Build data source
|
|
YSocksSource* YSocksWrapper::getSource()
|
|
{
|
|
if (!canRecv())
|
|
return 0;
|
|
if (m_source && m_source->ref())
|
|
return m_source;
|
|
return new YSocksSource(this);
|
|
}
|
|
|
|
// Build data consumer
|
|
YSocksConsumer* YSocksWrapper::getConsumer()
|
|
{
|
|
if (!canSend())
|
|
return 0;
|
|
if (m_consumer && m_consumer->ref())
|
|
return m_consumer;
|
|
return new YSocksConsumer(this);
|
|
}
|
|
|
|
// Build and start worker thread
|
|
bool YSocksWrapper::startWorker()
|
|
{
|
|
Lock lock(this);
|
|
if (m_thread)
|
|
return true;
|
|
lock.drop();
|
|
m_thread = new YSocksWrapperWorker(this);
|
|
bool ok = m_thread->startup();
|
|
if (!ok)
|
|
Debug(this,DebugGoOn,"Failed to start worker thread [%p]",this);
|
|
return ok;
|
|
}
|
|
|
|
// Build and start worker thread
|
|
void YSocksWrapper::stopWorker()
|
|
{
|
|
Lock lock(this);
|
|
if (!m_thread)
|
|
return;
|
|
bool hard = (m_conn && m_conn->status() == SOCKSConn::Connecting);
|
|
DDebug(this,DebugAll,"Stopping worker thread hard=%s [%p]",
|
|
String::boolText(hard),this);
|
|
m_thread->cancel(hard);
|
|
lock.drop();
|
|
while (!hard && m_thread)
|
|
Thread::yield(true);
|
|
}
|
|
|
|
// Get the wrapper id
|
|
const String& YSocksWrapper::toString() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
// Notify status in chan.notify
|
|
void YSocksWrapper::notify(const char* status) const
|
|
{
|
|
if (m_notify.null())
|
|
return;
|
|
XDebug(this,DebugAll,"Notifying %s notifier=%s [%p]",status,m_notify.c_str(),this);
|
|
Message* m = new Message("chan.notify");
|
|
m->addParam("module",__plugin.name());
|
|
m->addParam("id",m_id);
|
|
m->addParam("notify",m_notify);
|
|
m->addParam("status",status);
|
|
SocketAddr remote;
|
|
if (!client() && m_conn && m_conn->getAddr(false,remote)) {
|
|
m->addParam("remoteip",remote.host());
|
|
m->addParam("remoteport",String(remote.port()));
|
|
}
|
|
Engine::enqueue(m);
|
|
}
|
|
|
|
// Release memory
|
|
void YSocksWrapper::destroyed()
|
|
{
|
|
if (m_engine) {
|
|
m_engine->removeWrapper(this,false);
|
|
// Stop listener
|
|
if (!m_client)
|
|
m_engine->removeEpDef(m_id);
|
|
}
|
|
stopWorker();
|
|
lock();
|
|
if (m_source && m_source->alive())
|
|
TelEngine::destruct(m_source);
|
|
if (m_consumer && m_consumer->alive())
|
|
TelEngine::destruct(m_consumer);
|
|
if (m_engine && m_conn)
|
|
m_engine->removeSocksConn(m_conn,"terminated");
|
|
TelEngine::destruct(m_conn);
|
|
unlock();
|
|
Debug(this,DebugAll,"Destroyed [%p]",this);
|
|
RefObject::destroyed();
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksWrapperWorker
|
|
*/
|
|
void YSocksWrapperWorker::run()
|
|
{
|
|
if (!m_wrapper)
|
|
return;
|
|
Debug(&__plugin,DebugAll,"Worker started for (%p) '%s' [%p]",
|
|
m_wrapper,m_wrapper->toString().c_str(),this);
|
|
// Use while() to go to method exit point
|
|
while (true) {
|
|
// Connect client wrappers
|
|
if (m_wrapper->client() && !m_wrapper->connect())
|
|
break;
|
|
if (invalid())
|
|
break;
|
|
// Wait to transfer data
|
|
// NOTE: The SOCKS protocol is negotiated by the engine
|
|
bool waitStart = !m_wrapper->autoStart();
|
|
while (!invalid() && m_wrapper->state() != YSocksWrapper::Running) {
|
|
Thread::msleep(20);
|
|
if (waitStart && m_wrapper->state() == YSocksWrapper::WaitStart) {
|
|
waitStart = false;
|
|
m_wrapper->notify("established");
|
|
}
|
|
}
|
|
if (invalid())
|
|
break;
|
|
m_wrapper->notify("running");
|
|
// Read data
|
|
while (!invalid()) {
|
|
if (!m_wrapper->canRecv()) {
|
|
Thread::idle();
|
|
continue;
|
|
}
|
|
if (m_wrapper->recvData())
|
|
Thread::yield();
|
|
else
|
|
Thread::idle();
|
|
}
|
|
break;
|
|
}
|
|
m_wrapper->notify("terminated");
|
|
Debug(&__plugin,DebugAll,"Worker terminated for (%p) '%s' [%p]",
|
|
m_wrapper,m_wrapper->toString().c_str(),this);
|
|
m_wrapper->m_thread = 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksSource
|
|
*/
|
|
YSocksSource::YSocksSource(YSocksWrapper* w)
|
|
: m_wrapper(0), m_busy(false)
|
|
{
|
|
m_format = "";
|
|
if (w && w->ref()) {
|
|
m_wrapper = w;
|
|
m_format = m_wrapper->m_format;
|
|
m_wrapper->m_source = this;
|
|
}
|
|
Debug(m_wrapper,DebugAll,"YSocksSource(%s) [%p]",
|
|
m_wrapper ? m_wrapper->toString().c_str() : "",this);
|
|
}
|
|
|
|
// Remove from endpoint. Release memory
|
|
void YSocksSource::destroyed()
|
|
{
|
|
Debug(m_wrapper,DebugAll,"YSocksSource(%s) destroyed [%p]",
|
|
m_wrapper ? m_wrapper->toString().c_str() : "",this);
|
|
TelEngine::destruct(m_wrapper);
|
|
|
|
if (m_wrapper) {
|
|
s_srcMutex.lock();
|
|
YSocksWrapper* tmp = m_wrapper;
|
|
m_wrapper = 0;
|
|
tmp->m_source = 0;
|
|
s_srcMutex.unlock();
|
|
// Wait for any YSocksWrapper::readData() to finish
|
|
while (m_busy)
|
|
Thread::yield();
|
|
TelEngine::destruct(tmp);
|
|
}
|
|
DataSource::destroyed();
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksConsumer
|
|
*/
|
|
YSocksConsumer::YSocksConsumer(YSocksWrapper* w)
|
|
: m_wrapper(0)
|
|
{
|
|
m_format = "";
|
|
if (w && w->ref()) {
|
|
m_wrapper = w;
|
|
m_format = m_wrapper->m_format;
|
|
m_wrapper->m_consumer = this;
|
|
}
|
|
Debug(m_wrapper,DebugAll,"YSocksConsumer(%s) [%p]",
|
|
m_wrapper ? m_wrapper->toString().c_str() : "",this);
|
|
}
|
|
|
|
unsigned long YSocksConsumer::Consume(const DataBlock &data, unsigned long tStamp, unsigned long flags)
|
|
{
|
|
XDebug(m_wrapper,DebugAll,"Sending %u bytes [%p]",data.length(),m_wrapper);
|
|
unsigned int sent = data.length();
|
|
if (m_wrapper && m_wrapper->state() == YSocksWrapper::Running &&
|
|
m_wrapper->m_conn && m_wrapper->m_conn->send(data.data(),sent))
|
|
return sent;
|
|
return 0;
|
|
}
|
|
|
|
// Remove from endpoint. Release memory
|
|
void YSocksConsumer::destroyed()
|
|
{
|
|
Debug(m_wrapper,DebugAll,"YSocksConsumer(%s) destroyed [%p]",
|
|
m_wrapper ? m_wrapper->toString().c_str() : "",this);
|
|
if (m_wrapper) {
|
|
YSocksWrapper* tmp = m_wrapper;
|
|
m_wrapper = 0;
|
|
tmp->m_consumer = 0;
|
|
TelEngine::destruct(tmp);
|
|
}
|
|
DataConsumer::destroyed();
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksProcessThread
|
|
*/
|
|
void YSocksProcessThread::run()
|
|
{
|
|
while (s_engine && !s_engine->exiting() && !Engine::exiting()) {
|
|
if (Thread::check(false))
|
|
break;
|
|
if (s_engine->process())
|
|
Thread::yield(false);
|
|
else
|
|
Thread::idle(false);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* YSocksPlugin
|
|
*/
|
|
YSocksPlugin::YSocksPlugin()
|
|
: Module("socks","misc",true), m_wrapperId(0), m_init(false)
|
|
{
|
|
Output("Loaded module YSOCKS");
|
|
}
|
|
|
|
YSocksPlugin::~YSocksPlugin()
|
|
{
|
|
Output("Unloading module YSOCKS");
|
|
if (s_engine)
|
|
delete s_engine;
|
|
}
|
|
|
|
// 'chan.socks' message handler
|
|
bool YSocksPlugin::handleChanSocks(Message& msg)
|
|
{
|
|
NamedString* module = msg.getParam("module");
|
|
if (module && *module == name())
|
|
return false;
|
|
if (!s_engine)
|
|
return false;
|
|
|
|
RefObject* userdata = msg.userData();
|
|
CallEndpoint* cp = static_cast<CallEndpoint*>(userdata ? userdata->getObject("CallEndpoint") : 0);
|
|
if (!cp) {
|
|
Debug(&__plugin,DebugMild,"%s without data endpoint",msg.c_str());
|
|
return 0;
|
|
}
|
|
|
|
String* addr = msg.getParam("dst_addr_domain");
|
|
if (null(addr)) {
|
|
Debug(this,DebugNote,"%s with empty dst_addr_domain",msg.c_str());
|
|
return false;
|
|
}
|
|
|
|
bool client = msg.getBoolValue("client",true);
|
|
int port = msg.getIntValue("dst_port",0);
|
|
YSocksWrapper* w = s_engine->findWrapper(client,*addr,port);
|
|
XDebug(this,DebugAll,"Processing %s client=%u auth=%s port=%d found %p",
|
|
msg.c_str(),client,addr->c_str(),port,w);
|
|
if (!w) {
|
|
// Get and check required parameters
|
|
// Build client or server wrapper
|
|
SOCKSEndpointDef* epDef = 0;
|
|
if (client) {
|
|
epDef = new SOCKSEndpointDef("",true,msg.getValue("remoteip"),
|
|
msg.getIntValue("remoteport"),
|
|
msg.getValue("username"),msg.getValue("password"));
|
|
if (!epDef->address() || epDef->port() <= 0) {
|
|
Debug(&__plugin,DebugMild,"%s with invalid remote addr=%s:%s",
|
|
msg.c_str(),msg.getValue("remoteip"),msg.getValue("remoteport"));
|
|
TelEngine::destruct(epDef);
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
epDef = s_engine->findEpDef("server");
|
|
if (!(epDef || msg.getValue("localip"))) {
|
|
Debug(&__plugin,DebugNote,"%s: No server defined",msg.c_str());
|
|
return false;
|
|
}
|
|
TelEngine::destruct(epDef);
|
|
}
|
|
|
|
String id;
|
|
buildWrapperId(id);
|
|
w = new YSocksWrapper(id,s_engine,cp,msg,cp->id(),epDef);
|
|
if (!w->startWorker()) {
|
|
TelEngine::destruct(w);
|
|
return false;
|
|
}
|
|
s_engine->addWrapper(w);
|
|
|
|
if (w->media()) {
|
|
YSocksSource* s = w->getSource();
|
|
YSocksConsumer* c = w->getConsumer();
|
|
cp->setSource(s,w->media());
|
|
cp->setConsumer(c,w->media());
|
|
TelEngine::destruct(s);
|
|
TelEngine::destruct(c);
|
|
}
|
|
}
|
|
|
|
// Add server and client params
|
|
if (!w->client()) {
|
|
msg.setParam("address",w->srvAddr());
|
|
msg.setParam("port",String(w->srvPort()));
|
|
// Add remote ip
|
|
SocketAddr remote;
|
|
if (w->conn() && w->conn()->getAddr(false,remote)) {
|
|
msg.addParam("remoteip",remote.host());
|
|
msg.addParam("remoteport",String(remote.port()));
|
|
}
|
|
}
|
|
msg.setParam("notifier",w->toString());
|
|
// Start ?
|
|
const char* format = msg.getValue("format");
|
|
if (!null(format))
|
|
w->enableDataTransfer(format);
|
|
return !w->deref();
|
|
}
|
|
|
|
void YSocksPlugin::initialize()
|
|
{
|
|
Output("Initializing module YSOCKS");
|
|
Configuration cfg(Engine::configFile("ysockschan"));
|
|
|
|
NamedList dummy("");
|
|
NamedList* general = cfg.getSection("general");
|
|
if (!general)
|
|
general = &dummy;
|
|
|
|
if (!m_init) {
|
|
s_statusCmd = "status " + name();
|
|
setup();
|
|
installRelay(Halt);
|
|
installRelay(ChanSocks,"chan.socks",50);
|
|
s_engine = new YSocksEngine(*general);
|
|
(new YSocksProcessThread)->startup();
|
|
}
|
|
m_init = true;
|
|
|
|
s_engine->initialize(*general);
|
|
|
|
int tmp = general->getIntValue("buflen",4096);
|
|
s_bufLen = (tmp >= 1024 ? tmp : 1024);
|
|
|
|
// Update proxy list
|
|
unsigned int n = cfg.sections();
|
|
for (unsigned int i = 0; i < n; i++) {
|
|
NamedList* sect = cfg.getSection(i);
|
|
if (!sect || *sect == "general")
|
|
continue;
|
|
|
|
bool enabled = sect->getBoolValue("enable",false);
|
|
if (!enabled) {
|
|
s_engine->removeEpDef(*sect);
|
|
continue;
|
|
}
|
|
|
|
SOCKSEndpointDef* epDef = new SOCKSEndpointDef(*sect);
|
|
if (epDef->address().null() || epDef->port() < 0) {
|
|
Debug(this,DebugNote,"Invalid endpoint def '%s' in config (addr=%s port=%s)",
|
|
sect->c_str(),epDef->address().c_str(),sect->getValue("port"));
|
|
TelEngine::destruct(epDef);
|
|
continue;
|
|
}
|
|
// Check changes
|
|
SOCKSEndpointDef* p = s_engine->findEpDef(*sect);
|
|
if (p) {
|
|
if (p->address() == epDef->address() && p->port() == epDef->port() &&
|
|
p->username() == epDef->username() && p->password() == epDef->password())
|
|
TelEngine::destruct(epDef);
|
|
else
|
|
s_engine->removeEpDef(*sect);
|
|
TelEngine::destruct(p);
|
|
}
|
|
if (epDef) {
|
|
if (*sect == "server" || sect->getBoolValue("incoming",true))
|
|
(new YSocksListenerThread(s_engine,epDef,5))->addAndStart();
|
|
s_engine->addEpDef(epDef);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Common message relay handler
|
|
bool YSocksPlugin::received(Message& msg, int id)
|
|
{
|
|
if (id == ChanSocks)
|
|
return handleChanSocks(msg);
|
|
if (id == Status) {
|
|
String target = msg.getValue("module");
|
|
// Target is the driver or channel
|
|
if (!target || target == name())
|
|
return Module::received(msg,id);
|
|
// Check additional commands
|
|
if (!target.startSkip(name(),false))
|
|
return false;
|
|
target.trimBlanks();
|
|
if (target == "listeners") {
|
|
lock();
|
|
msg.retValue() << "name=" << name() << ",type=" << type();
|
|
unlock();
|
|
if (!(s_engine && s_engine->lock(1000000)))
|
|
return true;
|
|
msg.retValue() << ";count=" << s_engine->listeners().count();
|
|
msg.retValue() << ";format=Status";
|
|
for (ObjList* o = s_engine->listeners().skipNull(); o; o = o->skipNext()) {
|
|
ListenerPointer* p = static_cast<ListenerPointer*>(o->get());
|
|
SocketAddr addr;
|
|
(*p)->getAddr(addr);
|
|
msg.retValue() << ";" << addr.host() << ":" << addr.port();
|
|
msg.retValue() << "=" << lookup((*p)->status(),SOCKSListener::s_statusName);
|
|
}
|
|
s_engine->unlock();
|
|
msg.retValue() << "\r\n";
|
|
}
|
|
return false;
|
|
}
|
|
if (id == Halt)
|
|
unload();
|
|
return Module::received(msg,id);
|
|
}
|
|
|
|
void YSocksPlugin::statusParams(String& str)
|
|
{
|
|
if (!s_engine)
|
|
return;
|
|
Lock lock(s_engine);
|
|
str.append("wrappers=",",") << s_engine->m_wrappers.count();
|
|
}
|
|
|
|
void YSocksPlugin::statusDetail(String& str)
|
|
{
|
|
if (!s_engine)
|
|
return;
|
|
Lock lock(s_engine);
|
|
str.append("format=Notify|ConnStatus");
|
|
for (ObjList* o = s_engine->m_wrappers.skipNull(); o; o = o->skipNext()) {
|
|
YSocksWrapper* w = static_cast<YSocksWrapper*>(o->get());
|
|
Lock lockW(w);
|
|
str.append(w->toString(),";") << "=" << w->notify();
|
|
str << "|" << SOCKSConn::statusName(w->conn() ? w->conn()->status() : SOCKSConn::Terminated);
|
|
}
|
|
}
|
|
|
|
// Unload the module: uninstall the relays
|
|
bool YSocksPlugin::unload()
|
|
{
|
|
DDebug(this,DebugAll,"Cleanup");
|
|
if (s_engine) {
|
|
s_engine->setExiting();
|
|
s_engine->cleanup();
|
|
}
|
|
if (!lock(500000))
|
|
return false;
|
|
uninstallRelays();
|
|
unlock();
|
|
return true;
|
|
}
|
|
|
|
// Handle command complete requests
|
|
bool YSocksPlugin::commandComplete(Message& msg, const String& partLine,
|
|
const String& partWord)
|
|
{
|
|
if (partLine.null() && partWord.null())
|
|
return false;
|
|
|
|
bool status = partLine.startsWith("status");
|
|
if (!status)
|
|
return Module::commandComplete(msg,partLine,partWord);
|
|
|
|
// Add additional commands
|
|
if (partLine == s_statusCmd) {
|
|
for (String* list = s_statusCmds; !null(list); list++)
|
|
if (!partWord || list->startsWith(partWord))
|
|
Module::itemComplete(msg.retValue(),*list,partWord);
|
|
return true;
|
|
}
|
|
return Module::commandComplete(msg,partLine,partWord);
|
|
}
|
|
|
|
}; // anonymous namespace
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|