yate/modules/msgsniff.cpp

389 lines
10 KiB
C++

/*
* msgsniff.cpp
* This file is part of the YATE Project http://YATE.null.ro
*
* A sample message sniffer that inserts a wildcard message handler
*
* 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 <yatengine.h>
#include <stdio.h>
#include <math.h>
using namespace TelEngine;
namespace { // anonymous
static const char* s_debugs[] =
{
"on",
"off",
"enable",
"disable",
"true",
"false",
"yes",
"no",
"filter",
"timer",
"age",
"params",
0
};
class SniffParamMatch : public String
{
public:
inline SniffParamMatch(const char* name, const String& rex)
: String(name), m_match(rex.length() < 2 || '^' != rex[rex.length() - 1])
{
m_filter = m_match ? rex : rex.substr(0,rex.length() - 1);
m_filter.compile();
}
inline SniffParamMatch(const SniffParamMatch& other)
: String(other), m_filter(other.m_filter), m_match(other.m_match)
{
m_filter.compile();
}
Regexp m_filter; // Regexp to match parameter name
bool m_match; // Match value, false to revert match (matches if value don't matches)
};
class SniffMatch : public RefObject
{
public:
inline SniffMatch()
: m_allParams(true)
{}
inline SniffMatch(const SniffMatch& other)
: m_params(other.m_params.length()), m_allParams(other.m_allParams)
{
setFilter(other.m_filter);
for (unsigned int i = 0; i < other.m_params.length(); i++) {
const SniffParamMatch* m = static_cast<const SniffParamMatch*>(other.m_params[i]);
if (m)
m_params.set(new SniffParamMatch(*m),i);
}
}
inline bool valid() const
{ return m_filter || m_params.length(); };
inline bool matches(const Message& msg) const {
if (m_filter && !m_filter.matches(msg))
return false;
if (m_params.length()) {
bool matched = false;
for (unsigned int i = 0; i < m_params.length(); i++) {
SniffParamMatch* m = static_cast<SniffParamMatch*>(m_params[i]);
if (!m)
continue;
const NamedString* ns = msg.getParam(*m);
bool ok = false;
// Match if:
// - filter set: regexp matches
// - filter not set: match if parameter is missing or empty
if (m->m_filter)
ok = (m->m_match == m->m_filter.matches(TelEngine::c_safe(ns)));
else
ok = TelEngine::null(ns);
if (ok) {
matched = true;
if (m_allParams)
continue;
break;
}
if (m_allParams)
return false;
}
if (!matched)
return false;
}
return true;
}
inline void setFilter(const String& value) {
m_filter = value;
m_filter.compile();
}
inline void setParams(const String& line) {
String s = line;
m_allParams = !s.startSkip("any");
ObjList list;
static const Regexp s_matchParam("^\\(.* \\)\\?\\([^= ]\\+\\)=\\([^=]*\\)$");
while (s.matches(s_matchParam)) {
list.insert(new SniffParamMatch(s.matchString(2),s.matchString(3).trimSpaces()));
s = s.matchString(1);
}
m_params.assign(list);
}
Regexp m_filter; // Filter for message name
ObjVector m_params; // Filter(s) for message parameters
bool m_allParams; // Match all parameters or at least one
};
class MsgSniff : public Plugin
{
public:
MsgSniff();
virtual void initialize();
inline void setFilter(SniffMatch* flt = 0) {
Lock lck(m_mutex);
if (flt == m_filter)
return;
m_filter = flt && flt->valid() ? flt : 0;
TelEngine::destruct(flt);
}
inline bool getFilter(RefPointer<SniffMatch>& flt) {
Lock lck(m_mutex);
flt = m_filter;
return (0 != flt);
}
inline bool filterMatches(const Message& msg) {
RefPointer<SniffMatch> flt;
return !getFilter(flt) || flt->matches(msg);
}
private:
bool m_first;
RefPointer<SniffMatch> m_filter;
Mutex m_mutex;
};
class SniffHandler : public MessageHandler
{
public:
SniffHandler(const char* trackName = 0) : MessageHandler(0,0,trackName) { }
virtual bool received(Message &msg);
};
class HookHandler : public MessagePostHook
{
public:
virtual void dispatched(const Message& msg, bool handled);
};
static bool s_active = true;
static bool s_timer = false;
static u_int64_t s_minAge = 0;
INIT_PLUGIN(MsgSniff);
static void dumpParams(const Message &msg, String& par)
{
unsigned n = msg.length();
for (unsigned i = 0; i < n; i++) {
const NamedString *s = msg.getParam(i);
if (s) {
par << "\r\n param['" << s->name() << "'] = ";
if (s->name() == YSTRING("password"))
par << "(hidden)";
else
par << "'" << *s << "'";
if (const NamedPointer* p = YOBJECT(NamedPointer,s)) {
char buf[64];
GenObject* obj = p->userData();
::sprintf(buf," [%p]",obj);
par << buf;
if (obj)
par << " '" << obj->toString() << "'";
}
}
}
}
bool SniffHandler::received(Message &msg)
{
if (!s_timer && (msg == YSTRING("engine.timer")))
return false;
if (msg == YSTRING("engine.command")) {
static const String name("sniffer");
String line(msg.getValue(YSTRING("line")));
if (line.startSkip(name)) {
line >> s_active;
line.trimSpaces();
if (line.startSkip("timer"))
(line >> s_timer).trimSpaces();
RefPointer<SniffMatch> crtFlt;
__plugin.getFilter(crtFlt);
SniffMatch* newFlt = 0;
if (line.startSkip("filter")) {
if (crtFlt)
newFlt = new SniffMatch(*crtFlt);
else
newFlt = new SniffMatch;
newFlt->setFilter(line);
line = "";
}
if (line.startSkip("params")) {
if (!newFlt) {
if (crtFlt)
newFlt = new SniffMatch(*crtFlt);
else
newFlt = new SniffMatch;
}
newFlt->setParams(line);
line = "";
}
if (newFlt) {
__plugin.setFilter(newFlt);
__plugin.getFilter(crtFlt);
}
if (line.startSkip("age")) {
s_minAge = (u_int64_t)(1000000.0 * fabs(line.toDouble()));
line = "";
}
msg.retValue() << "Message sniffer: " << (s_active ? "on" : "off");
if (s_active)
msg.retValue() << ", timer: " << (s_timer ? "on" : "off");
if (s_active && crtFlt) {
if (crtFlt->m_filter)
msg.retValue() << ", filter: " << crtFlt->m_filter;
if (crtFlt->m_params.length()) {
msg.retValue() << ", params:";
if (!crtFlt->m_allParams)
msg.retValue() << " any";
for (unsigned int i = 0; i < crtFlt->m_params.length(); i++) {
SniffParamMatch* m = static_cast<SniffParamMatch*>(crtFlt->m_params[i]);
msg.retValue() << " " << *m << "=" << m->m_filter << (m->m_match ? "" : "^");
}
}
}
if (s_active && s_minAge)
msg.retValue() << ", age: " << String().printf("%u.%06u",
(unsigned int)(s_minAge / 1000000),(unsigned int)(s_minAge % 1000000));
msg.retValue() << "\r\n";
return true;
}
line = msg.getParam(YSTRING("partline"));
if (line.null()) {
if (name.startsWith(msg.getValue(YSTRING("partword"))))
msg.retValue().append(name,"\t");
}
else if (name == line) {
line = msg.getValue(YSTRING("partword"));
for (const char** b = s_debugs; *b; b++)
if (line.null() || String(*b).startsWith(line))
msg.retValue().append(*b,"\t");
}
}
if (!s_active)
return false;
if (!__plugin.filterMatches(msg))
return false;
u_int64_t mt = msg.msgTime().usec();
u_int64_t dt = Time::now() - mt;
if (s_minAge && (dt < s_minAge))
return false;
String extra;
if (msg.msgTimeEnqueue() && msg.msgTimeDispatch() > msg.msgTimeEnqueue()) {
uint64_t dur = msg.msgTimeDispatch() - msg.msgTimeEnqueue();
extra.printf(" queued=%u.%06u",(unsigned int)(dur / 1000000),
(unsigned int)(dur % 1000000));
}
String par;
dumpParams(msg,par);
Output("Sniffed '%s' time=%u.%06u age=%u.%06u%s%s\r\n thread=%p '%s'\r\n data=%p\r\n retval='%s'%s",
msg.c_str(),
(unsigned int)(mt / 1000000),
(unsigned int)(mt % 1000000),
(unsigned int)(dt / 1000000),
(unsigned int)(dt % 1000000),
extra.safe(),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
msg.retValue().c_str(),
par.safe());
return false;
};
void HookHandler::dispatched(const Message& msg, bool handled)
{
if (!s_active || (!s_timer && (msg == YSTRING("engine.timer"))))
return;
if (!__plugin.filterMatches(msg))
return;
u_int64_t dt = Time::now() - msg.msgTime().usec();
if (s_minAge && (dt < s_minAge))
return;
String par;
dumpParams(msg,par);
const char* rval = msg.retValue().c_str();
const char* rsep = "'";
if (handled && rval && (rval[0] != '-' || rval[1]) && (msg == YSTRING("user.auth"))) {
rval = "(hidden)";
rsep = "";
}
Output("Returned %s '%s' delay=%u.%06u%s\r\n thread=%p '%s'\r\n data=%p\r\n retval=%s%s%s%s",
String::boolText(handled),
msg.c_str(),
(unsigned int)(dt / 1000000),
(unsigned int)(dt % 1000000),
(msg.broadcast() ? " (broadcast)" : ""),
Thread::current(),
Thread::currentName(),
msg.userData(),
rsep,rval,rsep,
par.safe());
}
MsgSniff::MsgSniff()
: Plugin("msgsniff"),
m_first(true), m_mutex(false,"FilterSniff")
{
Output("Loaded module MsgSniffer");
}
void MsgSniff::initialize()
{
Output("Initializing module MsgSniffer");
if (m_first) {
m_first = false;
s_active = Engine::config().getBoolValue("general","msgsniff",false);
SniffMatch* m = new SniffMatch;
m->setFilter(Engine::config().getValue("general","filtersniff"));
m->setParams(Engine::config().getValue("general","filtersniffparams"));
setFilter(m);
s_minAge = (u_int64_t)(1000000.0 * fabs(Engine::config().getDoubleValue("general","agesniff")));
String trackName = Engine::config().getValue("general","msgsniff_trackname");
if (trackName) {
if (trackName.isBoolean()) {
if (trackName.toBoolean())
trackName = "msgsniff";
else
trackName = "";
}
}
Engine::install(new SniffHandler(trackName));
Engine::self()->setHook(new HookHandler);
}
}
}; // anonymous namespace
/* vi: set ts=8 sw=4 sts=4 noet: */