//////////////////////////////////////////////////////////////////
//
// GkStatus.cxx
//
// This work is published under the GNU Public License (GPL)
// see file COPYING for details.
// We also explicitely grant the right to link this code
// with the OpenH323 library.
//
// History:
// 990924 initial version (Jan Willamowius)
// 991025 added command thread (Ashley Unitt)
// 030511 redesign based on new architecture (cwhuang)
//
//////////////////////////////////////////////////////////////////
#if defined(_WIN32) && (_MSC_VER <= 1200)
#pragma warning(disable:4786) // warning about too long debug symbol off
#pragma warning(disable:4284)
#endif
#include <ptlib.h>
#include <ptlib/sockets.h>
#include <ptclib/telnet.h>
#include <h225.h>
#include "gk_const.h"
#include "stl_supp.h"
#include "SoftPBX.h"
#include "Toolkit.h"
#include "RasSrv.h"
#include "Routing.h"
#include "rwlock.h"
#include "gk.h"
#include "GkStatus.h"
void ReloadHandler(); // avoid to include...
static const char *authsec="GkStatus::Auth";
// a very lightweight implementation of telnet socket
class TelnetSocket : public ServerSocket {
#ifndef LARGE_FDSET
PCLASSINFO(TelnetSocket, TCPSocket)
#endif
public:
typedef PTelnetSocket::Options Options;
typedef PTelnetSocket::Command Command;
TelnetSocket();
// override from class TCPSocket
#ifdef LARGE_FDSET
virtual bool Accept(YaTCPSocket &);
#else
virtual BOOL Accept(PSocket &);
#endif
virtual int ReadChar();
bool SendDo(BYTE);
bool SendDont(BYTE);
bool SendWill(BYTE);
bool SendWont(BYTE);
bool SendCommand(Command, BYTE);
bool NeedEcho() const { return m_needEcho; }
private:
enum State {
StateNormal,
StateCarriageReturn,
StateIAC,
StateDo,
StateDont,
StateWill,
StateWont,
StateSubNegotiations,
StateEndNegotiations
};
// new virtual function
virtual void OnDo(BYTE);
virtual void OnDont(BYTE);
virtual void OnWill(BYTE);
virtual void OnWont(BYTE);
bool m_needEcho;
State m_state;
};
/** Class that manages a single status interface client connection.
*/
class StatusClient : public TelnetSocket, public USocket {
#ifndef LARGE_FDSET
PCLASSINFO(StatusClient, TelnetSocket)
#endif
public:
StatusClient(
/// unique session ID (instance number) for this client
int instanceNo
);
bool ReadCommand(
/// command that has been read (if ReadCommand succeeded)
PString& cmd,
/// should the command be echoed (also NeedEcho() has to be true)
bool echo = true,
/// timeout (ms) for read operation
int readTimeout = 0
);
/** Send the message (string) to the status interface client,
if the output trace level is lesser or equal to the one set
for this client.
@return
true if the message has been sent (or ignored because of trace level).
*/
bool WriteString(
/// string to be sent through the socket
const PString& msg,
/// output trace level assigned to the message
int level = MIN_STATUS_TRACE_LEVEL
);
void FlushData();
/** @return
A string with connection information about this client.
*/
PString WhoAmI() const;
/** Check the client with all configured status authentication rules.
Ask for username/password, if required.
@return
true if the client has been granted access, false to reject the client.
*/
bool Authenticate();
/** Executes the given command as a new Job (in a separate Worker thread).
*/
void OnCommand(
/// command to be executed
const PString& cmd
);
/** @return
Unique instance number (session identifier) for this client.
*/
int GetInstanceNo() const { return m_instanceNo; }
/** @return
Output trace level for this status client. The trace level
decides what kind of information is allowed to be broadcasted
to this client.
*/
int GetTraceLevel() const { return m_traceLevel; }
/// Set the new output trace level for this status interface client
void SetTraceLevel(
/// new output trace level to be set
int newLevel
)
{
m_traceLevel = newLevel;
}
/** @return
true if one or more commands from this status interface client
are executing by Worker threads.
*/
bool IsBusy()
{
PWaitAndSignal lock(m_cmutex);
return m_numExecutingCommands > 0;
}
const PString& GetUser() const { return m_user; }
private:
// override from class ServerSocket
virtual void Dispatch();
/// Handle the 'Debug' command (its many variants).
void DoDebug(
/// tokenized debug command
const PStringArray& args
);
/** Check a new client connection against the specified authentication
rule.
@return
true if the client satisfied the rule (has been authenticated successfully
with this rule).
*/
bool CheckAuthRule(
/// authentication rule to be used
const PString& rule
);
/** Authenticate this status interface client through all authentication
rules and return the final result.
@return
true if the use has been authenticated.
*/
bool AuthenticateUser();
/** @return
The decrypted password associated with the specified login.
*/
PString GetPassword(
/// login the password is to be retrieved for
const PString& login
) const;
/** Parse and execute the given command. The function is called
in a separate Worker thread (as a Job).
*/
void ExecCommand(
/// the command to be executed
PString cmd
);
/// the most recent command
PString m_lastCmd;
/// command being currently entered (and not yet completed)
PString m_currentCmd;
/// GkStatus instance that created this client
GkStatus* m_gkStatus;
/// status interface user that is logged in
PString m_user;
/// for atomic access to the m_numExecutingCommands counter
PMutex m_cmutex;
/// number of commands being currently executed by Worker threads
int m_numExecutingCommands;
/// unique identifier for this client
int m_instanceNo;
/// output trace level for this client
int m_traceLevel;
};
TelnetSocket::TelnetSocket() : m_needEcho(false), m_state(StateNormal)
{
}
#ifdef LARGE_FDSET
bool TelnetSocket::Accept(YaTCPSocket & socket)
#else
BOOL TelnetSocket::Accept(PSocket & socket)
#endif
{
if (!TCPSocket::Accept(socket))
return false;
SendDo(PTelnetSocket::SuppressGoAhead);
SendWill(PTelnetSocket::StatusOption);
SendDont(PTelnetSocket::EchoOption);
#ifndef LARGE_FDSET
Address raddr, laddr;
WORD rport = 0, lport = 0;
GetPeerAddress(raddr, rport);
GetLocalAddress(laddr, lport);
SetName(AsString(raddr, rport) + "=>" + AsString(laddr, lport));
SetReadTimeout(0);
#else
// name already be set
#endif
return true;
}
int TelnetSocket::ReadChar()
{
bool hasData = false;
BYTE currentByte = 0;
while (!hasData) {
if (!TCPSocket::Read(¤tByte, 1)) {
if (GetErrorCode(PSocket::LastReadError) != PSocket::Timeout) {
PTRACE(3, "TELNET\t" << GetName() << " closed the connection ("
<< GetErrorCode(PSocket::LastReadError) << '/'
<< GetErrorNumber(PSocket::LastReadError) << ": "
<< GetErrorText(PSocket::LastReadError) << ')'
);
Close();
}
break;
}
switch (m_state)
{
case StateCarriageReturn:
m_state = StateNormal;
if (currentByte == '\0' || currentByte == '\n')
break; // Ignore \0 \n after CR
// else fall through for normal processing
case StateNormal:
if (currentByte == PTelnetSocket::IAC)
m_state = StateIAC;
else {
if (currentByte == '\r')
m_state = StateCarriageReturn;
hasData = true;
}
break;
case StateIAC:
switch (currentByte)
{
case PTelnetSocket::IAC:
hasData = true;
m_state = StateNormal;
break;
case PTelnetSocket::DO:
m_state = StateDo;
break;
case PTelnetSocket::DONT:
m_state = StateDont;
break;
case PTelnetSocket::WILL:
m_state = StateWill;
break;
case PTelnetSocket::WONT:
m_state = StateWont;
break;
default:
m_state = StateNormal;
break;
}
break;
case StateDo:
OnDo(currentByte);
m_state = StateNormal;
break;
case StateDont:
OnDont(currentByte);
m_state = StateNormal;
break;
case StateWill:
OnWill(currentByte);
m_state = StateNormal;
break;
case StateWont:
OnWont(currentByte);
m_state = StateNormal;
break;
default:
PTRACE(1, "TELNET\t" << GetName() << " telnet socket entered unrecognized state " << m_state);
m_state = StateNormal;
break;
}
}
return hasData ? currentByte : -1;
}
bool TelnetSocket::SendDo(BYTE code)
{
return SendCommand(PTelnetSocket::DO, code);
}
bool TelnetSocket::SendDont(BYTE code)
{
return SendCommand(PTelnetSocket::DONT, code);
}
bool TelnetSocket::SendWill(BYTE code)
{
return SendCommand(PTelnetSocket::WILL, code);
}
bool TelnetSocket::SendWont(BYTE code)
{
return SendCommand(PTelnetSocket::WONT, code);
}
bool TelnetSocket::SendCommand(Command cmd, BYTE opt)
{
int length = 2;
BYTE buffer[3];
buffer[0] = PTelnetSocket::IAC;
buffer[1] = (BYTE)cmd;
switch (cmd)
{
case PTelnetSocket::DO:
case PTelnetSocket::DONT:
case PTelnetSocket::WILL:
case PTelnetSocket::WONT:
buffer[2] = opt;
length = 3;
break;
default:
// do not support now
break;
}
return Write(buffer, length);
}
void TelnetSocket::OnDo(BYTE code)
{
PTRACE(6, "TELNET\t" << GetName() << " got DO " << int(code));
}
void TelnetSocket::OnDont(BYTE code)
{
PTRACE(6, "TELNET\t" << GetName() << " got DONT " << int(code));
}
void TelnetSocket::OnWill(BYTE code)
{
PTRACE(6, "TELNET\t" << GetName() << " got WILL " << int(code));
}
void TelnetSocket::OnWont(BYTE code)
{
PTRACE(6, "TELNET\t" << GetName() << " got WONT " << int(code));
if (code == PTelnetSocket::EchoOption) // Windows client?
m_needEcho = true;
}
namespace {
PString PrintGkVersion()
{
return PString("Version:\r\n") + Toolkit::GKVersion() +
"GkStatus: Version(2.0) Ext()\r\n"
"Toolkit: Version(1.0) Ext(" + Toolkit::Instance()->GetName() + ")\r\n"
+ SoftPBX::Uptime() + "\r\n;\r\n";
}
}
// class GkStatus
GkStatus::GkStatus() : Singleton<GkStatus>("GkStatus"), SocketsReader(500)
{
#ifdef LARGE_FDSET
PTRACE(1, "STATUS\tLarge fd_set(" << LARGE_FDSET << ") enabled");
#endif
SetName("GkStatus");
Execute();
}
void GkStatus::AuthenticateClient(
StatusClient* newClient
)
{
if (newClient->Authenticate()) {
newClient->SetTraceLevel(GkConfig()->GetInteger("StatusTraceLevel", MAX_STATUS_TRACE_LEVEL));
PTRACE(1, "STATUS\tNew client authenticated succesfully: " << newClient->WhoAmI()
<< ", login: " << newClient->GetUser()
);
// the welcome messages
newClient->WriteString(PrintGkVersion());
newClient->Flush();
AddSocket(newClient);
} else {
PTRACE(3, "STATUS\tNew client rejected: " << newClient->WhoAmI()
<< ", login: " << newClient->GetUser()
);
newClient->WriteString("\r\nAccess forbidden!\r\n");
newClient->Flush();
delete newClient;
}
}
/** Functor class used to send the message to the specified client.
*/
class ClientSignalStatus
{
public:
ClientSignalStatus(
/// message to be sent
const PString& msg,
/// output trace level assigned to the message
int level
) : m_message(msg), m_traceLevel(level) {}
/** Actual function call operator that sends the message.
*/
void operator()(
/// socket to send the message through
IPSocket* clientSocket
) const
{
StatusClient* client = static_cast<StatusClient*>(clientSocket);
if (m_traceLevel <= client->GetTraceLevel())
client->WriteString(m_message);
}
private:
/// message to be sent
const PString& m_message;
/// output trace level assigned to the message
int m_traceLevel;
};
void GkStatus::SignalStatus(
/// message string to be broadcasted
const PString& msg,
/// trace level at which the message should be broadcasted
int level
)
{
ReadLock lock(m_listmutex);
ForEachInContainer(m_sockets, ClientSignalStatus(msg, level));
}
bool GkStatus::DisconnectSession(
/// session ID (instance number) for the status client to be disconnected
int instanceNo,
/// status interface client that requested disconnect
StatusClient* requestingClient
)
{
ReadLock lock(m_listmutex);
for (iterator i = m_sockets.begin(); i != m_sockets.end(); ++i) {
StatusClient *client = static_cast<StatusClient *>(*i);
if (client->GetInstanceNo() == instanceNo) {
client->WriteString("Disconnected by session " + requestingClient->WhoAmI());
PTRACE(1, "STATUS\tClient " << client->WhoAmI() << " (ID: "
<< instanceNo << ") disconnected by " << requestingClient->WhoAmI()
);
return client->Close();
}
}
return false;
}
/** Functor class used to gather a list of active status interface clients.
*/
class WriteWhoAmI
{
public:
WriteWhoAmI(
/// status interface client to send the information to
StatusClient* requestingClient
) : m_requestingClient(requestingClient) {}
void operator()(
/// status interface client to send the information to
const IPSocket* clientSocket
) const
{
const StatusClient* client = static_cast<const StatusClient *>(clientSocket);
m_requestingClient->WriteString(" " + client->WhoAmI() + "\r\n");
}
private:
/// status interface client to send the information to
StatusClient* m_requestingClient;
};
void GkStatus::ShowUsers(
/// client that requested the list of all active clients
StatusClient* requestingClient
) const
{
ReadLock lock(m_listmutex);
ForEachInContainer(m_sockets, WriteWhoAmI(requestingClient));
}
void GkStatus::PrintHelp(
/// client that requested the help message
StatusClient* requestingClient
) const
{
requestingClient->WriteString("Commands:\r\n");
std::map<PString, int>::const_iterator i = m_commands.begin();
while (i != m_commands.end())
requestingClient->WriteString(i->first + "\r\n"), ++i;
requestingClient->WriteString(";\r\n");
}
int GkStatus::ParseCommand(
/// message to be parsed
const PString& msg,
/// message split into tokens upon successful return
PStringArray& args
)
{
// the 'Tokenise' doesn't seem to work correctly for leading spaces
args = msg.Trim().Tokenise(" \t", FALSE);
if (args.GetSize() == 0)
return -1;
// try explicit match
const PString command = args[0].ToLower();
if (m_commands.find(command) != m_commands.end())
return m_commands[command];
// try to find the closest match (expand command prefix)
std::map<PString, int>::iterator iter = m_commands.begin();
std::map<PString, int>::iterator expandedCmd = m_commands.end();
while (iter != m_commands.end()) {
if (command == iter->first.Left(command.GetLength())) {
// if the key matches more than one command, do not expand
if( expandedCmd != m_commands.end() )
return -1;
else
expandedCmd = iter;
}
iter++;
}
if( expandedCmd != m_commands.end() ) {
PTRACE(6, "STATUS\tExpanded prefix '" << command << "' into command '"
<< expandedCmd->first << "'"
);
return expandedCmd->second;
}
return -1;
}
void GkStatus::OnStart()
{
m_commands["printallregistrations"] = e_PrintAllRegistrations;
m_commands["r"] = e_PrintAllRegistrations;
m_commands["?"] = e_PrintAllRegistrations;
m_commands["printallregistrationsverbose"] = e_PrintAllRegistrationsVerbose;
m_commands["rv"] = e_PrintAllRegistrationsVerbose;
m_commands["??"] = e_PrintAllRegistrationsVerbose;
m_commands["printallcached"] = e_PrintAllCached;
m_commands["rc"] = e_PrintAllCached;
m_commands["printcurrentcalls"] = e_PrintCurrentCalls;
m_commands["c"] = e_PrintCurrentCalls;
m_commands["!"] = e_PrintCurrentCalls;
m_commands["printcurrentcallsverbose"] = e_PrintCurrentCallsVerbose;
m_commands["cv"] = e_PrintCurrentCallsVerbose;
m_commands["!!"] = e_PrintCurrentCallsVerbose;
m_commands["find"] = e_Find;
m_commands["f"] = e_Find;
m_commands["findverbose"] = e_FindVerbose;
m_commands["fv"] = e_FindVerbose;
m_commands["disconnectip"] = e_DisconnectIp;
m_commands["disconnectcall"] = e_DisconnectCall;
m_commands["disconnectalias"] = e_DisconnectAlias;
m_commands["disconnectendpoint"] = e_DisconnectEndpoint;
m_commands["disconnectsession"] = e_DisconnectSession;
m_commands["clearcalls"] = e_ClearCalls;
m_commands["unregisterallendpoints"] = e_UnregisterAllEndpoints;
m_commands["unregisterip"] = e_UnregisterIp;
m_commands["unregisteralias"] = e_UnregisterAlias;
m_commands["transfercall"] = e_TransferCall;
m_commands["makecall"] = e_MakeCall;
m_commands["yell"] = e_Yell;
m_commands["who"] = e_Who;
m_commands["gk"] = e_GK;
m_commands["help"] = e_Help;
m_commands["h"] = e_Help;
m_commands["version"] = e_Version;
m_commands["v"] = e_Version;
m_commands["debug"] = e_Debug;
m_commands["statistics"] = e_Statistics;
m_commands["s"] = e_Statistics;
m_commands["reload"] = e_Reload;
m_commands["routetoalias"] = e_RouteToAlias;
m_commands["rta"] = e_RouteToAlias;
m_commands["routetogateway"] = e_RouteToGateway;
m_commands["rtg"] = e_RouteToGateway;
m_commands["routereject"] = e_RouteReject;
m_commands["shutdown"] = e_Shutdown;
m_commands["exit"] = e_Exit;
m_commands["quit"] = e_Exit;
m_commands["q"] = e_Exit;
m_commands["trace"] = e_Trace;
#if PTRACING
m_commands["rotatelog"] = e_RotateLog;
m_commands["setlog"] = e_SetLogFilename;
#endif
}
void GkStatus::ReadSocket(
IPSocket* clientSocket
)
{
PString cmd;
StatusClient* client = static_cast<StatusClient*>(clientSocket);
if (client->ReadCommand(cmd) && !cmd)
client->OnCommand(cmd);
}
void GkStatus::CleanUp()
{
if (m_rmsize > 0) {
PWaitAndSignal lock(m_rmutex);
iterator iter = m_removed.begin();
while (iter != m_removed.end()) {
iterator i = iter++;
StatusClient *client = static_cast<StatusClient *>(*i);
if (!client->IsBusy()) {
m_removed.erase(i);
--m_rmsize;
delete client;
}
}
}
RemoveClosed(false);
}
StatusClient::StatusClient(
/// unique session ID (instance number) for this client
int instanceNo
)
:
USocket(this, "Status"),
m_gkStatus(GkStatus::Instance()),
m_numExecutingCommands(0),
m_instanceNo(instanceNo),
m_traceLevel(MAX_STATUS_TRACE_LEVEL)
{
SetWriteTimeout(10);
}
bool StatusClient::ReadCommand(
/// command that has been read (if ReadCommand succeeded)
PString& cmd,
/// should the command be echoed (also NeedEcho() has to be true)
bool echo,
/// timeout (ms) for read operation, 0 means infinite
int readTimeout
)
{
while (IsReadable(readTimeout)) {
char byte;
int read = ReadChar();
switch (read)
{
case -1:
break; // read IAC or socket closed
case '\r':
case '\n':
cmd = m_currentCmd;
m_lastCmd = cmd;
m_currentCmd = PString();
if (echo && NeedEcho())
TransmitData("\r\n");
return true;
case '\b':
if (m_currentCmd.GetLength()) {
m_currentCmd = m_currentCmd.Left(m_currentCmd.GetLength() - 1);
byte = char(read);
if (echo && NeedEcho()) {
Write(&byte, 1);
Write(&" ", 1);
Write(&byte, 1);
}
}
break;
default:
byte = char(read);
m_currentCmd += byte;
cmd = m_currentCmd.Right(3);
if (cmd == "\x1b\x5b\x41") { // Up Arrow
if (echo && NeedEcho()) {
for(PINDEX i = 0; i < m_currentCmd.GetLength() - 3; i ++)
Write("\b", 1);
WriteString(m_lastCmd);
}
m_currentCmd = m_lastCmd;
} else if (cmd.Find('\x1b') == P_MAX_INDEX)
if (echo && NeedEcho())
Write(&byte, 1);
break;
}
}
return false;
}
bool StatusClient::WriteString(
/// string to be sent through the socket
const PString& msg,
/// output trace level assigned to the message
int level
)
{
if (level > m_traceLevel)
return true;
if (CanFlush())
Flush();
return WriteData(msg, msg.GetLength());
}
/*
void StatusClient::FlushData()
{
// flush data in another thread
int i;
MarkSocketBlocked lock(this);
SetWriteTimeout(GkConfig()->GetInteger("StatusWriteTimeout", 5000));
for (i = 0; i < 3; ++i) // try three times
if (CanFlush() && Flush())
break;
if (i == 3) {
PTRACE(1, "Status\tClose dead client " << m_instanceNo << ' ' << GetName());
Close();
}
SetWriteTimeout(10);
}
*/
PString StatusClient::WhoAmI() const
{
return PString(m_instanceNo) + '\t' + GetName() + '\t' + m_user;
}
bool StatusClient::Authenticate()
{
PINDEX rule_start = 0;
bool result, logical_or;
const PString rules = GkConfig()->GetString(authsec, "rule", "forbid");
while (true) {
const PINDEX rule_end = rules.FindOneOf("&|", rule_start);
if (rule_end == P_MAX_INDEX) {
result = CheckAuthRule(rules(rule_start, P_MAX_INDEX).Trim());
break;
} else
result = CheckAuthRule(rules(rule_start, rule_end - 1).Trim());
logical_or = (rules[rule_end] == '|') ;
if ((logical_or && result) || !(logical_or || result))
break;
rule_start = rule_end + 1;
}
PTRACE(4, "STATUS\tNew connection from " << GetName()
<< (result?" accepted":" rejected")
);
return result;
}
void StatusClient::OnCommand(
/// command to be executed
const PString& cmd
)
{
{
PWaitAndSignal lock(m_cmutex);
++m_numExecutingCommands;
}
// problem - if the ExecCommand does not get executed for some reason,
// m_numExecutingCommands will not decrement
CreateJob(this, &StatusClient::ExecCommand, cmd, "StatusCmd " + cmd);
}
void StatusClient::Dispatch()
{
ReadLock lockConfig(ConfigReloadMutex);
m_gkStatus->AuthenticateClient(this);
}
void StatusClient::DoDebug(
/// tokenized debug command
const PStringArray& args
)
{
if (args.GetSize() <= 1) {
WriteString("Debug options:\r\n"
" trc [+|-|n] Show/modify trace level for the log\r\n"
" cfg Read and print config sections\r\n"
" cfg SEC PAR Read and print a config PARameter in a SECtion\r\n"
" set SEC PAR VAL Write a config VALue PARameter in a SECtion\r\n"
" remove SEC PAR Remove a config VALue PARameter in a SECtion\r\n"
" remove SEC Remove a SECtion\r\n"
" printrm VERBOSE Print all removed endpoint records\r\n"
);
} else {
if (args[1] *= "trc") {
if(args.GetSize() >= 3) {
if((args[2] == "-") && (PTrace::GetLevel() > 0))
PTrace::SetLevel(PTrace::GetLevel()-1);
else if(args[2] == "+")
PTrace::SetLevel(PTrace::GetLevel()+1);
else PTrace::SetLevel(args[2].AsInteger());
}
WriteString(PString(PString::Printf, "Trace Level is now %d\r\n", PTrace::GetLevel()));
} else if (args[1] *= "cfg") {
if (args.GetSize()>=4)
WriteString(GkConfig()->GetString(args[2],args[3],"") + "\r\n;\r\n");
else if (args.GetSize()>=3) {
const PStringList cfgs(GkConfig()->GetKeys(args[2]));
PString result = "Section [" + args[2] + "]\r\n";
for (PINDEX i=0; i < cfgs.GetSize(); ++i)
result += cfgs[i] + "="
+ GkConfig()->GetString(args[2], cfgs[i], "") + "\r\n";
WriteString(result + ";\r\n");
} else {
const PStringList secs(GkConfig()->GetSections());
PString result = "Config sections\r\n";
for (PINDEX i = 0; i < secs.GetSize(); i++)
result += "[" + secs[i] + "]\r\n";
WriteString(result);
}
} else if ((args[1] *= "set") && (args.GetSize()>=5)) {
Toolkit::Instance()->SetConfig(1, args[2], args[3], args[4]);
WriteString(GkConfig()->GetString(args[2],args[3],"") + "\r\n");
} else if (args[1] *= "remove") {
if (args.GetSize()>=4) {
Toolkit::Instance()->SetConfig(2, args[2], args[3]);
WriteString("Remove " + args[3] + " in section " + args[2] + "\r\n");
} else if (args.GetSize()>=3) {
Toolkit::Instance()->SetConfig(3, args[2]);
WriteString("Remove section " + args[2] + "\r\n");
}
} else if ((args[1] *= "printrm")) {
SoftPBX::PrintRemoved(this, (args.GetSize() >= 3));
} else {
WriteString("Unknown debug command!\r\n");
}
}
}
bool StatusClient::CheckAuthRule(
/// authentication rule to be used
const PString& rule
)
{
PIPSocket::Address peerAddress;
GetPeerAddress(peerAddress);
const PString peer = peerAddress.AsString();
bool result = false;
if (rule *= "forbid") { // "*=": case insensitive
result = false;
} else if (rule *= "allow") {
result = true;
} else if (rule *= "explicit") {
PString val;
if (!peer)
val = GkConfig()->GetString(authsec, peer, "");
if (val.IsEmpty()) { // use "default" entry
result = Toolkit::AsBool(GkConfig()->GetString(authsec, "default", "0"));
PTRACE(5, "STATUS\tClient IP " << peer << " not found for explicit rule, using default ("
<< result << ')'
);
} else
result = Toolkit::AsBool(val);
} else if (rule *= "regex") {
PString val;
if (!peer)
val = GkConfig()->GetString(authsec, peer, "");
if (val.IsEmpty()) {
result = Toolkit::MatchRegex(peer, GkConfig()->GetString(authsec, "regex", ""));
PTRACE(5, "STATUS\tClient IP " << peer << " not found for regex rule, using default ("
<< GkConfig()->GetString(authsec, "regex", "") << ')'
);
} else
result = Toolkit::AsBool(val);
} else if (rule *= "password") {
result = AuthenticateUser();
} else {
PTRACE(1, "STATUS\tERROR: Unrecognized [GkStatus::Auth] rule (" << rule << ')');
}
PTRACE(4, "STATUS\tAuthentication rule '" << rule
<< (result?"' accepted":"' rejected") << " the client " << Name()
);
return result;
}
bool StatusClient::AuthenticateUser()
{
const time_t now = time(NULL);
const int delay = GkConfig()->GetInteger(authsec, "DelayReject", 0);
const int loginTimeout = GkConfig()->GetInteger(authsec, "LoginTimeout", 120);
for (int retries = 0; retries < 3; ++retries) {
PString login, password;
WriteString("\r\n" + Toolkit::GKName() + " login: ");
if (!ReadCommand(login, true, loginTimeout * 1000))
break;
login = login.Trim();
SendWill(PTelnetSocket::EchoOption);
WriteString("Password: ");
if (!ReadCommand(password, false, loginTimeout * 1000))
break;
password = password.Trim();
WriteString("\r\n", 1);
SendWont(PTelnetSocket::EchoOption);
const PString storedPassword = GetPassword(login);
if (storedPassword.IsEmpty())
PTRACE(5, "STATUS\tCould not find password in the config for user " << login);
else if (!password && password == storedPassword) {
m_user = login;
return true;
} else
PTRACE(5, "STATUS\tPassword mismatch for user " << login);
PProcess::Sleep(delay * 1000);
if ((time(NULL) - now) > loginTimeout)
break;
}
return false;
}
PString StatusClient::GetPassword(
/// login the password is to be retrieved for
const PString& login
) const
{
return !login
? Toolkit::Instance()->ReadPassword(authsec, login, true) : PString();
}
void StatusClient::ExecCommand(
/// the command to be executed
PString cmd
)
{
ReadLock lockConfig(ConfigReloadMutex);
PTRACE(5, "STATUS\tGot command " << cmd << " from client " << Name());
PStringArray args;
switch (m_gkStatus->ParseCommand(cmd, args))
{
case GkStatus::e_DisconnectIp:
// disconnect call on this IP number
if (args.GetSize() == 2)
SoftPBX::DisconnectIp(args[1]);
else
WriteString("Syntax Error: DisconnectIp IP_ADDRESS\r\n");
break;
case GkStatus::e_DisconnectAlias:
// disconnect call on this alias
if (args.GetSize() == 2)
SoftPBX::DisconnectAlias(args[1]);
else
WriteString("Syntax Error: DisconnectAlias ALIAS\r\n");
break;
case GkStatus::e_DisconnectCall:
// disconnect call with this call number
if (args.GetSize() >= 2)
for (PINDEX p=1; p < args.GetSize(); ++p)
SoftPBX::DisconnectCall(args[p].AsInteger());
else
WriteString("Syntax Error: DisconnectCall CALL_NUMBER [CALL_NUMBER...]\r\n");
break;
case GkStatus::e_DisconnectEndpoint:
// disconnect call on this alias
if (args.GetSize() == 2)
SoftPBX::DisconnectEndpoint(args[1]);
else
WriteString("Syntax Error: DisconnectEndpoint ENDPOINT_IDENTIFIER\r\n");
break;
case GkStatus::e_DisconnectSession:
// disconnect a user from status port
if (args.GetSize() == 2)
if (m_gkStatus->DisconnectSession(args[1].AsInteger(), this))
WriteString("Session " + args[1] + " disconnected\r\n");
else
WriteString("Session " + args[1] + " not found\r\n");
else
WriteString("Syntax Error: DisconnectSession SESSION_ID\r\n");
break;
case GkStatus::e_ClearCalls:
SoftPBX::DisconnectAll();
break;
case GkStatus::e_PrintAllRegistrations:
// print list of all registered endpoints
SoftPBX::PrintAllRegistrations(this);
break;
case GkStatus::e_PrintAllRegistrationsVerbose:
// print list of all registered endpoints verbose
SoftPBX::PrintAllRegistrations(this, TRUE);
break;
case GkStatus::e_PrintAllCached:
// print list of all cached outer-zone endpoints
SoftPBX::PrintAllCached(this, (args.GetSize() > 1));
break;
case GkStatus::e_PrintCurrentCalls:
// print list of currently ongoing calls
SoftPBX::PrintCurrentCalls(this);
break;
case GkStatus::e_PrintCurrentCallsVerbose:
// print list of currently ongoing calls
SoftPBX::PrintCurrentCalls(this, TRUE);
break;
case GkStatus::e_Statistics:
SoftPBX::PrintStatistics(this, TRUE);
break;
case GkStatus::e_Find:
if (args.GetSize() == 2)
SoftPBX::PrintEndpoint(args[1], this, FALSE);
else
WriteString("Syntax Error: Find ALIAS\r\n");
break;
case GkStatus::e_FindVerbose:
if (args.GetSize() == 2)
SoftPBX::PrintEndpoint(args[1], this, TRUE);
else
WriteString("Syntax Error: FindVerbose ALIAS\r\n");
break;
case GkStatus::e_Yell:
m_gkStatus->SignalStatus(PString(" "+ WhoAmI() + ": " + cmd + "\r\n"));
break;
case GkStatus::e_Who:
m_gkStatus->ShowUsers(this);
WriteString(";\r\n");
break;
case GkStatus::e_GK:
WriteString(RasServer::Instance()->GetParent() + "\r\n;\r\n");
break;
case GkStatus::e_Help:
m_gkStatus->PrintHelp(this);
break;
case GkStatus::e_Debug:
DoDebug(args);
break;
case GkStatus::e_Version:
WriteString(PrintGkVersion());
break;
case GkStatus::e_Exit:
Close();
break;
case GkStatus::e_UnregisterAllEndpoints:
SoftPBX::UnregisterAllEndpoints();
WriteString("Done\n;\n");
break;
case GkStatus::e_UnregisterAlias:
// unregister this alias
if (args.GetSize() == 2)
SoftPBX::UnregisterAlias(args[1]);
else
WriteString("Syntax Error: UnregisterAlias ALIAS\r\n");
break;
case GkStatus::e_UnregisterIp:
// unregister this IP
if (args.GetSize() == 2)
SoftPBX::UnregisterIp(args[1]);
else
WriteString("Syntax Error: UnregisterIp IP_ADDRESS\r\n");
break;
case GkStatus::e_TransferCall:
if (args.GetSize() == 3)
SoftPBX::TransferCall(args[1], args[2]);
else
WriteString("Syntax Error: TransferCall SOURCE DESTINATION\r\n");
break;
case GkStatus::e_MakeCall:
if (args.GetSize() == 3)
SoftPBX::MakeCall(args[1], args[2]);
else
WriteString("Syntax Error: MakeCall SOURCE DESTINATION\r\n");
break;
case GkStatus::e_Reload:
{
ConfigReloadMutex.EndRead();
ReloadHandler();
ConfigReloadMutex.StartRead();
}
PTRACE(1, "STATUS\tConfig reloaded.");
m_gkStatus->SignalStatus("Config reloaded.\r\n");
break;
case GkStatus::e_Shutdown:
if (!Toolkit::AsBool(GkConfig()->GetString(authsec, "Shutdown", "1"))) {
WriteString("Shutdown not allowed!\r\n");
break;
}
SoftPBX::PrintStatistics(this, true);
RasServer::Instance()->Stop();
break;
case GkStatus::e_RouteToAlias:
if (args.GetSize() == 4) {
RasServer::Instance()->GetVirtualQueue()->RouteToAlias(args[1], "", args[2], args[3].AsUnsigned());
} else
WriteString("Syntax Error: RouteToAlias TARGET_ALIAS CALLING_ENDPOINT_ID CRV\r\n");
break;
case GkStatus::e_RouteToGateway:
if (args.GetSize() == 5) {
RasServer::Instance()->GetVirtualQueue()->RouteToAlias(args[1], args[2], args[3], args[4].AsUnsigned());
} else
WriteString("Syntax Error: RouteToGateway TARGET_ALIAS TARGET_IP CALLING_ENDPOINT_ID CRV\r\n");
break;
case GkStatus::e_RouteReject:
if (args.GetSize() == 3) {
RasServer::Instance()->GetVirtualQueue()->RouteReject(args[1], args[2].AsUnsigned());
} else
WriteString("Syntax Error: RouteReject CALLING_ENDPOINT_ID CRV\r\n");
break;
case GkStatus::e_Trace:
if (args.GetSize() == 2) {
if (args[1] *= "min")
m_traceLevel = MIN_STATUS_TRACE_LEVEL;
else if (args[1] *= "max")
m_traceLevel = MAX_STATUS_TRACE_LEVEL;
else {
unsigned level = args[1].AsUnsigned();
if (level >= MIN_STATUS_TRACE_LEVEL
&& level <= MAX_STATUS_TRACE_LEVEL)
m_traceLevel = level;
else {
WriteString("Syntax Error: trace 0|1|2|\"min\"|\"max\"\r\n");
break;
}
}
}
WriteString("Output trace level is " + PString(m_traceLevel) + "\r\n");
break;
#if PTRACING
case GkStatus::e_RotateLog:
if (Gatekeeper::RotateLogFile())
WriteString("Log file rotation succeeded\r\n");
else
WriteString("Log file rotation failed\r\n");
break;
case GkStatus::e_SetLogFilename:
if (args.GetSize() == 2) {
if (Gatekeeper::SetLogFilename(args[1])) {
WriteString("Logging to the file '" + args[1] + "'\r\n");
} else
WriteString("Failed to open the log file'" + args[1] + "'\r\n");
} else
WriteString("Syntax Error: setlog <logfilepath>\r\n");
break;
#endif
default:
// commmand not recognized
WriteString("Error: Unknown command " + cmd + "\r\n");
PTRACE(5, "STATUS\tUnknown command '" << cmd << "' from client " << Name());
break;
}
PWaitAndSignal lock(m_cmutex);
--m_numExecutingCommands;
}
// class StatusListener
StatusListener::StatusListener(
const Address& addr,
WORD port
)
{
const unsigned queueSize = GkConfig()->GetInteger("ListenQueueLength", GK_DEF_LISTEN_QUEUE_LENGTH);
if (!Listen(addr, queueSize, port, PSocket::CanReuseAddress)) {
PTRACE(1, "STATUS\tCould not open listening socket at " << addr << ':' << port
<< " - error " << GetErrorCode(PSocket::LastGeneralError) << '/'
<< GetErrorNumber(PSocket::LastGeneralError) << ": "
<< GetErrorText(PSocket::LastGeneralError)
);
Close();
}
SetName(AsString(addr, GetPort()));
}
ServerSocket *StatusListener::CreateAcceptor() const
{
static int StaticInstanceNo = 0;
return new StatusClient(++StaticInstanceNo);
}
syntax highlighted by Code2HTML, v. 0.9.1