yate/modules/ysockschan.cpp

4007 lines
110 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-2014 Null Team
*
* This software is distributed under multiple licenses;
* see the COPYING file in the main directory for licensing
* information for this specific distribution.
*
* This use of this software may be subject to additional restrictions.
* See the LEGAL file in the main directory for details.
*
* 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.
*/
#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 YSocksConnectThread; // A connect 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 External (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_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);
}
/**
* Set connecting state (outgoing only)
*/
void setConnecting();
/**
* Set socket (outgoing only)
* @param sock The socket. It will be consumed
* @param sendAuthMeth True to send auth methods after succesfully connected
* @return True on success
*/
bool setSocket(Socket* sock, 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); }
/**
* Connect a socket
* @param engine The engine owning the object requesting connect (used for debug)
* @param address Address to connect to
* @param port Port to connect to
* @param connToutMs Connect timeout in milliseconds
* @param error Error code on failure
* @param timeout Connection timeout flag
* @return Connected Socket pointer, 0 on failure
*/
static Socket* connect(SOCKSEngine* engine, const String& address, int port,
unsigned int connToutMs, int& error, bool& timeout);
/**
* 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; }
/**
* Retrieve the connect timeout interval
* @return Connect timeout interval in milliseconds
*/
inline unsigned int connectTimeout() const
{ return m_connectToutMs; }
/**
* 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 when connected
* @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);
/**
* Retrieve connect timeout from parameters
* @param params Parameter list
* @param defVal Default value to return if missing/invalid
* @return Connect timeout value
*/
static inline unsigned int getConnectTimeout(const NamedList& params,
unsigned int defVal) {
unsigned int val = params.getIntValue(YSTRING("connect_timeout"),defVal,0,120000);
if (!val || val >= 1000)
return val;
return 1000;
}
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;
unsigned int m_connectToutMs; // Connect timeout in milliseconds
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. Return a referenced object if found
YSocksWrapper* findWrapper(const String& wID);
// Find a wrapper with a given connection
// Return a referenced object if found
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,
Connecting,
WaitStart,
Established,
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; }
inline unsigned int connectTimeoutInterval() const
{ return m_connectToutMs; }
inline YSocksEngine* engine() const
{ return m_engine; }
// Connect socket if client
bool connect();
void connectTerminated(YSocksConnectThread* th, Socket* sock, int error,
bool timeout);
// 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(bool wait);
// 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(int stat);
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;
// Client connect
unsigned int m_connectToutMs; // Connect timeout interval
YSocksConnectThread* m_connect; // Connect 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; }
inline bool shouldSendEmpty() {
if (m_sentEmpty)
return false;
Lock lck(this);
m_sentEmpty = (0 != m_consumers.skipNull());
return m_sentEmpty;
}
inline void resetSendEmpty()
{ m_sentEmpty = true; }
protected:
// Remove from wrapper. Release memory
virtual void destroyed();
private:
YSocksWrapper* m_wrapper;
volatile bool m_busy;
bool m_sentEmpty;
};
// 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("SOCKSListen",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("SOCKSProcess",prio)
{}
void run();
};
// A connect thread
class YSocksConnectThread : public Thread
{
public:
YSocksConnectThread(YSocksWrapper* w, Thread::Priority prio = Thread::Normal);
virtual void cleanup()
{ notify(); }
virtual void run();
protected:
void notify(Socket* sock = 0, int error = 0, bool timeout = false);
YSocksEngine* m_engine;
String m_wrapperId;
String m_address;
int m_port;
unsigned int m_toutIntervalMs;
};
// 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] = {(unsigned char)(port >> 8),(unsigned char)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,DebugCrit,
"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;
}
// Set connecting state (outgoing only)
void SOCKSConn::setConnecting()
{
if (!outgoing())
return;
if (m_socket)
terminate();
changeStatus(Connecting);
}
// Set socket (outgoing only)
bool SOCKSConn::setSocket(Socket* sock, bool sendAuthMeth)
{
Lock lck(this);
if (!outgoing() || m_status != Connecting) {
SOCKSEngine::destroySocket(sock);
return false;
}
if (m_socket)
terminate();
changeStatus(Idle);
m_socket = sock;
buildId();
Debug(m_engine,DebugAll,"SOCKSConn(%s)::setSocket(%p) [%p]",m_id.c_str(),m_socket,this);
if (m_socket) {
m_socket->setBlocking(false);
if (sendAuthMeth)
sendAuthMethods();
return true;
}
terminate();
return false;
}
// 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 (read) {
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;
}
// Connect a socket
Socket* SOCKSConn::connect(SOCKSEngine* engine, const String& address, int port,
unsigned int connToutMs, int& error, bool& timeout)
{
SocketAddr addr(PF_INET);
addr.host(address);
if (!addr.host()) {
Debug(engine,DebugNote,"Failed to resolve '%s'",address.c_str());
error = Thread::lastError();
return 0;
}
addr.port(port);
String sa;
if (!engine || engine->debugAt(DebugNote)) {
sa << addr.host().c_str() << ":" << addr.port();
if (addr.host() != address)
sa << " (" << address << ")";
}
Debug(engine,DebugAll,"Connecting to '%s'",sa.safe());
Socket* sock = new Socket;
bool ok = false;
error = 0;
timeout = false;
if (sock->create(PF_INET,SOCK_STREAM)) {
if (connToutMs && sock->canSelect() && sock->setBlocking(false))
ok = sock->connectAsync(addr,connToutMs * 1000,&timeout);
else
ok = sock->connect(addr);
if (Thread::check(false)) {
SOCKSEngine::destroySocket(sock);
XDebug(engine,DebugAll,"Connect to %s cancelled",sa.c_str());
return 0;
}
}
if (ok) {
Debug(engine,DebugAll,"Connected to '%s'",sa.safe());
return sock;
}
if (!timeout)
error = sock->error();
SOCKSEngine::destroySocket(sock);
if (!engine || engine->debugAt(DebugNote)) {
String s;
if (timeout)
s = "Timeout";
else {
String tmp;
Thread::errorString(tmp,error);
s << error << " " << tmp;
}
Debug(engine,DebugNote,"Failed to connect to %s: %s",sa.c_str(),s.c_str());
}
return 0;
}
// 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_connectToutMs(0),
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(YSTRING("print-msg"),false);
m_dumpExtended = params.getBoolValue(YSTRING("print-extended"),false);
m_waitMsgAuthInterval = params.getIntValue(YSTRING("auth-timeout"),10000,3000,30000);
m_waitMsgReplyInterval = params.getIntValue(YSTRING("reply-timeout"),30000,5000,120000);
m_connectToutMs = getConnectTimeout(params,10000);
}
// 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)
return false;
if (!conn->ref()) {
conn->terminate();
return false;
}
Lock lck(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);
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;
if (error) {
lock.drop();
socksConnError(conn,timeout);
removeSocksConn(conn,timeout ? "timeout" : "received invalid packet");
}
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
YSocksWrapper* YSocksEngine::findWrapper(const String& wID)
{
if (!wID)
return 0;
Lock lock(this);
ObjList* o = m_wrappers.find(wID);
if (!o)
return 0;
YSocksWrapper* w = static_cast<YSocksWrapper*>(o->get());
return w->ref() ? w : 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);
GenObject* gen = m_wrappers.remove(w,false);
if (!(gen && gen->alive()))
return;
Debug(this,DebugAll,"Removed wrapper (%p,'%s') delObj=%s",
w,w->toString().c_str(),String::boolText(delObj));
if (delObj)
TelEngine::destruct(gen);
}
// 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),
m_connectToutMs(0), m_connect(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_connectToutMs = SOCKSEngine::getConnectTimeout(params,engine->connectTimeout());
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()
{
Lock lck(this);
if (!(m_engine && m_client && m_conn))
return false;
if (m_connect)
m_connect->cancel();
m_connect = new YSocksConnectThread(this);
if (!m_connect->startup()) {
Debug(this,DebugWarn,"Failed to start connect thread [%p]",this);
return false;
}
XDebug(this,DebugAll,"Started connect thread (%p) [%p]",m_connect,this);
m_conn->setConnecting();
u_int64_t tout = 0;
if (m_connectToutMs)
tout = Time::now() + m_connectToutMs * 1000 + 500000;
lck.drop();
// Wait for connect to complete
bool timeout = false;
while (m_connect && !timeout) {
Thread::idle();
if (Thread::check(false))
break;
if (tout)
timeout = tout < Time::now();
}
lck.acquire(this);
if (m_connect) {
m_connect->cancel();
m_connect = 0;
if (!m_conn)
return false;
m_conn->setSocket(0);
if (timeout)
Debug(this,DebugNote,"Connect timed out [%p]",this);
else
XDebug(this,DebugAll,"Worker cancelled while connecting [%p]",this);
return false;
}
if (m_conn && m_conn->valid() && !Thread::check(false))
return m_engine->addConnection(m_conn);
return false;
}
void YSocksWrapper::connectTerminated(YSocksConnectThread* th, Socket* sock, int error,
bool timeout)
{
XDebug(this,DebugAll,"connectTerminated(%p,%p,%d,%d) [%p]",th,sock,error,timeout,this);
if (!(th && m_connect)) {
if (sock)
SOCKSEngine::destroySocket(sock);
return;
}
Lock lck(this);
if (m_connect != th || !m_conn) {
if (sock)
SOCKSEngine::destroySocket(sock);
return;
}
m_connect = 0;
m_conn->setSocket(sock);
if (sock)
return;
if (!debugAt(DebugMild))
return;
String err;
if (timeout)
Debug(this,DebugMild,"Connect to '%s:%d' timeout [%p]",
m_conn->epDef()->address().c_str(),m_conn->epDef()->port(),this);
else {
String s;
if (error) {
String tmp;
Thread::errorString(tmp,error);
s << ": " << error << " " << tmp;
}
String addr;
if (m_conn->epDef())
addr << m_conn->epDef()->address() << ":" << m_conn->epDef()->port();
Debug(this,DebugMild,"Failed to connect to '%s'%s [%p]",
addr.c_str(),s.safe(),this);
}
}
// 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);
notify(Terminated);
stopWorker(false);
Lock lock(this);
m_state = Terminated;
m_conn->terminate();
}
// 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) {
if (source->shouldSendEmpty()) {
DataBlock block;
XDebug(this,DebugAll,"Forwarding empty block [%p]",this);
source->Forward(DataBlock::empty());
}
source->busy(false);
return false;
}
source->resetSendEmpty();
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);
if (m_thread->startup())
return true;
m_thread = 0;
Debug(this,DebugCrit,"Failed to start worker thread [%p]",this);
return false;
}
// Build and start worker thread
void YSocksWrapper::stopWorker(bool wait)
{
Lock lock(this);
if (!m_thread)
return;
if (m_connect) {
m_connect->cancel();
m_connect = 0;
if (m_conn)
m_conn->setSocket(0);
}
bool hard = (m_conn && m_conn->status() == SOCKSConn::Connecting);
DDebug(this,DebugAll,"Stopping worker thread hard=%s wait=%s [%p]",
String::boolText(hard),String::boolText(wait),this);
m_thread->cancel(hard);
if (hard) {
m_thread = 0;
return;
}
if (!wait)
return;
lock.drop();
#ifdef XDEBUG
Debugger debug("YSocksWrapper::stopWorker"," %p crt=%p,'%s' [%p]",
m_thread,Thread::current(),Thread::currentName(),this);
#endif
while (m_thread)
Thread::idle(true);
}
// Get the wrapper id
const String& YSocksWrapper::toString() const
{
return m_id;
}
// Notify status in chan.notify
void YSocksWrapper::notify(int stat)
{
Lock lck(this);
if (m_state == Terminated)
return;
if (m_notify.null())
return;
const char* what = 0;
switch (stat) {
case Established:
what = "established";
break;
case Running:
what = "running";
break;
case Terminated:
what = "terminated";
break;
default:
return;
}
XDebug(this,DebugAll,"Notifying %s notifier=%s [%p]",what,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",what);
SocketAddr remote;
if (!client() && m_conn && m_conn->getAddr(false,remote)) {
m->addParam("remoteip",remote.host());
m->addParam("remoteport",String(remote.port()));
}
lck.drop();
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(true);
lock();
if (m_source && m_source->alive())
TelEngine::destruct(m_source);
if (m_consumer && m_consumer->alive())
TelEngine::destruct(m_consumer);
SOCKSConn* tmp = m_conn;
TelEngine::destruct(m_conn);
if (m_connect) {
m_connect->cancel();
m_connect = 0;
}
unlock();
if (m_engine && tmp)
m_engine->removeSocksConn(tmp,"terminated");
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::idle();
if (waitStart && m_wrapper->state() == YSocksWrapper::WaitStart) {
waitStart = false;
m_wrapper->notify(YSocksWrapper::Established);
}
}
if (invalid())
break;
m_wrapper->notify(YSocksWrapper::Running);
// Read data
while (!invalid()) {
if (!m_wrapper->canRecv()) {
Thread::idle();
continue;
}
m_wrapper->recvData();
Thread::idle();
}
break;
}
m_wrapper->notify(YSocksWrapper::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_sentEmpty(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);
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);
}
}
/*
* YSocksConnectThread
*/
YSocksConnectThread::YSocksConnectThread(YSocksWrapper* w, Thread::Priority prio)
: Thread("SOCKSConnect",prio),
m_engine(0), m_port(0), m_toutIntervalMs(0)
{
if (!(w && w->engine()))
return;
m_engine = w->engine();
m_wrapperId = w->toString();
m_toutIntervalMs = w->connectTimeoutInterval();
if (w->conn() && w->conn()->epDef()) {
m_address = w->conn()->epDef()->address();
m_port = w->conn()->epDef()->port();
}
}
void YSocksConnectThread::run()
{
Socket* sock = 0;
int error = 0;
bool tout = false;
if (m_address)
sock = SOCKSConn::connect(static_cast<SOCKSEngine*>(m_engine),m_address,m_port,
m_toutIntervalMs,error,tout);
notify(sock,error,tout);
}
void YSocksConnectThread::notify(Socket* sock, int error, bool timeout)
{
YSocksWrapper* w = m_engine ? m_engine->findWrapper(m_wrapperId) : 0;
m_engine = 0;
if (w) {
w->connectTerminated(this,sock,error,timeout);
TelEngine::destruct(w);
}
else if (sock)
SOCKSEngine::destroySocket(sock);
}
/*
* 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(YATOM("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: */