Support for multiple rmanager listeners, each with its own settings.

Support for SSL encrypted connections (needs the openssl module).
Example of connecting with SSL activated: telnet-ssl -z ssl 10.0.0.1 5038


git-svn-id: http://voip.null.ro/svn/yate@2948 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2009-11-14 14:38:57 +00:00
parent 1f6771971e
commit 403425fd81
2 changed files with 219 additions and 92 deletions

View File

@ -1,7 +1,9 @@
[general] [general]
; This section controls the behaviour of the Remote Manager ; Each section creates a connection listener in the Remote Manager.
; An empty (all defaults) general section is assumed only in server mode if the
; configuration file is missing.
; port: int: TCP Port to listen on, 0 to disable module ; port: int: TCP Port to listen on, 0 to disable the listener
;port=5038 ;port=5038
; addr: ipaddress: IP address to bind to ; addr: ipaddress: IP address to bind to
@ -25,8 +27,22 @@
;telnet=yes ;telnet=yes
; output: bool: Enable output as soon as connecting ; output: bool: Enable output as soon as connecting
; This setting is ignored if an userpass is set
;output=no ;output=no
; interactive: bool: Disable the TCP coalescing to improve interactivity ; interactive: bool: Disable the TCP coalescing to improve interactivity
; this is almost never required and needs Yate to run as superuser ; This is almost never required and needs Yate to run as superuser
;interactive=no ;interactive=no
; context: string: SSL context to use to secure the connection
; Setting a context enables SSL on the listener and overrides any domain
;context=
; domain: string: Domain used to identify the SSL context to use
; Setting a domain enables SSL on the listener
;domain=
; verify: keyword: SSL handshake client certificate verification type
; For acceptable values see the documentation of the openssl module
; By default no client certificate is required
;verify=

View File

@ -127,23 +127,63 @@ static void completeWords(String& str, const char** list, const char* partial =
} }
} }
static Configuration s_cfg; static Mutex s_mutex(true,"RManager");
static bool s_output = false;
/* I need this here because i'm gonna use it in both classes */
Socket s_sock;
Mutex s_mutex(true,"RManager");
//we gonna create here the list with all the new connections. //we gonna create here the list with all the new connections.
static ObjList s_connList; static ObjList s_connList;
// the incomming connections listeners list
static ObjList s_listeners;
class Connection;
class RManagerThread;
class SockRef : public RefObject
{
public:
inline SockRef(Socket** sock)
: m_sock(sock)
{ }
void* getObject(const String& name) const
{
if (name == "Socket*")
return m_sock;
return RefObject::getObject(name);
}
private:
Socket** m_sock;
};
class RManagerListener : public RefObject
{
friend class RManagerThread;
public:
inline RManagerListener(const NamedList& sect)
: m_cfg(sect)
{ }
~RManagerListener();
void init();
inline NamedList& cfg()
{ return m_cfg; }
private:
void run();
bool initSocket();
Connection* checkCreate(Socket* sock, const char* addr);
NamedList m_cfg;
Socket m_socket;
String m_address;
};
class RManagerThread : public Thread class RManagerThread : public Thread
{ {
public: public:
RManagerThread() : Thread("RManager Listener") { } inline RManagerThread(RManagerListener* listener)
virtual void run(); : Thread("RManager Listener"), m_listener(listener)
{ }
virtual void run()
{ m_listener->run(); }
private: private:
RefPointer<RManagerListener> m_listener;
}; };
class Connection : public GenObject, public Thread class Connection : public GenObject, public Thread
@ -154,7 +194,7 @@ public:
User, User,
Admin Admin
}; };
Connection(Socket* sock, const char* addr); Connection(Socket* sock, const char* addr, RManagerListener* listener);
~Connection(); ~Connection();
virtual void run(); virtual void run();
@ -171,8 +211,9 @@ public:
{ writeStr(s.safe(),s.length()); } { writeStr(s.safe(),s.length()); }
inline const String& address() const inline const String& address() const
{ return m_address; } { return m_address; }
inline const NamedList& cfg() const
{ return m_listener->cfg(); }
void checkTimer(u_int64_t time); void checkTimer(u_int64_t time);
static Connection *checkCreate(Socket* sock, const char* addr = 0);
private: private:
Level m_auth; Level m_auth;
bool m_debug; bool m_debug;
@ -188,6 +229,7 @@ private:
String m_buffer; String m_buffer;
String m_address; String m_address;
String m_lastcmd; String m_lastcmd;
RefPointer<RManagerListener> m_listener;
}; };
class RManager : public Plugin class RManager : public Plugin
@ -207,6 +249,7 @@ public:
virtual void dispatched(const Message& msg, bool handled); virtual void dispatched(const Message& msg, bool handled);
}; };
static void dbg_remote_func(const char *buf, int level) static void dbg_remote_func(const char *buf, int level)
{ {
s_mutex.lock(); s_mutex.lock();
@ -219,36 +262,127 @@ static void dbg_remote_func(const char *buf, int level)
s_mutex.unlock(); s_mutex.unlock();
} }
void RManagerThread::run()
RManagerListener::~RManagerListener()
{
DDebug("RManager",DebugInfo,"No longer listening '%s' on %s",
m_cfg.c_str(),m_address.c_str());
s_mutex.lock();
s_listeners.remove(this,false);
s_mutex.unlock();
}
void RManagerListener::init()
{
if (initSocket()) {
s_mutex.lock();
s_listeners.append(this);
s_mutex.unlock();
}
deref();
}
bool RManagerListener::initSocket()
{
// check configuration
int port = m_cfg.getIntValue("port",5038);
const char* host = c_safe(m_cfg.getValue("addr","127.0.0.1"));
if (!(port && *host))
return false;
m_socket.create(AF_INET, SOCK_STREAM);
if (!m_socket.valid()) {
Debug("RManager",DebugGoOn,"Unable to create the listening socket: %s",
strerror(m_socket.error()));
return false;
}
if (!m_socket.setBlocking(false)) {
Debug("RManager",DebugGoOn, "Failed to set listener to nonblocking mode: %s",
strerror(m_socket.error()));
return false;
}
SocketAddr sa(AF_INET);
sa.host(host);
sa.port(port);
m_address << sa.host() << ":" << sa.port();
m_socket.setReuse();
if (!m_socket.bind(sa)) {
Debug("RManager",DebugGoOn,"Failed to bind to %s : %s",
m_address.c_str(),strerror(m_socket.error()));
return false;
}
if (!m_socket.listen(2)) {
Debug("RManager",DebugGoOn,"Unable to listen on socket: %s",
strerror(m_socket.error()));
return false;
}
Debug("RManager",DebugInfo,"Starting listener '%s' on %s",
m_cfg.c_str(),m_address.c_str());
RManagerThread* t = new RManagerThread(this);
if (t->startup())
return true;
delete t;
return false;
}
void RManagerListener::run()
{ {
for (;;) for (;;)
{ {
Thread::idle(true); Thread::idle(true);
SocketAddr sa; SocketAddr sa;
Socket* as = s_sock.accept(sa); Socket* as = m_socket.accept(sa);
if (!as) { if (!as) {
if (!s_sock.canRetry()) if (!m_socket.canRetry())
Debug("RManager",DebugWarn, "Accept error: %s", strerror(s_sock.error())); Debug("RManager",DebugWarn, "Accept error: %s",strerror(m_socket.error()));
continue; continue;
} else { } else {
String addr(sa.host()); String addr(sa.host());
addr << ":" << sa.port(); addr << ":" << sa.port();
if (!Connection::checkCreate(as,addr)) if (!checkCreate(as,addr))
Debug("RManager",DebugWarn,"Connection rejected for %s",addr.c_str()); Debug("RManager",DebugWarn,"Connection rejected for %s",addr.c_str());
} }
} }
} }
Connection *Connection::checkCreate(Socket* sock, const char* addr) Connection* RManagerListener::checkCreate(Socket* sock, const char* addr)
{ {
if (!sock)
return 0;
if (!sock->valid()) { if (!sock->valid()) {
delete sock; delete sock;
return 0; return 0;
} }
const NamedString* secure = m_cfg.getParam("context");
if (TelEngine::null(secure))
secure = m_cfg.getParam("domain");
if (TelEngine::null(secure))
secure = 0;
if (secure) {
Message m("socket.ssl");
m.addParam("server",String::boolText(true));
m.addParam(secure->name(),*secure);
m.copyParam(m_cfg,"verify");
SockRef* s = new SockRef(&sock);
m.userData(s);
TelEngine::destruct(s);
if (!(Engine::dispatch(m) && sock)) {
Debug("RManager",DebugWarn, "Failed to switch '%s' to SSL for %s '%s'",
cfg().c_str(),secure->name().c_str(),secure->c_str());
delete sock;
return 0;
}
}
else if (!sock->setBlocking(false)) {
Debug("RManager",DebugGoOn, "Failed to set tcp socket to nonblocking mode: %s",
strerror(sock->error()));
delete sock;
return 0;
}
// should check IP address here // should check IP address here
Connection *conn = new Connection(sock,addr); Output("Remote%s connection from %s to %s",
(secure ? " secure" : ""),addr,m_address.c_str());
Connection* conn = new Connection(sock,addr,this);
if (conn->error()) { if (conn->error()) {
conn->destruct(); conn->destruct();
return 0; return 0;
@ -257,11 +391,12 @@ Connection *Connection::checkCreate(Socket* sock, const char* addr)
return conn; return conn;
} }
Connection::Connection(Socket* sock, const char* addr)
Connection::Connection(Socket* sock, const char* addr, RManagerListener* listener)
: Thread("RManager Connection"), : Thread("RManager Connection"),
m_auth(None), m_debug(false), m_output(s_output), m_colorize(false), m_machine(false), m_auth(None), m_debug(false), m_output(false), m_colorize(false), m_machine(false),
m_socket(sock), m_lastch(0), m_escmode(0), m_echoing(false), m_beeping(false), m_socket(sock), m_lastch(0), m_escmode(0), m_echoing(false), m_beeping(false),
m_timeout(0), m_address(addr) m_timeout(0), m_address(addr), m_listener(listener)
{ {
s_mutex.lock(); s_mutex.lock();
s_connList.append(this); s_connList.append(this);
@ -284,31 +419,28 @@ void Connection::run()
{ {
if (!m_socket) if (!m_socket)
return; return;
if (!m_socket->setBlocking(false)) {
Debug("RManager",DebugGoOn, "Failed to set tcp socket to nonblocking mode: %s",strerror(m_socket->error()));
return;
}
// For the sake of responsiveness try to turn off the tcp assembly timer // For the sake of responsiveness try to turn off the tcp assembly timer
int arg = 1; int arg = 1;
if (s_cfg.getBoolValue("general","interactive",false) && if (cfg().getBoolValue("interactive",false) &&
!m_socket->setOption(SOL_SOCKET, TCP_NODELAY, &arg, sizeof(arg))) !m_socket->setOption(SOL_SOCKET, TCP_NODELAY, &arg, sizeof(arg)))
Debug("RManager",DebugMild, "Failed to set tcp socket to TCP_NODELAY mode: %s", strerror(m_socket->error())); Debug("RManager",DebugMild, "Failed to set tcp socket to TCP_NODELAY mode: %s", strerror(m_socket->error()));
Output("Remote connection from %s",m_address.c_str()); if (cfg().getValue("userpass")) {
if (s_cfg.getValue("general","userpass")) { int tout = cfg().getIntValue("timeout",30000);
int tout = s_cfg.getIntValue("general","timeout",30000);
if (tout > 0) { if (tout > 0) {
if (tout < 5000) if (tout < 5000)
tout = 5000; tout = 5000;
m_timeout = Time::now() + 1000 * tout; m_timeout = Time::now() + 1000 * tout;
} }
} }
else else {
m_auth = s_cfg.getValue("general","password") ? User : Admin; m_auth = cfg().getValue("password") ? User : Admin;
String hdr = s_cfg.getValue("general","header","YATE ${version}-${release} (http://YATE.null.ro) ready."); m_output = cfg().getBoolValue("output",false);
}
String hdr = cfg().getValue("header","YATE ${version}-${release} (http://YATE.null.ro) ready.");
Engine::runParams().replaceParams(hdr); Engine::runParams().replaceParams(hdr);
if (s_cfg.getBoolValue("general","telnet",true)) { if (cfg().getBoolValue("telnet",true)) {
// WILL SUPPRESS GO AHEAD, WILL ECHO - and enough BS and blanks to hide them // WILL SUPPRESS GO AHEAD, WILL ECHO - and enough BS and blanks to hide them
writeStr("\377\373\003\377\373\001\r \b\b\b\b\b\b"); writeStr("\377\373\003\377\373\001\r \b\b\b\b\b\b");
} }
@ -494,7 +626,11 @@ bool Connection::processChar(unsigned char c)
return false; return false;
case 0x0F: // ^O case 0x0F: // ^O
m_escmode = 0; m_escmode = 0;
// cycle [no output] -> [output] -> [debug (only if auth)] if (m_auth < User) {
errorBeep();
return false;
}
// cycle [no output] -> [output] -> [debug (only if admin)]
if (m_debug) if (m_debug)
m_output = m_debug = false; m_output = m_debug = false;
else if (m_output) { else if (m_output) {
@ -813,14 +949,14 @@ bool Connection::processLine(const char *line)
writeStr(m_machine ? "%%=auth:success\r\n" : "You are already authenticated as admin!\r\n"); writeStr(m_machine ? "%%=auth:success\r\n" : "You are already authenticated as admin!\r\n");
return false; return false;
} }
const char* pass = s_cfg.getValue("general","password"); const char* pass = cfg().getValue("password");
if (pass && (str == pass)) { if (pass && (str == pass)) {
Output("Authenticated admin connection %s",m_address.c_str()); Output("Authenticated admin connection %s",m_address.c_str());
m_auth = Admin; m_auth = Admin;
m_timeout = 0; m_timeout = 0;
writeStr(m_machine ? "%%=auth:success\r\n" : "Authenticated successfully as admin!\r\n"); writeStr(m_machine ? "%%=auth:success\r\n" : "Authenticated successfully as admin!\r\n");
} }
else if ((pass = s_cfg.getValue("general","userpass")) && (str == pass)) { else if ((pass = cfg().getValue("userpass")) && (str == pass)) {
if (m_auth < User) { if (m_auth < User) {
Output("Authenticated user connection %s",m_address.c_str()); Output("Authenticated user connection %s",m_address.c_str());
m_auth = User; m_auth = User;
@ -843,8 +979,13 @@ bool Connection::processLine(const char *line)
Message m("engine.status"); Message m("engine.status");
if (str.startSkip("overview")) if (str.startSkip("overview"))
m.addParam("details",String::boolText(false)); m.addParam("details",String::boolText(false));
if (str.null() || (str == "rmanager")) if (str.null() || (str == "rmanager")) {
m.retValue() << "name=rmanager,type=misc;conn=" << s_connList.count() << "\r\n"; s_mutex.lock();
m.retValue() << "name=rmanager,type=misc"
<< ";listeners=" << s_listeners.count()
<< ",conn=" << s_connList.count() << "\r\n";
s_mutex.unlock();
}
if (!str.null()) { if (!str.null()) {
m.addParam("module",str); m.addParam("module",str);
str = ":" + str; str = ":" + str;
@ -1191,7 +1332,8 @@ RManager::RManager()
RManager::~RManager() RManager::~RManager()
{ {
Output("Unloading module RManager"); Output("Unloading module RManager");
s_sock.terminate(); s_connList.clear();
s_listeners.clear();
Debugger::setIntOut(0); Debugger::setIntOut(0);
} }
@ -1203,55 +1345,24 @@ bool RManager::isBusy() const
void RManager::initialize() void RManager::initialize()
{ {
Output("Initializing module RManager");
s_cfg = Engine::configFile("rmanager");
s_cfg.load();
s_output = s_cfg.getBoolValue("general","output",false);
if (s_sock.valid())
return;
// check configuration
int port = s_cfg.getIntValue("general","port",5038);
const char *host = c_safe(s_cfg.getValue("general","addr","127.0.0.1"));
if (!(port && *host))
return;
s_sock.create(AF_INET, SOCK_STREAM);
if (!s_sock.valid()) {
Debug("RManager",DebugGoOn,"Unable to create the listening socket: %s",strerror(s_sock.error()));
return;
}
if (!s_sock.setBlocking(false)) {
Debug("RManager",DebugGoOn, "Failed to set listener to nonblocking mode: %s",strerror(s_sock.error()));
return;
}
const int reuseFlag = 1;
s_sock.setOption(SOL_SOCKET,SO_REUSEADDR,&reuseFlag,sizeof(reuseFlag));
SocketAddr sa(AF_INET);
sa.host(host);
sa.port(port);
s_sock.setReuse();
if (!s_sock.bind(sa)) {
Debug("RManager",DebugGoOn,"Failed to bind to %s:%u : %s",sa.host().c_str(),sa.port(),strerror(s_sock.error()));
s_sock.terminate();
return;
}
if (!s_sock.listen(2)) {
Debug("RManager",DebugGoOn,"Unable to listen on socket: %s", strerror(s_sock.error()));
s_sock.terminate();
return;
}
// don't bother to install handlers until we are listening
if (m_first) { if (m_first) {
m_first = false; Output("Initializing module RManager");
Engine::self()->setHook(new RHook); Configuration cfg;
RManagerThread *mt = new RManagerThread; cfg = Engine::configFile("rmanager");
mt->startup(); // in server mode assume a default empty "general" section exists
if (!(cfg.load() || Engine::clientMode()))
(new RManagerListener(NamedList("general")))->init();
for (unsigned int i = 0; i < cfg.sections(); i++) {
NamedList* s = cfg.getSection(i);
if (s)
(new RManagerListener(*s))->init();
}
Lock mylock(s_mutex);
// don't bother to install handlers until we are listening
if (s_listeners.count()) {
m_first = false;
Engine::self()->setHook(new RHook);
}
} }
} }