/**
 * ysnmpagent.cpp
 * This file is part of the YATE Project http://YATE.null.ro
 *
 * Module for SNMP protocol agent
 *
 * 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 <yatesnmp.h>

#include <string.h>

// values for the different versions of the protocol
#define SNMP_VERSION_1     	0
#define SNMP_VERSION_2C		1
#define SNMP_VERSION_2S		2	// not implemented
#define SNMP_VERSION_3		3

// user security model
#define USM_SEC_MODEL		3

// privacy flags
#define REPORT_FLAG		0x04
#define PRIVACY_FLAG		0x02
#define	AUTH_FLAG		0x01

// maximum value for the agent encryption salt
#define SALT_MAX_VAL		0xffffffffffffffffULL

// maximum time frame window (in seconds) in which a message should be handled
#define TIMEFRAME_VAL		150
// maximum value engine boots, after which it should be reset
#define ENGINE_BOOTS_MAX 	2147483647
// maximum value for engine time, after which it should be reset
#define ENGINE_TIME_MAX		2147483647

#define MSG_MAX_SIZE		65507

using namespace TelEngine;

namespace {

class TransportType;
class SnmpSocketListener;
class SnmpMsgQueue;
class SnmpMessage;
class SnmpAgent;
class SnmpUser;

class SnmpUdpListener;
class TrapHandler;
class AsnMibTree;

/**
  *	TransportType
  */
class TransportType
{
public:
    enum Type {
	UDP	= 0,
	TCP	= 1, // not implemented
//	OSI	= 2, // not implemented
    };
    inline TransportType(int type = UDP)
	: m_type(type)
	{}

    static inline const char* lookupType(int stat, const char* defVal = 0)
	{ return lookup(stat, s_typeText, defVal); }

    static const TokenDict s_typeText[];

    int m_type;
};

/**
  * Abstract class SnmpSocketListener
  */
class SnmpSocketListener : public Thread
{
public:
    inline SnmpSocketListener(const char* addr, int port, SnmpMsgQueue* queue)
	: Thread("SNMP Socket"),
	  m_addr(TelEngine::null(addr) ? "0.0.0.0" : addr),
	  m_port(port), m_msgQueue(queue)
	{}
    inline ~SnmpSocketListener()
	{}
    virtual bool init() = 0;
    virtual void run() = 0;
    virtual bool sendMessage(DataBlock& data, const SocketAddr& to) = 0;
    virtual void cleanup() = 0;

protected:
    Socket m_socket;
    String m_addr;
    int m_port;
    SnmpMsgQueue* m_msgQueue;
};

/**
  * SnmpMsgQueue - class for holding a queue of messages received
  */
class SnmpMsgQueue : public Thread
{
public:
    SnmpMsgQueue(SnmpAgent* agent, Thread::Priority prio = Thread::Normal,
	const char* addr = "0.0.0.0", unsigned int port = 161, int type = TransportType::UDP);
    ~SnmpMsgQueue();
    virtual bool init();
    virtual void run();
    virtual void cleanup();
    virtual void addMsg(unsigned char* msg, int len, SocketAddr& fromAddr);
    virtual bool sendMsg(SnmpMessage* msg);
    void setSocket(SnmpSocketListener* socket);

protected:
    SnmpSocketListener* m_socket;

private:
    TransportType m_type;
    ObjList m_msgQueue;
    Mutex m_queueMutex;
    SnmpAgent* m_snmpAgent;
};

/**
  * SnmpMessage - a wrapper for SNMP messages
  */
class SnmpMessage : public GenObject
{
public:
    inline SnmpMessage()
	{}
    inline SnmpMessage(unsigned char* rawData, unsigned int length, SocketAddr fromAddr)
	:  m_from(fromAddr)
	{ m_data.assign(rawData,length);}
    inline ~SnmpMessage()
	{ }
    inline void setData(const DataBlock& data)
	{ m_data = data;}
    inline DataBlock& data()
	{ return m_data;}
    inline const SocketAddr& peer()
	{ return m_from;}
    inline void setPeer(SocketAddr peer)
	{ m_from = peer;}
private:
    DataBlock  m_data;
    SocketAddr m_from;
};

class SnmpUser : public GenObject
{
public:
    // enum for authentication and privacy encryption
    enum SecurityProtocols {
	MD5_AUTH	=	1,
	SHA1_AUTH	= 	2,
	AES_ENCRYPT	=	3,
	DES_ENCRYPT	=	4
    };
    // Constructor
    SnmpUser(NamedList* cfg);

    // Destructor
    inline ~SnmpUser()
    {}

    inline const String& toString() const
	{ return m_name; }

    inline bool needsAuth()
	{ return !m_authPassword.null(); }
    inline bool needsPriv()
    	{ return !m_privPassword.null(); }
    inline int authProto()
	{ return m_authProto; }
    inline int privProto()
	{ return m_privProto; }
    inline int accessLevel()
	{ return m_accessLevel; }
    inline const DataBlock& digestK1()
	{ return m_k1; }
    inline const DataBlock& digestK2()
	{ return m_k2; }
    inline const DataBlock& authKey()
	{ return m_authKey; }
    inline const DataBlock& privKey()
	{ return m_privKey; }

private:
    String m_name;
    String m_authPassword;
    String m_privPassword;
    int m_authProto;
    int m_privProto;
    int m_accessLevel;

    DataBlock m_authKey;
    DataBlock m_k1;
    DataBlock m_k2;
    DataBlock m_privKey;

    static TokenDict s_access[];
    // generate encryption key
    DataBlock generateAuthKey(String pass = "");
    // generate an authentication information
    void generateAuthInfo();
};

/**
  * SnmpV3MsgContainer - a container for a decoded SNMPv3 message data
  */
class SnmpV3MsgContainer : public GenObject
{
public:
    // Constructor
    inline SnmpV3MsgContainer()
	: m_scopedPdu(0), m_user(0), m_msgEngineBoots(0),
	  m_msgEngineTime(0), m_msgId(0), m_securityModel(0),
	  m_msgMaxSize(MSG_MAX_SIZE), m_privFlag(false), m_authFlag(false), m_reportFlag(false)
    {}
    // Destructor
    inline ~SnmpV3MsgContainer()
	{ TelEngine::destruct(m_scopedPdu);}

    // Return the security parameters from the message
    inline Snmp::UsmSecurityParameters& getSecurity()
	{ return m_security;}
    // reportable flag
    inline bool reportable()
	{ return m_reportFlag;}

    // validate a SNMPv3 message
    int validate(Snmp::SNMPv3Message& msg, int& authRes);
    // handle a request
    int processRequest(Snmp::SNMPv3Message& msg);
    // prepare a message for sending. Generate authentication and privacy information as needed.
    int prepareForSend(Snmp::SNMPv3Message& msg);
    // in case the message exceeds the size constraint for a SNMPv3 message, generate a tooBigMessage response
    int generateTooBigMsg(Snmp::SNMPv3Message& msg);

    // extract information from the message header and verify it
    int processHeader(Snmp::SNMPv3Message& msg);
    // extract and verify authentication and privacy information
    int processSecurityModel(Snmp::SNMPv3Message& msg);
    // proces the pdu from the message
    int processScopedPdu();

    // check the authentication credentials for the given user
    int checkUser();
    // check to see if the message was received in the acceptable timeframe window
    int checkTimeWindow();
    // check digest of the message
    int checkAuth(Snmp::SNMPv3Message& msg);

    // build a message digest
    void msgDigest(Snmp::SNMPv3Message& msg, OctetString& digest);

    // decrypt a pdu
    int decrypt(Snmp::SNMPv3Message& msg);
    // encrypt a given pdu, return encryption result in ecryptedPdu
    int encrypt(Snmp::ScopedPDU* pdu, DataBlock& encryptedPdu);

    // get the maximum size of a message
    inline int msgMaxSize()
	{ return m_msgMaxSize;}

    // set the SNMPv3 user for this message
    inline void setUser(SnmpUser* user)
    {
	m_user = user;
	if (m_user) {
	    m_authFlag = m_user->needsAuth();
	    m_privFlag = m_user->needsPriv();
	}
    }

    inline void setScopedPdu(Snmp::ScopedPDU* scopedPdu)
	{ m_scopedPdu = scopedPdu;}
    inline void setAuthFlag(bool val = true)
	{ m_authFlag = val; }
    inline void setPrivFlag(bool val = true)
	{ m_privFlag = val; }
    inline void setReportFlag(bool val = true)
	{ m_reportFlag = val; }
private:
    // security parameters
    Snmp::UsmSecurityParameters m_security;
    // message pdu
    Snmp::ScopedPDU* m_scopedPdu;
    // user
    SnmpUser* m_user;
     // message "salt"
    DataBlock m_msgSalt;
    // message engine boots
    u_int32_t m_msgEngineBoots;
    // message engine time
    u_int32_t m_msgEngineTime;
    // message id
    int m_msgId;
    // message USM
    int m_securityModel;
    // message maximum size
    int m_msgMaxSize;
    // message flags
    bool m_privFlag;
    bool m_authFlag;
    bool m_reportFlag;
};

/**
  * SnmpAgent
  */
class SnmpAgent : public Module
{
public:
    // type of values
    enum ValType {
	INTEGER,
	STRING,
	OBJECT_ID,
	IPADDRESS,
	COUNTER,
	TIMETICKS,
	ARBITRARY,
	BIG_COUNTER,
	UNSIGNED_INTEGER
    };
    // SNMPv3 process statuses
    enum USMEnum {
	WRONG_COMMUNITY		= -2,
	MESSAGE_DROP		= -1,
	SUCCESS			= 0,
	WRONG_SEC_LEVEL		= 1,
	WRONG_WINDOW_TIME	= 2,
	WRONG_USER 		= 3,
	WRONG_ENGINE_ID		= 4,
	WRONG_DIGEST		= 5,
	WRONG_ENCRYPT		= 6
    };
    // formats for generating a engine ID
    enum EngineFormats {
	IPv4		= 1,
        IPv6		= 2,
	MAC		= 3,
	TEXT		= 4,
	OCTETS		= 5,
	ENTERPRISE 	= 128
    };

    SnmpAgent();
    virtual ~SnmpAgent();
    //inherited methods
    virtual void initialize();
    virtual bool received(Message& msg, int id);
    bool unload();

    inline const OctetString& getEngineID()
	{ return m_engineId;}

    inline u_int32_t getEngineBoots()
	{ return m_engineBoots;}

    inline u_int32_t getEngineTime()
    {
	u_int32_t time = Time::secNow() - m_startTime;
	if (time >= ENGINE_TIME_MAX) {
	    m_engineBoots++;
	    m_startTime += time;
	    time = 0;
	}
	return time;
    }

    // get the salt used for security. Change it's value with each call.
    inline u_int32_t getEngineSalt()
    {
	u_int32_t tmp = (u_int32_t)(m_salt++);
	m_salt = (m_salt == SALT_MAX_VAL ? 0 : m_salt);
	return tmp;
    }

    // process a SNMP message
    int processMsg(SnmpMessage* msg);
    // process a SNMPv2 message
    int processSnmpV2Msg(Snmp::Message& msg, const String& host);
    // decode a pdu and process it accordingly
    void decodePDU(int& reqType, Snmp::PDU* obj, const int& access);
    // process get/getnext/set requests
    int processGetReq(Snmp::VarBind* varBind, AsnValue* value, int& error, const int& access);
    int processGetNextReq(Snmp::VarBind* varBind, AsnValue* value, int& error, const int& access);
    int processSetReq(Snmp::VarBind* varBind, int& error, const int& access);
    // handle a getbulkrequest
    Snmp::PDU* decodeBulkPDU(int& reqType, Snmp::BulkPDU* pdu, const int& access);

    // handle a SNMPv3 message
    int processSnmpV3Msg(Snmp::SNMPv3Message& msg, const String& host, int& authRes);
    // generate reports and responses for SNMPv3 messages
    int generateReport(Snmp::SNMPv3Message& msg, const int& usmRes, SnmpV3MsgContainer& msgContainer);
    int generateResponse(Snmp::SNMPv3Message& msg, SnmpV3MsgContainer& msgContainer);


    Snmp::PDU* getPDU(Snmp::PDUs& p);
    // get a encryption object
    Cipher* getCipher(int criptoType = SnmpUser::DES_ENCRYPT);

    // get&set the value from/for a variable binding
    DataBlock getVal(Snmp::VarBind* varBind);
    void assignValue(Snmp::VarBind* varBind, AsnValue* val);

    // obtain the value for a query
    AsnValue makeQuery(const String& query, unsigned int& index, AsnMib* mib = 0);

    // send in form of a SNMP trap a notification
    bool sendNotification(const String& notif, const String* value = 0,
	unsigned int index = 0, const NamedList* extra = 0);
    // build trap network destination
    SocketAddr buildDestination(const String& ip, const String& port);
    // build a variable bindings list of mandatory OIDs for a trap
    Snmp::VarBindList* addTrapOIDs(const String& notifOID);
    // build SNMPv2 and SNMPv3 traps
    Snmp::SNMPv2_Trap_PDU buildTrapPDU(const String& name, const String* value = 0,
	unsigned int index = 0);
    int buildTrapMsgV3(Snmp::SNMPv3Message& msg, DataBlock pduData);

    // set a value from a varbind
    void setValue(const String& varName, Snmp::VarBind* val, int& error);
    // generate a engineID
    OctetString genEngineId(const int format, String& info);
    // verify if a trap is disabled
    bool trapDisabled(const String& name);

    // verify if a query is in the Yate tree
    bool queryIsSupported(const String& query, AsnMib* mib = 0);
    // obtain a SNMPv3 user
    inline SnmpUser* getUser(const String& user)
	{ return static_cast<SnmpUser*>(m_users[user]); }
    void setMsgQueue(SnmpMsgQueue* queue);
    void authFail(const SocketAddr& addr, int snmpVersion, int reason, int protocol = TransportType::UDP);

private:
    bool m_init;
    SnmpMsgQueue* m_msgQueue;
    String m_lastRecvHost;
    String m_roCommunity;
    String m_rwCommunity;
    String m_rcCommunity;
    AsnMibTree* m_mibTree;
    // msg v3 vars
    OctetString m_engineId;
    u_int32_t m_engineBoots;
    u_int32_t m_startTime;

    //  user security model statistics
    u_int32_t m_stats[7];

    u_int32_t m_silentDrops;
    u_int64_t m_salt;

    TrapHandler* m_trapHandler;
    ObjList* m_traps;

    // SNMP v3 users
    SnmpUser* m_trapUser;
    ObjList m_users;

    // AES and DES ciphers
    Cipher* m_cipherAES;
    Cipher* m_cipherDES;
};

/**
  * SnmpUdpListener - UDP socket for receiving and sending messages
  */
class SnmpUdpListener : public SnmpSocketListener
{
public:
    SnmpUdpListener(const char* addr, int port, SnmpMsgQueue* queue);
    ~SnmpUdpListener();
    virtual bool init();
    virtual void run();
    virtual bool sendMessage(DataBlock& data, const SocketAddr& to);
    virtual void cleanup();
};

/**
  * CipherHolder - class for obtaining an appropriate encryption/decryption object from OpenSSL module
  */
class CipherHolder : public RefObject
{
public:
    inline CipherHolder()
	: m_cipher(0)
	{ }
    virtual ~CipherHolder()
	{ TelEngine::destruct(m_cipher); }
    virtual void* getObject(const String& name) const
	{ return (name == YATOM("Cipher*")) ? (void*)&m_cipher : RefObject::getObject(name); }
    inline Cipher* cipher()
	{ Cipher* tmp = m_cipher; m_cipher = 0; return tmp; }
private:
    Cipher* m_cipher;
};

/**
 * Tree of OIDs.
 */
class AsnMibTree : public GenObject {
    YCLASS(AsnMibTree, GenObject)
public:
    inline AsnMibTree()
	{}
    // Constructor with file name from which the tree is to be built
    AsnMibTree(const String& fileName);
    virtual ~AsnMibTree();
    // Find a MIB object given the object id
    AsnMib* find(const ASNObjId& id);
    // Find a MIB given the MIB name
    AsnMib* find(const String& name);
    // Find the next MIB object in the tree
    AsnMib* findNext(const ASNObjId& id);
    // Get access level for the given object id
    int getAccess(const ASNObjId& oid);
    // Build the tree of MIB objects
    void buildTree();
    //Find the module revision of which this OID is part of
    String findRevision(const String& name);

private:
    String m_treeConf;
    ObjList m_mibs;
};

const TokenDict TransportType::s_typeText[] = {
    {"UDP",	UDP},
    {"TCP",	TCP},
    {0,0}
};

static const TokenDict s_proto[] = {
    {"SNMPv1",	SNMP_VERSION_1},
    {"SNMPv2c",	SNMP_VERSION_2C},
    {"SNMPv3",	SNMP_VERSION_3},
    {0,0}
};

static const TokenDict s_errors[] = {
    {"MESSAGE_DROP",		SnmpAgent::MESSAGE_DROP},
    {"SUCCESS",			SnmpAgent::SUCCESS},
    {"WRONG_SEC_LEVEL",		SnmpAgent::WRONG_SEC_LEVEL},
    {"WRONG_WINDOW_TIME",	SnmpAgent::WRONG_WINDOW_TIME},
    {"WRONG_USER",		SnmpAgent::WRONG_USER},
    {"WRONG_ENGINE_ID",		SnmpAgent::WRONG_ENGINE_ID},
    {"WRONG_DIGEST",		SnmpAgent::WRONG_DIGEST},
    {"WRONG_ENCRYPT",		SnmpAgent::WRONG_ENCRYPT},
    {0,0}
};

static const TokenDict s_readableErrors[] = {
    {"wrong community string",  SnmpAgent::WRONG_COMMUNITY},
    {"message dropped",         SnmpAgent::MESSAGE_DROP},
    {"success",                 SnmpAgent::SUCCESS},
    {"wrong security level",    SnmpAgent::WRONG_SEC_LEVEL},
    {"wrong time window",       SnmpAgent::WRONG_WINDOW_TIME},
    {"unknown user",            SnmpAgent::WRONG_USER},
    {"wrong engine ID",         SnmpAgent::WRONG_ENGINE_ID},
    {"wrong digest",            SnmpAgent::WRONG_DIGEST},
    {"encryption failure",      SnmpAgent::WRONG_ENCRYPT},
    {0,0}
};

static const TokenDict s_stats[] = {
    {"usmStatsUnknownEngineIDs",	SnmpAgent::WRONG_ENGINE_ID},
    {"usmStatsUnknownUserNames",	SnmpAgent::WRONG_USER},
    {"usmStatsWrongDigests",		SnmpAgent::WRONG_DIGEST},
    {"usmStatsUnsupportedSecLevels",	SnmpAgent::WRONG_SEC_LEVEL},
    {"usmStatsDecryptionErrors",	SnmpAgent::WRONG_ENCRYPT},
    {"usmStatsNotInTimeWindows",	SnmpAgent::WRONG_WINDOW_TIME},
    {0,0}
};

static const TokenDict s_crypto[] = {
    {"DES_CBC",		SnmpUser::DES_ENCRYPT},
    {"AES128_CFB",	SnmpUser::AES_ENCRYPT},
    {0,0}
};

static const TokenDict s_pdus[] = {
    {"GetRequest",	Snmp::PDUs::GET_REQUEST},
    {"GetNextRequest",	Snmp::PDUs::GET_NEXT_REQUEST},
    {"GetBulkRequest",	Snmp::PDUs::GET_BULK_REQUEST},
    {"Response",	Snmp::PDUs::RESPONSE},
    {"SetRequest",	Snmp::PDUs::SET_REQUEST},
    {"InformRequest",	Snmp::PDUs::INFORM_REQUEST},
    {"SnmpV2Trap",	Snmp::PDUs::SNMPV2_TRAP},
    {"Report",		Snmp::PDUs::REPORT},
    {0,0}
};

static const TokenDict s_types[] = {
    // ASN.1 built-in types
    {"INTEGER",		AsnValue::INTEGER},
    {"OCTET_STRING",	AsnValue::STRING},
    {"OBJECT_ID",	AsnValue::OBJECT_ID},
    // SNMP v2 SMI
    {"Integer32",	AsnValue::INTEGER},
    {"DisplayString", 	AsnValue::STRING},
    // SNMP v2 SMI tagged types
    {"IpAddress",	AsnValue::IPADDRESS},
    {"Counter32",	AsnValue::COUNTER},
    {"Gauge32",		AsnValue::UNSIGNED_INTEGER},
    {"Unsigned32",	AsnValue::UNSIGNED_INTEGER},
    {"TimeTicks",	AsnValue::TIMETICKS},
    {"Opaque",		AsnValue::ARBITRARY},
    {"Counter64",	AsnValue::BIG_COUNTER},
    {0,0}
};

TokenDict SnmpUser::s_access[] = {
    {"readonly",    AsnMib::readOnly},
    {"readwrite",   AsnMib::readWrite},
    {"readCreate",  AsnMib::readCreate},
    {0,0}
};

static Configuration s_cfg;
static Configuration s_saveCfg;
static bool s_enabledTraps = false;
static u_int8_t s_zero = 0;

static u_int32_t s_pen = 34501;

static SocketAddr s_remote;
static String s_yateRoot;
static String s_yateVersion;

// zero digest
static const DataBlock s_zeroKey(0,12);

INIT_PLUGIN(SnmpAgent);

UNLOAD_PLUGIN(unloadNow)
{
    if (unloadNow && !__plugin.unload())
	return false;
    return true;
}

/**
  * AsnMibTree
  */
AsnMibTree::AsnMibTree(const String& fileName)
{
    DDebug(&__plugin,DebugAll,"AsnMibTree object created from %s", fileName.c_str());
    m_treeConf = fileName;
    buildTree();
}

AsnMibTree::~AsnMibTree()
{
    m_mibs.clear();
}

void AsnMibTree::buildTree()
{
    Configuration cfgTree;
    cfgTree = m_treeConf;
    if(!cfgTree.load())
	Debug(&__plugin,DebugWarn,"Failed to load MIB tree");
    else {
    	for (unsigned int i = 0; i < cfgTree.sections(); i++) {
    	    NamedList* sect = cfgTree.getSection(i);
    	    if (sect) {
	    	AsnMib* mib = new AsnMib(*sect);
	    	m_mibs.append(mib);
	    }
    	}
    }
}

String AsnMibTree::findRevision(const String& name)
{
    AsnMib* mib = find(name);
    if (!mib)
    	return "";
    String revision = "";
    while (revision.null()) {
    	ASNObjId parentID = mib->getParent();
    	AsnMib* parent = find(parentID);
    	if (!parent)
    	    return revision;
    	revision = parent->getRevision();
    	mib = parent;
    }
    return revision;
}

AsnMib* AsnMibTree::find(const String& name)
{
    DDebug(&__plugin,DebugAll,"AsnMibTree::find('%s')",name.c_str());
    const ObjList *n = m_mibs.skipNull();
    AsnMib* mib = 0;
    while (n) {
	mib = static_cast<AsnMib*>(n->get());
	if (name == mib->getName())
	    break;
	n = n->skipNext();
	mib = 0;
    }
    return mib;
}

AsnMib* AsnMibTree::find(const ASNObjId& id)
{
    DDebug(&__plugin,DebugAll,"AsnMibTree::find('%s')",id.toString().c_str());

    String value = id.toString();
    int pos = 0;
    int index = 0;
    AsnMib* searched = 0;
    unsigned int cycles = 0;
    while (cycles < 2) {
 	ObjList* n = m_mibs.find(value);
	searched = n ? static_cast<AsnMib*>(n->get()) : 0;
	if (searched) {
	    searched->setIndex(index);
	    return searched;
	}
	pos = value.rfind('.');
	if (pos < 0)
	    return 0;
	index = value.substr(pos + 1).toInteger();
	value = value.substr(0,pos);
	cycles++;
    }
    return searched;
}

AsnMib* AsnMibTree::findNext(const ASNObjId& id)
{
    DDebug(&__plugin,DebugAll,"AsnMibTree::findNext('%s')",id.toString().c_str());
    String searchID = id.toString();
    // check it the oid is in our known tree
    AsnMib* root = static_cast<AsnMib*>(m_mibs.get());
    if (root && !(id.toString().startsWith(root->toString()))) {
    	NamedList p(id.toString());
    	AsnMib oid(p);
    	int comp = oid.compareTo(root);
    	if (comp < 0)
    	    searchID = root->toString();
    	else if (comp > 0)
    	    return 0;
    }
    AsnMib* searched = static_cast<AsnMib*>(m_mibs[searchID.toString()]);
    if (searched) {
    	if (searched->getAccessValue() > AsnMib::accessibleForNotify) {
	    DDebug(&__plugin,DebugInfo,"AsnMibTree::findNext('%s') - found an exact match to be '%s'",
			id.toString().c_str(), searched->toString().c_str());
	    return searched;
	}
    }
    String value = searchID.toString();
    int pos = 0;
    int index = 0;
    while (true) {
 	ObjList* n = m_mibs.find(value);
	searched = n ? static_cast<AsnMib*>(n->get()) : 0;
	if (searched) {
	    if (id.toString() == searched->getOID() || id.toString() == searched->toString()) {
	    	ObjList* aux = n->skipNext();
	    	if (!aux)
	    	    return 0;
	    	while (aux) {
		    AsnMib* mib = static_cast<AsnMib*>(aux->get());
		    if (mib && mib->getAccessValue() > AsnMib::accessibleForNotify)
			return mib;
		    aux = aux->skipNext();
	    	}
		return 0;
	    }
	    else {
	    	searched->setIndex(index + 1);
	    	return searched;
	    }
	}
	pos = value.rfind('.');
	if (pos < 0)
	    return 0;
	index = value.substr(pos + 1).toInteger();
	value = value.substr(0,pos);
    }
    return 0;
}

int AsnMibTree::getAccess(const ASNObjId& id)
{
    DDebug(&__plugin,DebugAll,"AsnMibTree::getAccess('%s')",id.toString().c_str());
    AsnMib* mib = find(id);
    if (!mib)
	return 0;
    return mib->getAccessValue();
}

/**
  * TrapHandler - message handler for incoming notifications
  */
class TrapHandler : public MessageHandler
{
public:
    inline TrapHandler(unsigned int priority = 100)
	: MessageHandler("monitor.notify",priority,__plugin.name())
	{ }
    virtual ~TrapHandler()
	{ }
    virtual bool received(Message& msg);
};


static DataBlock toNetworkOrder(u_int64_t val, unsigned int size)
{
    XDebug(&__plugin,DebugAll,"toNetworkOrder("FMT64")",val);
    DataBlock res;
    for (unsigned int i = 0; i < size; i++) {
	DataBlock aux;
	u_int8_t auxInt = (u_int8_t)(val >> (8 * i));
	aux.append(&auxInt,1);
	res.insert(aux);
    }
    return res;
}

/**
  * SnmpUdpListener
  */
SnmpUdpListener::SnmpUdpListener(const char* addr, int port, SnmpMsgQueue* queue)
    : SnmpSocketListener(addr,port,queue)
{
    DDebug(&__plugin,DebugAll,"SnmpUdpListener created for %s:%d",m_addr.safe(),m_port);
}

SnmpUdpListener::~SnmpUdpListener()
{
    DDebug(&__plugin,DebugAll,"SnmpUdpListener for %s:%d destroyed",m_addr.safe(),m_port);
}

bool SnmpUdpListener::init()
{
    SocketAddr addr;

    if (!addr.assign(AF_INET) || !addr.host(m_addr) || !addr.port(m_port)) {
	Alarm(&__plugin,"socket",DebugWarn,"Could not assign values to socket address for SNMP UDP Listener");
	return false;
    }

    if (!m_socket.create(addr.family(),SOCK_DGRAM)) {
	Alarm(&__plugin,"socket",DebugWarn,"Could not create socket for SNMP UDP Listener error %d",
	    m_socket.error());
	return false;
    }

    m_socket.setReuse();

    if (!m_socket.bind(addr)) {
	Alarm(&__plugin,"socket",DebugWarn,"Could not bind SNMP UDP Listener, error %d %s",
	    m_socket.error(),strerror(m_socket.error()));
	return false;
    }
    if (!m_socket.setBlocking(false)) {
	Alarm(&__plugin,"socket",DebugWarn,"Could not set nonblocking SNMP UDP Listener, error %d %s",
	    m_socket.error(),strerror(m_socket.error()));
	return false;
    }
    Debug(&__plugin,DebugInfo,"SNMP UDP Listener initialized on port %d", m_port);
    return startup();
}

void SnmpUdpListener::run()
{
    DDebug(&__plugin,DebugInfo,"SNMP UDP Listener started to run");
    unsigned char buffer[2048];

    for (;;) {
	bool readOk = false;
	bool error = false;

	check();

	if (!m_socket.select(&readOk,0,&error,idleUsec()))
	    continue;

	if (!readOk || error) {
	     if (error)
		Debug(&__plugin,DebugInfo,"SNMP UDP Reading data error: (%d)",m_socket.error());
	     continue;
	}

	SocketAddr from;
	int readSize = m_socket.recvFrom(buffer,sizeof(buffer),from,0);
	if (!readSize) {
	    if (m_socket.canRetry()) {
		idle(true);
		continue;
	    }
	}
	else if (readSize < 0) {
	    if (m_socket.canRetry()) {
		idle(true);
		continue;
	    }
	    cancel();
	    Debug(&__plugin,DebugWarn,"SNMP UDP Read error in SnmpUdpListener [%p]",this);
	    break;
	}

	buffer[readSize] = 0;

	if (m_msgQueue)
	    m_msgQueue->addMsg(buffer,readSize,from);
    }
}

bool SnmpUdpListener::sendMessage(DataBlock& d, const SocketAddr& to)
{
    DDebug(&__plugin,DebugAll,"SnmpUdpListener::sendMessage() of length '%d' to '%s:%d'",d.length(),to.host().c_str(),to.port());

    int len = d.length();
    while (m_socket.valid() && (len > 0)) {

	bool writeOk = false,error = false;

	if (!m_socket.select(0,&writeOk,&error,idleUsec()))
	    continue;
	if (!writeOk || error)
	    continue;
	int w = m_socket.sendTo(d.data(),len,to);
	if (w < 0) {
	    if (!m_socket.canRetry()) {
		// we are shooting ourselves in the foot!
		s_enabledTraps = false;
		Alarm(&__plugin,"socket",DebugWarn,"Could not send message, SNMP disabled!");
		cancel();
		return false;
	    }
	}
	else
	    len -= w;
    }
    return true;
}

void SnmpUdpListener::cleanup()
{
    DDebug(&__plugin,DebugAll,"SnmpUdpListener::cleanup() [%p]",this);
    m_msgQueue->setSocket(0);
}


/**
  * SnmpMsgQueue
  */
SnmpMsgQueue::SnmpMsgQueue(SnmpAgent* agent, Thread::Priority prio, const char* addr, unsigned int port, int type)
    : Thread("SNMP Queue",prio),
      m_socket(0),
      m_queueMutex(false,"SnmpAgent::queue"),
      m_snmpAgent(agent)
{
    Debug(&__plugin,DebugAll,"SnmpMsgQueue created for %s:%d with priority '%s'",addr,port,priority(prio));
    if (type == TransportType::UDP) {
	m_socket = new SnmpUdpListener(addr,port,this);
	if (!m_socket->init()) {
	    delete m_socket;
	    m_socket = 0;
	}
    }
}

SnmpMsgQueue::~SnmpMsgQueue()
{
    DDebug(&__plugin,DebugAll,"~SnmpMsgQueue() [%p]",this);
    m_snmpAgent->setMsgQueue(0);
}

bool SnmpMsgQueue::init()
{
    DDebug(&__plugin,DebugAll,"SnmpMsgQueue::init()");
    if (!m_socket)
	return false;
    return startup();
}

void SnmpMsgQueue::cleanup()
{
    DDebug(&__plugin,DebugAll,"SnmpMsgQueue::cleanup()");
    m_msgQueue.clear();
    if (m_socket)
	m_socket->cancel();
    while (m_socket)
	Thread::idle();
}

void SnmpMsgQueue::setSocket(SnmpSocketListener* socket)
{
    m_queueMutex.lock();
    m_socket = socket;
    m_queueMutex.unlock();
}

void SnmpMsgQueue::run()
{
    while (m_socket && m_snmpAgent) {
	Thread::check();
	SnmpMessage* msg = 0;
	if (m_msgQueue.get()) {
	    m_queueMutex.lock();
	    msg = static_cast<SnmpMessage*>(m_msgQueue.remove(false));
	    m_queueMutex.unlock();
	}
	if (!msg) {
	    idle();
	    continue;
	}

	XDebug(&__plugin,DebugAll,"Processing message [%p]",msg);

	if (m_snmpAgent) {
	    int res = m_snmpAgent->processMsg(msg);
	    if (res < 0)
		Debug(&__plugin,DebugAll,"Error processing message [%p]",msg);
	}
	XDebug(&__plugin,DebugAll,"Processing of message [%p] finished",msg);
	TelEngine::destruct(msg);
    }
}

void SnmpMsgQueue::addMsg(unsigned char* msg, int len, SocketAddr& fromAddr)
{
    XDebug(&__plugin,DebugAll,"SnmpMsgQueue::addMsg() - message received with length %d from address %s:%d",
	len,fromAddr.host().c_str(),fromAddr.port() );

    if(!msg)
	return;
    SnmpMessage* snmpMsg = new SnmpMessage(msg,len,fromAddr);

    m_queueMutex.lock();
    m_msgQueue.append(snmpMsg);
    m_queueMutex.unlock();
}

bool SnmpMsgQueue::sendMsg(SnmpMessage* msg)
{
    DDebug(&__plugin,DebugAll,"SnmpMsgQueue::sendMsg([%p])",msg);
    if (!msg)
	return false;
    DataBlock content = msg->data();
    return m_socket && (content.length() > 0) && m_socket->sendMessage(content,msg->peer());
}

/**
  * TrapHandler
  */
bool TrapHandler::received(Message& msg)
{
    unsigned int index = msg.getIntValue("index",0);
    const String& single = msg[YSTRING("notify")];
    if (single)
        return __plugin.sendNotification(single,msg.getParam(YSTRING("value")),index,&msg);
    bool ok = false;
    int count = msg.getIntValue("count",-1);
    for (int i = 0; ; i++) {
	if (count >= 0 && i >= count)
	    break;
	String param = "notify.";
	param << i;
        const String& notif = msg[param];
        if (!notif.null()) {
	    String paramValue = "value.";
	    paramValue << i;
            ok = __plugin.sendNotification(notif,msg.getParam(paramValue),index) || ok;
        }
        else if (count < 0)
	    break;
    }
    return ok;
}

/**
 * SnmpUser
 */
SnmpUser::SnmpUser(NamedList* cfg)
{
    DDebug(&__plugin,DebugAll,"SnmpUser::SnmpUser(cfg=%p) [%p]",cfg,this);
    if (cfg) {
	m_name = *cfg;
	m_authPassword = cfg->getValue("auth_password","");

    	String proto = cfg->getValue("auth_protocol","MD5");
	m_authProto = (proto == "MD5" ? MD5_AUTH : SHA1_AUTH);

	m_privPassword = cfg->getValue("priv_password","");

	proto = cfg->getValue("priv_protocol","DES");
	m_privProto = (proto == "DES" ? DES_ENCRYPT : AES_ENCRYPT);

	// get the user's privilege level
	String access = cfg->getValue("access","readonly");
	m_accessLevel = lookup(access,s_access);

	if (needsAuth())
	    generateAuthInfo();
    }
}

DataBlock SnmpUser::generateAuthKey(String password)
{
    DataBlock b;
    if (TelEngine::null(password))
	return b;
    DDebug(&__plugin,DebugAll,"SnmpUser::generateAuthKey(user=%s) [%p]",m_name.c_str(),this);
    DataBlock authKey(0,64);

    MD5 digestMD5;
    SHA1 digestSHA;
    int count = 0;
    int passIndex = 0;
    int len = password.length();
    // initialization
    unsigned char ku[64];
    while (count < 1048576) {	// 1MB
	for (int i = 0; i < 64; i++)
	   ku[i] = password[passIndex++ % len];
	count += 64;
	if (m_authProto == MD5_AUTH) {
	    digestMD5.update(ku,64);
	    continue;
	}
	if (m_authProto == SHA1_AUTH)
	    digestSHA.update(ku,64);
    }

    DataBlock aux;
    if (m_authProto == MD5_AUTH) {
	digestMD5.finalize();
	aux.append((void*)digestMD5.rawDigest(), digestMD5.rawLength());
	digestMD5.clear();
    }
    else if (m_authProto == SHA1_AUTH) {
	digestSHA.finalize();
	aux.append((void*)digestSHA.rawDigest(), digestSHA.rawLength());
	digestSHA.clear();
    }
    authKey.clear();

    // key localization
    authKey.append(aux);
    authKey.append(__plugin.getEngineID());
    authKey.append(aux);

    // obtain final key according to encryption method
    if (m_authProto == SnmpUser::MD5_AUTH) {
	digestMD5.update(authKey);
	digestMD5.finalize();
	authKey.clear();
	authKey.append((void*)digestMD5.rawDigest(),digestMD5.rawLength());
	authKey.truncate(16);
	DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::generateAuthKey() [%p] - MD5 authKey generated %s",
	      this,digestMD5.hexDigest().substr(0,32).c_str());
	return authKey;
    }
    else if (m_authProto == SnmpUser::SHA1_AUTH) {
	digestSHA.update(authKey);
	digestSHA.finalize();
	authKey.clear();
	authKey.append((void*)digestSHA.rawDigest(),digestSHA.rawLength());
	authKey.truncate(20);
	DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::generateAuthKey() [%p] - SHA authKey generated %s",
		this,digestSHA.hexDigest().substr(0,40).c_str());
	return authKey;
    }
    Debug(&__plugin,DebugInfo,"::generateAuthKey() [%p] - invalid auth protocol",this);
    m_authKey.clear();
    return authKey;
}

// generate an authentication information
void SnmpUser::generateAuthInfo()
{
    if (TelEngine::null(m_authPassword))
	return;

    m_authKey = generateAuthKey(m_authPassword);
    m_k1.clear();
    m_k2.clear();
    for (unsigned int i = 0; i < 64; i++) {
	u_int8_t val = 0;
	if (i < m_authKey.length())
	    val = m_authKey[i];
	u_int8_t x1 = val ^ 0x36;
	u_int8_t x2 = val ^ 0x5c;
	m_k1.append(&x1,1);
	m_k2.append(&x2,1);
    }
    m_privKey = generateAuthKey(m_privPassword);
}

/**
  * SnmpV3MsgContainer
  */

// validate a message
int SnmpV3MsgContainer::validate(Snmp::SNMPv3Message& msg, int& authRes)
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::validate() [%p]",this);
    int res = processHeader(msg);
    if (res != SnmpAgent::SUCCESS)
	return res;
    res = processSecurityModel(msg);
    if (res != SnmpAgent::SUCCESS)
	return res;

    // if the auth flag is set, check the digest for the message
    if (m_authFlag) {
	res = checkAuth(msg);
	if (res != SnmpAgent::SUCCESS) {
	    authRes = res;
	    return res;
	}
    }
    // check the user data
    res = checkUser();
    if (res != SnmpAgent::SUCCESS) {
	authRes = res;
	return res;
    }
    // if the privacy flag is set, decrypt the message
    if (m_privFlag) {
	res = decrypt(msg);
	if (res != SnmpAgent::SUCCESS)
	    return res;
    }
    Debug(&__plugin,DebugAll,"SnmpV3MsgContainer::validate() [%p] - message %p validated",this,&msg);
    return res;
}
// handle the request fom the SNMPv3 message
int SnmpV3MsgContainer::processRequest(Snmp::SNMPv3Message& msg)
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::processRequest() [%p]",this);
    if (msg.m_msgData && msg.m_msgData->m_choiceType == Snmp::ScopedPduData::PLAINTEXT)
	m_scopedPdu = msg.m_msgData->m_plaintext;
    processScopedPdu();
    return SnmpAgent::SUCCESS;
}

// prepare for sending a SNMPv3 message
int SnmpV3MsgContainer::prepareForSend(Snmp::SNMPv3Message& msg)
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::prepareForSend() [%p]",this);

    // set the message flags
    u_int8_t msgFlags = 0;
    if (m_reportFlag)
	msgFlags &= ~REPORT_FLAG;
    if (m_authFlag)
	msgFlags |= AUTH_FLAG;
    if (m_privFlag)
	msgFlags |= PRIVACY_FLAG;
    if (!msg.m_msgGlobalData)
	return SnmpAgent::MESSAGE_DROP;
    msg.m_msgGlobalData->m_msgFlags.assign(&msgFlags,sizeof(msgFlags));

    // make sure auth and encrypt parameters are empty
    m_security.m_msgPrivacyParameters.clear();
    m_security.m_msgAuthenticationParameters.clear();
    m_security.m_msgAuthoritativeEngineID = __plugin.getEngineID();
    m_security.m_msgAuthoritativeEngineTime = __plugin.getEngineTime();
    m_security.m_msgAuthoritativeEngineBoots = __plugin.getEngineBoots();

    if (!m_user && m_authFlag)
	return SnmpAgent::MESSAGE_DROP;
    // if the privacy flag is set, encrypt the pdu
    if (m_user && m_privFlag && m_user->needsPriv()) {
	if (!msg.m_msgData)
	    msg.m_msgData = new Snmp::ScopedPduData();
	msg.m_msgData->m_encryptedPDU.clear();
	encrypt(m_scopedPdu,msg.m_msgData->m_encryptedPDU);
	msg.m_msgData->m_choiceType = Snmp::ScopedPduData::ENCRYPTEDPDU;
	m_security.m_msgPrivacyParameters.append(m_msgSalt);
    }
    // if the auth flag is set, compute the message digest and set it in the message
    if (m_user && m_authFlag && m_user->needsAuth()) {
	m_security.m_msgAuthenticationParameters = s_zeroKey;
	OctetString digest;
	msgDigest(msg,digest);
	m_security.m_msgAuthenticationParameters = digest;
    }
    // encode and set the security parameters
    DataBlock data;
    msg.m_msgSecurityParameters.clear();
    m_security.encode(msg.m_msgSecurityParameters);
    return SnmpAgent::SUCCESS;
}

// build a tooBigMessage
int SnmpV3MsgContainer::generateTooBigMsg(Snmp::SNMPv3Message& msg)
{
    Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::generateTooBigMsg() [%p]",this);
    if (!m_scopedPdu)
	return SnmpAgent::MESSAGE_DROP;
    DataBlock data = m_scopedPdu->m_data;
    Snmp::PDUs pdus;
    pdus.decode(data);
    Snmp::PDU* pdu = __plugin.getPDU(pdus);
    if (!pdu)
	return SnmpAgent::MESSAGE_DROP;
    pdu->m_error_status = Snmp::PDU::s_tooBig_error_status;
    pdu->m_error_index = 0;
    if (!pdu->m_variable_bindings)
	pdu->m_variable_bindings = new Snmp::VarBindList();
    pdu->m_variable_bindings->m_list.clear();
    data.clear();
    pdus.encode(data);
    m_scopedPdu->m_data.clear();
    m_scopedPdu->m_data.append(data);
    prepareForSend(msg);
    return SnmpAgent::SUCCESS;
}

// parse the header data from the message
int SnmpV3MsgContainer::processHeader(Snmp::SNMPv3Message& msg)
{
    Snmp::HeaderData* header = msg.m_msgGlobalData;
    if (!header) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::processHeader() - no header [%p]",this);
	return SnmpAgent::MESSAGE_DROP;
    }
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::processHeader('%p')",header);
    // * msgId
    m_msgId = header->m_msgID;
    // * msgMaxSize
    m_msgMaxSize = header->m_msgMaxSize;
    // * msgFlags
    u_int8_t msgFlags = (u_int8_t)(header->m_msgFlags.length() == 1 ? header->m_msgFlags[0] : 0);

    // get the message flags
    m_reportFlag = ((msgFlags &  REPORT_FLAG) == 0x0 ? false : true);
    m_privFlag = ((msgFlags & PRIVACY_FLAG) == 0x0 ? false : true);
    m_authFlag = ((msgFlags & AUTH_FLAG) == 0x0 ? false : true);
    // * msgSecurityModel
    m_securityModel = header->m_msgSecurityModel;
    if (m_securityModel != USM_SEC_MODEL) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::processHeader() [%p] - invalid security model=%d",this,m_securityModel);
	return SnmpAgent::MESSAGE_DROP;
    }
    DDebug(&__plugin,DebugInfo,"SnmpV3MsgContainer::processHeader() found msgID = %d, m_msgMaxSize = %d, "
	  "reportFlag = %s, privFlag = %s, authFlag = %s",m_msgId,m_msgMaxSize,String::boolText(m_reportFlag),
	  String::boolText(m_privFlag),String::boolText(m_authFlag));

    return SnmpAgent::SUCCESS;
}

// parse and handle the security data
int SnmpV3MsgContainer::processSecurityModel(Snmp::SNMPv3Message& msg)
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::processSecurityModel() [%p]",this);

    int r = m_security.decode(msg.m_msgSecurityParameters);
    if (r < 0)
	return SnmpAgent::MESSAGE_DROP;

    // extract from the message the engineID, engineBoots/Time and the username
    OctetString authEngineId = m_security.m_msgAuthoritativeEngineID;
    m_msgEngineBoots = m_security.m_msgAuthoritativeEngineBoots;
    m_msgEngineTime = m_security.m_msgAuthoritativeEngineTime;

    m_user = __plugin.getUser(m_security.m_msgUserName.getString());

    DDebug(&__plugin,DebugInfo,"SnmpV3MsgContainer::processSecurityModel found authEngineId = '%s', engineBoots = '%d', "
	"engineTime = '%d', username = '%s'", authEngineId.toHexString().c_str(),
	    m_msgEngineBoots,m_msgEngineTime,(m_user ? m_user->toString().c_str() : ""));

    // check the engine data and if it doesn't match set the correct data and return a wrong engine id status
    if (authEngineId.toHexString() != __plugin.getEngineID().toHexString())
	return  SnmpAgent::WRONG_ENGINE_ID;

    int res = checkTimeWindow();
    if (res != SnmpAgent::SUCCESS)
	return res;
    return SnmpAgent::SUCCESS;
}

// verify the message time against the engine time and the time window
int SnmpV3MsgContainer::checkTimeWindow()
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::checkTimeWindow() [%p]",this);
    u_int32_t engineBoots = __plugin.getEngineBoots();
    if (engineBoots == ENGINE_BOOTS_MAX || engineBoots != m_msgEngineBoots)
	return SnmpAgent::WRONG_WINDOW_TIME;

    int32_t engineTime = (int32_t)__plugin.getEngineTime();
    if (((engineTime - TIMEFRAME_VAL) > (int32_t)m_msgEngineTime) || ((engineTime + TIMEFRAME_VAL) < (int32_t)m_msgEngineTime))
	return SnmpAgent::WRONG_WINDOW_TIME;
    return SnmpAgent::SUCCESS;
}

// check the user data provided in the message against locally stored data
int SnmpV3MsgContainer::checkUser()
{
    if (!m_user) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::checkUser() - Unknown user name [%p]",this);
	return SnmpAgent::WRONG_USER;
    }
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::checkUser('%s') [%p]",m_user->toString().c_str(),this);

    if (m_authFlag != m_user->needsAuth()) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::checkUser() [%p] - Unsupported security level 'auth' for user %s",
		this,m_user->toString().c_str());
	return  SnmpAgent::WRONG_SEC_LEVEL;
    }

    if (m_privFlag != m_user->needsPriv()) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::checkUser() [%p] - Unsupported security level 'priv' for user %s",
		this,m_user->toString().c_str());
	return  SnmpAgent::WRONG_SEC_LEVEL;
    }
    return SnmpAgent::SUCCESS;
}

// handle the pdu from the SNMPv3 message
int SnmpV3MsgContainer::processScopedPdu()
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::processScopedPdu(scopedPdu=%p) [%p]",m_scopedPdu,this);
    if (!(m_scopedPdu && m_user))
	return SnmpAgent::MESSAGE_DROP;

    Snmp::PDUs pdus;
    pdus.decode(m_scopedPdu->m_data);
    int type = pdus.m_choiceType;

    Snmp::PDU* decodedPdu = 0;
    if (type != Snmp::PDUs::GET_BULK_REQUEST) {
	decodedPdu = __plugin.getPDU(pdus);
	__plugin.decodePDU(type,decodedPdu,m_user->accessLevel());
	pdus.m_choiceType = type;
    }
    else {
	Snmp::BulkPDU* bulkReq = pdus.m_get_bulk_request->m_GetBulkRequest_PDU;
	if (bulkReq) {
	    // handle bulk request
	    decodedPdu = __plugin.decodeBulkPDU(type,bulkReq,m_user->accessLevel());
	    type = pdus.m_choiceType = Snmp::PDUs::RESPONSE;
	}
    }
    if (type == Snmp::PDUs::RESPONSE) {
	TelEngine::destruct(pdus.m_response->m_Response_PDU);
	pdus.m_response->m_Response_PDU = (decodedPdu ? decodedPdu : new Snmp::PDU());
    }

    m_scopedPdu->m_data.clear();
    pdus.encode(m_scopedPdu->m_data);
    return SnmpAgent::SUCCESS;
}

// check the digest received in the message
int SnmpV3MsgContainer::checkAuth(Snmp::SNMPv3Message& msg)
{
    if (!m_user)
	return SnmpAgent::WRONG_USER;
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::checkAuth('%s') [%p]",m_user->toString().c_str(),this);
    //put digest on zero
    OctetString authDigest = m_security.m_msgAuthenticationParameters;
    m_security.m_msgAuthenticationParameters = s_zeroKey;
    OctetString digest;
    msgDigest(msg,digest);

    if (digest.toHexString() != authDigest.toHexString()) {
	Debug(&__plugin,DebugInfo,"SnmpV3MsgContainer::checkAuth('%s') [%p] - wrong digest received on wire",m_user->toString().c_str(),this);
	return SnmpAgent::WRONG_DIGEST;
    }
    DDebug(&__plugin,DebugInfo,"SnmpV3MsgContainer::checkAuth('%s') [%p] - AUTH SUCCESS",m_user->toString().c_str(),this);
    return SnmpAgent::SUCCESS;
}

// compute a message digest
void SnmpV3MsgContainer::msgDigest(Snmp::SNMPv3Message& msg, OctetString& digest)
{
    digest.clear();
    if (!m_user)
	return;
    if (!m_user->needsAuth())
	return;
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::msgDigest(%s) [%p]",m_user->toString().c_str(),this);

    const DataBlock& k1 = m_user->digestK1();
    const DataBlock& k2 = m_user->digestK2();

    m_security.encode(digest);
    msg.m_msgSecurityParameters.clear();
    msg.m_msgSecurityParameters.append(digest);
    digest.clear();
    msg.encode(digest);

    //  md51 = MD5 digest on  k1 + msg, then md52 = MD5 digest on k2 + md51
    // msgDigest = md52[0..11]
    //  sha1 = SHA1 digest on  k1 + msg, then sha2 = SHA1 digest on k2 + sha1
    // msgDigest = sha2[0..11]
    if (m_user->authProto() == SnmpUser::MD5_AUTH) {
	MD5 md5;
	md5.update(k1);
	md5.update(digest);
	md5.finalize();

	digest.clear();
	digest.append((void*)md5.rawDigest(),md5.rawLength());

	md5.clear();
	md5.update(k2);
	md5.update(digest);
	md5.finalize();

	digest.clear();
	digest.append((void*)md5.rawDigest(),md5.rawLength());
	digest.truncate(12);
	DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::msgDigest()[%p]:MD5 digest is %s",this,md5.hexDigest().substr(0,24).c_str());
    }
    else if (m_user->authProto() == SnmpUser::SHA1_AUTH) {
	SHA1 sha1;
	sha1.update(k1);
	sha1.update(digest);
	sha1.finalize();

	digest.clear();
	digest.append((void*)sha1.rawDigest(),sha1.rawLength());

	sha1.clear();
	sha1.update(k2);
	sha1.update(digest);
	sha1.finalize();

	digest.clear();
	digest.append((void*)sha1.rawDigest(),sha1.rawLength());
	digest.truncate(12);
	DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::msgDigest() [%p] : SHA1 digest is %s",this,sha1.hexDigest().substr(0,24).c_str());
    }
}

// decrypt an encrypted pdu
int SnmpV3MsgContainer::decrypt(Snmp::SNMPv3Message& msg)
{
    if(msg.m_msgData && msg.m_msgData->m_choiceType != Snmp::ScopedPduData::ENCRYPTEDPDU)
	return SnmpAgent::WRONG_ENCRYPT;
    if (!msg.m_msgData || !m_user)
	return SnmpAgent::MESSAGE_DROP;
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::decrypt() [%p]",this);

    DataBlock encryptedBlock = msg.m_msgData->m_encryptedPDU;
    // get privacy key
    DataBlock privKey = m_user->privKey();
    DataBlock encryptKey = privKey;
    DataBlock initVector;

    // build the initialization vector from the key according to encryption method
    if (m_user->privProto() == SnmpUser::DES_ENCRYPT) {
	if (encryptedBlock.length() % 8 != 0 )
	    return SnmpAgent::WRONG_ENCRYPT;
	encryptKey.truncate(8);
	DataBlock preIV = privKey;
	preIV.truncate(16);
	preIV.cut(-8);
	//m_security.m_msgPrivacyParameters is the salt
	for (unsigned int i  = 0; i < m_security.m_msgPrivacyParameters.length(); i++) {
	    u_int8_t aux = preIV[i] ^ m_security.m_msgPrivacyParameters[i];
	    initVector.append(&aux,1);
	}
    }
    if (m_user->privProto() == SnmpUser::AES_ENCRYPT) {
	encryptKey.truncate(16);
 	DataBlock aux = toNetworkOrder(m_msgEngineBoots,4);
 	initVector.append(aux);
 	aux = toNetworkOrder(m_msgEngineTime,4);
 	initVector.append(aux);
  	initVector.append(m_security.m_msgPrivacyParameters);
    }

    //rfc3826
    Cipher* cipher = __plugin.getCipher(m_user->privProto());
    if (!cipher){
	Debug(&__plugin,DebugInfo,"Could not obtain %s cipher",lookup(m_user->privProto(),s_crypto,""));
	return SnmpAgent::WRONG_ENCRYPT;
    }
    // set the decrypt key
    if (!cipher->setKey(encryptKey))
	return SnmpAgent::WRONG_ENCRYPT;
    // set the initialization vector
    if (!cipher->initVector(initVector))
	return SnmpAgent::WRONG_ENCRYPT;
    // decrypt the data
    cipher->decrypt(encryptedBlock);
    // decode the pdu from the data
    m_scopedPdu = new Snmp::ScopedPDU();
    if(m_scopedPdu->decode(encryptedBlock) < 0)
	return SnmpAgent::WRONG_ENCRYPT;

    return SnmpAgent::SUCCESS;
}

// encrypt the given pdu
int SnmpV3MsgContainer::encrypt(Snmp::ScopedPDU* pdu, DataBlock& encryptedPdu)
{
    DDebug(&__plugin,DebugAll,"SnmpV3MsgContainer::encrypt() pdu=[%p] [%p]",pdu,this);
    if (!pdu ||!m_user)
	return SnmpAgent::MESSAGE_DROP;

    pdu->encode(encryptedPdu);

    // obtain the engine salt
    m_msgSalt.clear();
    u_int64_t engineSalt = __plugin.getEngineSalt();
    m_msgSalt = toNetworkOrder(engineSalt,8);

    DataBlock privKey = m_user->privKey();
    DataBlock encryptKey = privKey;
    // build the initialization vector
    DataBlock initVector;
    if (m_user->privProto() == SnmpUser::DES_ENCRYPT) {
	encryptKey.truncate(8);
	DataBlock preIV = privKey;
	preIV.truncate(16);
	preIV.cut(-8);
	for (unsigned int i  = 0; i < m_msgSalt.length(); i++) {
	    u_int8_t aux = preIV[i] ^ m_msgSalt[i];
	    initVector.append(&aux,1);
	}
	int r = encryptedPdu.length() % 8;
	if(r != 0)
	    for (int i = 0; i < 8 - r; i++)
		encryptedPdu.append(&s_zero,1);
    }
    else if (m_user->privProto() == SnmpUser::AES_ENCRYPT) {
	encryptKey.truncate(16);
	DataBlock aux = toNetworkOrder(m_msgEngineBoots,4);
	initVector.append(aux);
	aux = toNetworkOrder(m_msgEngineTime,4);
	initVector.append(aux);
	initVector.append(m_msgSalt);
    }

    Cipher* cipher = __plugin.getCipher(m_user->privProto());
    if (!cipher) {
	Debug(&__plugin,DebugInfo,"Could not obtain '%s' cipher",lookup(m_user->privProto(),s_crypto,""));
	return SnmpAgent::MESSAGE_DROP;
    }

    // set the encryption key
    if (!cipher->setKey(encryptKey))
	return SnmpAgent::MESSAGE_DROP;

    // set the initialization vector
    if (!cipher->initVector(initVector))
	return SnmpAgent::MESSAGE_DROP;

    // encrypt the data
    cipher->encrypt(encryptedPdu);
    return SnmpAgent::SUCCESS;
}

/**
  * SnmpAgent
  */
SnmpAgent::SnmpAgent()
      : Module("snmpagent","misc"),
	m_init(false), m_msgQueue(0), m_mibTree(0),
	m_engineBoots(0),m_startTime(0), m_silentDrops(0),
	m_salt(0),
	m_trapHandler(0),
	m_traps(0),
	m_trapUser(0),
	m_cipherAES(0),
	m_cipherDES(0)
{
    Output("Loaded module SNMP Agent");
}

SnmpAgent::~SnmpAgent()
{
    Output("Unloaded module SNMP Agent");
    TelEngine::destruct(m_trapHandler);
    TelEngine::destruct(m_traps);
    TelEngine::destruct(m_cipherAES);
    TelEngine::destruct(m_cipherDES);
    TelEngine::destruct(m_mibTree);
    TelEngine::destruct(m_trapUser);
}

bool SnmpAgent::unload()
{
    Debug(this,DebugAll,"::unload()");
    if (!lock(500000))
	return false;

    uninstallRelays();
    Engine::uninstall(m_trapHandler);

    if (m_traps) {
        String traps = "";
        for (ObjList* o = m_traps->skipNull(); o; o = o->skipNext()) {
            String* str = static_cast<String*>(o->get());
            traps.append(*str,",");
        }
        s_saveCfg.setValue("traps_conf","traps_disable",traps);
        s_saveCfg.save();
    }

    if (m_msgQueue)
	m_msgQueue->cancel();
    m_users.clear();
    unlock();
    while (m_msgQueue)
	Thread::idle();
    return true;
}

void SnmpAgent::setMsgQueue(SnmpMsgQueue* queue)
{
    lock();
    m_msgQueue = queue;
    unlock();
}

void SnmpAgent::initialize()
{
    Output("Initializing module SNMP Agent");

    s_cfg = Engine::configFile("ysnmpagent");
    s_cfg.load();

    // load community strings for SNMPv2 or prior
    m_roCommunity = s_cfg.getValue("snmp_v2","ro_community","");
    m_rwCommunity = s_cfg.getValue("snmp_v2","rw_community","");
    m_rcCommunity = s_cfg.getValue("snmp_v2","rc_community","");

    // load the file containing the Yate OID and initialize the tree
    String treeConf = s_cfg.getValue("general","mibs");
    if (treeConf.null())
	treeConf << Engine::sharedPath() << Engine::pathSeparator() <<
	    "data" << Engine::pathSeparator() << "snmp_mib.conf";
    // in case of reinitialization, first destroy the previously allocated object
    TelEngine::destruct(m_mibTree);
    m_mibTree = new AsnMibTree(treeConf);

    // get information needed for the computation of the agents' engine id (SNMPv3)
    int engineFormat = s_cfg.getIntValue("snmp_v3","engine_format",TEXT);
    const char* defaultInfo = (TEXT == engineFormat) ? Engine::nodeName().c_str() : 0;
    String engineInfo = s_cfg.getValue("snmp_v3","engine_info",defaultInfo);
    m_engineId = genEngineId(engineFormat,engineInfo);

    // read configuration for traps
    s_enabledTraps = s_cfg.getBoolValue("traps","enable_traps",true);
    String remoteIP = s_cfg.getValue("traps","remote_ip","localhost");
    String remotePort = s_cfg.getValue("traps","remote_port","162");
    s_remote = buildDestination(remoteIP,remotePort);
    if (!s_remote.valid())
	s_enabledTraps = false;
    // initiate the user for sending SNMPv3 traps
    String trapUser = s_cfg.getValue("traps","trap_user","");
    TelEngine::destruct(m_trapUser);
    if (!TelEngine::null(trapUser) && s_cfg.getSection(trapUser))
	m_trapUser = new SnmpUser(s_cfg.getSection(trapUser));

    for (unsigned int i = 0; i < s_cfg.sections(); i++) {
	NamedList* sec = s_cfg.getSection(i);
	if (!sec || (*sec == "general") || (*sec == "snmp_v2") || (*sec == "snmp_v3")
		    || (*sec == "traps") || (*sec == s_cfg.getValue("traps","trap_user","")))
	    continue;
	m_users.append(new SnmpUser(sec));
    }

    // reported version
    String ver = s_cfg.getValue("general","version","${version}");
    Engine::runParams().replaceParams(ver);
    s_yateVersion = ver;

    // load saved data
    s_saveCfg = Engine::configFile("snmp_data");
    s_saveCfg.load();

    // read last used snmpEngineID
    String snmpEngineId = s_saveCfg.getValue("snmp_v3","engine_id","");
    if (snmpEngineId == m_engineId.toHexString()) {
	// the snmpEngineID hasn't been modified so snmpEngineBoots must be increased
	// if there is no engineBoots value saved, it must be set to ENGINE_BOOTS_MAX
	m_engineBoots = s_saveCfg.getIntValue("snmp_v3","engine_boots",ENGINE_BOOTS_MAX);
	if (m_engineBoots == ENGINE_BOOTS_MAX)
	    Alarm(this,"config",DebugWarn,"snmpEngineBoots reached maximum value, snmpEngineID must be reconfigured");
	else
	    m_engineBoots++;
	s_saveCfg.setValue("snmp_v3","engine_boots",(int)m_engineBoots);
    }
    else {
	// reset snmpEngineBoots if snmpEngineID has changed. Save the new value of snmpEngineID
	Debug(this,DebugInfo,"snmpEngineID has been reconfigured, resetting snmpEngineBoots");
	s_saveCfg.setValue("snmp_v3","engine_id",m_engineId.toHexString());
	m_engineBoots = 1;
	s_saveCfg.setValue("snmp_v3","engine_boots",(int)m_engineBoots);
    }
    s_saveCfg.save();

    // load disabled traps
    String traps = s_saveCfg.getValue("traps_conf","traps_disabled","");
    TelEngine::destruct(m_traps);
    m_traps = traps.split(',',false);

    //	USM inits
    // initialize all counters for USM stats
    for (int i = 0; i < 7; i++)
	m_stats[i] = 0;

    // init engine start time
    m_startTime = Time::secNow();
    // init encryption salt
    m_salt = 0;
    m_salt += m_engineBoots;
    m_salt <<= 32;
    m_salt += m_startTime;

    m_silentDrops = 0;
    m_lastRecvHost.clear();

    AsnMib* yateMib = (m_mibTree ? m_mibTree->find("yate") : 0);
    if (yateMib)
	s_yateRoot = yateMib->toString();

    // port on which to listen for SNMP requests
    int snmpPort = s_cfg.getIntValue("general","port",161);
    const char* snmpAddr = s_cfg.getValue("general","addr");
    if (!snmpAddr)
	snmpAddr = "0.0.0.0";
    // thread priority
    Thread::Priority threadPrio = Thread::priority(s_cfg.getValue("general","thread"));

    // do module init, install message handlers
    if (!m_init) {
	m_init = true;
	setup();
	installRelay(Halt);
	m_msgQueue = new SnmpMsgQueue(this,threadPrio,snmpAddr,snmpPort);
	if (!m_msgQueue->init()) {
	    delete m_msgQueue;
	    m_msgQueue = 0;
	}
	if (m_trapHandler)
	    return;
	m_trapHandler = new TrapHandler();
	Engine::install(m_trapHandler);
    }
}

bool SnmpAgent::received(Message& msg, int id)
{
    if (id == Halt) {
	// save and cleanup
	DDebug(&__plugin,DebugInfo,"::received() - Halt Message");
	unload();
    }
    return Module::received(msg,id);
}


int SnmpAgent::processMsg(SnmpMessage* msg)
{
    DDebug(&__plugin,DebugAll,"::processMsg([%p])",msg);
    if(!msg)
	return MESSAGE_DROP;
    DataBlock data = msg->data();
    const String& host = msg->peer().host();

    // determine the version of the SNMP message
    Snmp::Message msgSnmp;
    int l = msgSnmp.decode(data);
    if (l > 0) {
	// SNMPv2 message
	DDebug(&__plugin,DebugAll,"::processMsg() - received %s message msg=%p",lookup(msgSnmp.m_version,s_proto,""),&msgSnmp);
	// try to handle it
	int res = processSnmpV2Msg(msgSnmp,host);
	if (res < 0) {
	    if (res == WRONG_COMMUNITY)
		authFail(msg->peer(),msgSnmp.m_version,res);
	    m_silentDrops++;
	    return MESSAGE_DROP;
	}
	data.clear();
	// encode response in case of successful handling into data
	msgSnmp.encode(data);
    }
    else {
	data = msg->data();
	Snmp::SNMPv3Message m;
	l = m.decode(data);
	if (l >= 0) {
	    // SNMPv3 message
	    DDebug(&__plugin,DebugAll,"::processMsg() - received SNMPv3 message msg=%p",&m);
	    int authRes = SUCCESS;
	    int res = processSnmpV3Msg(m,host,authRes);
	    if (authRes != SUCCESS)
		authFail(msg->peer(),m.m_msgVersion,authRes);
	    if(res < 0) {
		m_silentDrops++;
		return MESSAGE_DROP;
	    }
	    data.clear();
	    // encode response in case of successful handling into data
	    m.encode(data);
	}
	else {
	    Debug(&__plugin,DebugNote,"Unknown SNMP protocol version from %s",host.c_str());
	    return MESSAGE_DROP;
	}
    }
    if (host != m_lastRecvHost) {
	m_lastRecvHost = host;
	Debug(&__plugin,DebugNote,"SNMP client connected from %s",host.c_str());
    }
#ifdef DEBUG
    else
	Debug(&__plugin,DebugAll,"::processMsg([%p]) - successful",msg);
#endif
    // set the data for the message wrapper
    msg->setData(data);
    // send it and return with success
    if (m_msgQueue)
	m_msgQueue->sendMsg(msg);

    return SUCCESS;
}

// process a SNMPv2 message
int SnmpAgent::processSnmpV2Msg(Snmp::Message& msg, const String& host)
{
    DDebug(&__plugin,DebugAll,"::processSnmpV2Msg() [%p]",&msg);
    // verify community string
    String community = msg.m_community.getString();
    int access = AsnMib::notAccessible;
    if (!m_rcCommunity.null() && m_rcCommunity == community)
	access = AsnMib::readCreate;
    else if (!m_rwCommunity.null() && m_rwCommunity == community)
	access = AsnMib::readWrite;
    else if (!m_roCommunity.null() && m_roCommunity == community)
	access = AsnMib::readOnly;
    if (access == AsnMib::notAccessible) {
    	Debug(&__plugin,DebugInfo,"Dropping message from %s with wrong community '%s'",
	    host.c_str(),community.safe());
	return WRONG_COMMUNITY;
    }
    // obtain pdus and do decoding
    DataBlock pdu = msg.m_data;
    if (pdu.length() > 0) {
	Snmp::PDUs chosen;
	int l = chosen.decode(pdu);
	if (l < 0)
	    return MESSAGE_DROP;
	Snmp::BulkPDU* bulkReq = 0;
	Snmp::PDU* req = getPDU(chosen);
	Snmp::PDU* tmp = chosen.m_response->m_Response_PDU;
	if (req) {
	    // handle received pdu according to type
	    decodePDU(chosen.m_choiceType,req,access);
	    if (chosen.m_choiceType == Snmp::PDUs::RESPONSE) {
		if (req != tmp)
		    TelEngine::destruct(tmp);
		chosen.m_response->m_Response_PDU = (req ? req : new Snmp::PDU());
	    }
	}
	else if (chosen.m_choiceType == Snmp::PDUs::GET_BULK_REQUEST) {
	    bulkReq = chosen.m_get_bulk_request->m_GetBulkRequest_PDU;
	    if (bulkReq) {
		// handle bulk request
		Snmp::PDU* responsePDU = decodeBulkPDU(chosen.m_choiceType,bulkReq,access);
		if (tmp != responsePDU)
		    TelEngine::destruct(tmp);
		chosen.m_response->m_Response_PDU = (responsePDU ? responsePDU : new Snmp::PDU());
		chosen.m_choiceType = Snmp::PDUs::RESPONSE;
	    }
	}
	// encode the result and set it in the message wrapper
	msg.m_data.clear();
	chosen.encode(msg.m_data);
    }
    DDebug(&__plugin,DebugAll,"::processSnmpV2Msg() [%p] - successful",&msg);
    return SUCCESS;
}

// handle a request pdu, generate response
void SnmpAgent::decodePDU(int& reqType, Snmp::PDU* obj, const int& access)
{
    DDebug(&__plugin,DebugAll,"::decodePDU([%p]) - pdu type is %s",obj,lookup(reqType,s_pdus,""));
    if (!obj) {
	Debug(&__plugin,DebugMild,"No SNMP PDU to decode");
	return;
    }

    // obtain list of requested OIDs
    Snmp::VarBindList* list = obj->m_variable_bindings;
    if (!list)
	return;

    for (unsigned int i = 0; i < list->m_list.count(); i++) {
	Snmp::VarBind* obji = static_cast<Snmp::VarBind*>(list->m_list[i]);
	if (obji) {
	    int res = 0;
	    AsnValue val;
	    // for each OID requested, handle the request accordingly
	    switch (reqType) {
		case Snmp::PDUs::GET_REQUEST:
		    res = processGetReq(obji,&val,obj->m_error_status,access);
		    break;
		case Snmp::PDUs::GET_NEXT_REQUEST:
		    res = processGetNextReq(obji,&val,obj->m_error_status,access);
		    break;
		case Snmp::PDUs::SET_REQUEST:
		    res = processSetReq(obji,obj->m_error_status,access);
		    break;
		case Snmp::PDUs::SNMPV2_TRAP:
		case Snmp::PDUs::INFORM_REQUEST:
		case Snmp::PDUs::REPORT:
		default:
		  break;
	    }
	    // if the request was handled, but an error was returned, set the error in the response
	    if (res == 1 && obj->m_error_status) {
		obj->m_error_index = i + 1;
		obj->m_variable_bindings = list;
		break;
	    }
	    // if the request was not handled, set a generic error
	    if (!res) {
		obj->m_error_status =  Snmp::PDU::s_genErr_error_status;
		obj->m_error_index = i + 1;
		break;
	    }
	    assignValue(obji,&val);
	}
    }
    reqType = Snmp::PDUs::RESPONSE;
}

// handle a GetRequest for a single variable binding
int SnmpAgent::processGetReq(Snmp::VarBind* varBind, AsnValue* value,  int& error, const int& access)
{
    DDebug(&__plugin,DebugInfo,"::processGetRequest() - varBind [%p], value [%p]",varBind,value);
    if (!(varBind && value && m_mibTree))
	return 0;
    // obtain the OID
    Snmp::ObjectName* objName = varBind->m_name;
    if (!objName)
	return 0;

    ASNObjId oid = objName->m_ObjectName;

    // try to find the OID in the tree, if not found set the appropiate error and return
    AsnMib* mib = m_mibTree->find(oid);
    if (!mib) {
	varBind->m_choiceType = Snmp::VarBind::NOSUCHOBJECT;
	return 1;
    }

    // get the access level of the requested oid, if it doesn't match the access level of the request, set error and return
    if (mib->getAccessValue() < access) {
	varBind->m_choiceType = Snmp::VarBind::NOSUCHOBJECT;
	return 1;
    }
    DDebug(&__plugin,DebugInfo,"::processGetRequest() - varBind [%p], value [%p], oid %s",varBind,value,oid.toString().c_str());
    unsigned int index = mib->index();
    objName->m_ObjectName = mib->getOID();
    mib->setIndex(0);
    // obtain the string equivalent of the OID (the name of the oid)
    String askFor = mib->getName();
    if (TelEngine::null(askFor)) {
	varBind->m_choiceType = Snmp::VarBind::NOSUCHOBJECT;
	return 1;
    }
    // try to get its value
    *value = makeQuery(askFor,index,mib);
    String typeStr = mib->getType();
    // get the type of the OID's value (integer,string,OID?) and set it
    int type = lookup(typeStr,s_types,0);
    if (type != 0)
	value->setType(type);
    // if the is no value for the requested OID, return with error set
    if (!value || value->getValue().null()) {
 	varBind->m_choiceType = Snmp::VarBind::NOSUCHINSTANCE;
 	return 1;
    }
    return 1;
}

// handle a GetNextRequest
int SnmpAgent::processGetNextReq(Snmp::VarBind* varBind, AsnValue* value,  int& error, const int& access)
{
    DDebug(&__plugin,DebugInfo,"::processGetNextRequest() - varBind [%p], value [%p]",varBind,value);
    if (!(varBind && value && m_mibTree))
	return 0;
    // obtain the OID in the request
    Snmp::ObjectName* objName = varBind->m_name;
    if (!objName)
	return 0;

    ASNObjId oid = objName->m_ObjectName;
    AsnMib *next = 0, *aux = 0;

    // obtain the value for the next oid
    next = m_mibTree->find(oid);
    if (next && !next->getName().null()) {
	String name = next->getName();
	unsigned int idx = next->index();
	if (!idx) {
	    *value = makeQuery(name,idx,next);
	    int type = lookup(next->getType(),s_types,0);
	    if (type != 0)
		value->setType(type);
	    if (!value || value->getValue().null())
		next->setIndex(idx + 1);
	    else
		next = m_mibTree->findNext(oid);
	}
	else
	    next->setIndex(idx + 1);
    }
    else
	next = m_mibTree->findNext(oid);

    if (!next) {
        varBind->m_choiceType = Snmp::VarBind::ENDOFMIBVIEW;
	return 1;
    }
    unsigned int index = next->index();

    // obtain the value for the next oid
    while (next) {
        aux = next;
        String askFor = next->getName();
        if (TelEngine::null(askFor)) {
	    varBind->m_choiceType = Snmp::VarBind::NOSUCHINSTANCE;
	    next->setIndex(0);
	    return 1;
        }
        *value = makeQuery(askFor,index,next);
        int type = lookup(next->getType(),s_types,0);
        if (type != 0)
	    value->setType(type);
        if (!value || value->getValue().null()) {
	    if (!index) {
	        index++;
	        continue;
	    }
	    else {
	        oid = next->getOID();
	        next = m_mibTree->findNext(oid);
	        index = 0;
	        aux->setIndex(0);
	        aux = next;
	        continue;
	    }
        }
        else {
	    next->setIndex(index);
	    objName->m_ObjectName = next->getOID();
	    next->setIndex(0);
	    return 1;
        }
    }
    // no OID with a value was found, set end of mib view
    varBind->m_choiceType = Snmp::VarBind::ENDOFMIBVIEW;
    return 1;
}

// process a SetRequest
int SnmpAgent::processSetReq(Snmp::VarBind* varBind, int& error, const int& access)
{
    // !NOTE : Yate will not allow setting values. Besides the enableTrap and disableTrap, it will always return with error
    DDebug(&__plugin,DebugInfo,"::setRequest() - varBind [%p] userAccess %d",varBind,access);
    if (!(varBind && m_mibTree))
	return 0;
    Snmp::ObjectName* objName = varBind->m_name;
    if (!objName)
	return 0;
    if (access < AsnMib::readWrite) {
	error = Snmp::PDU::s_noAccess_error_status;
	return 1;
    }

    ASNObjId oid = objName->m_ObjectName;
    AsnMib* mib = m_mibTree->find(oid);
    if (!mib) {
	error = Snmp::PDU::s_noAccess_error_status;
	return 1;
    }

    // check access level
    int oidAccess = mib->getAccessValue();
    switch (oidAccess) {
	case AsnMib::notAccessible:
	case AsnMib::accessibleForNotify:
	    error = Snmp::PDU::s_noAccess_error_status;
	    return 1;
	    break;
	case AsnMib::readOnly:
	    error = Snmp::PDU::s_notWritable_error_status;
	    return 1;
	    break;
	case AsnMib::readWrite:
	case AsnMib::readCreate:
	default:
	    break;
    }

    // set value
    String name = mib->getName();
    setValue(name,varBind,error);
    return 1;
}

// set a value for YATE
void SnmpAgent::setValue(const String& varName, Snmp::VarBind* val, int& error)
{
    DDebug(this,DebugAll,"::setValue('%s', [%p])",varName.c_str(),val);
    if (!val) {
	error = Snmp::PDU::s_wrongType_error_status;
	return;
    }
    if (!m_mibTree) {
	error = Snmp::PDU::s_noCreation_error_status;
	return;
    }
    // only if the variable asked to be set are these
    if (varName == "enableTrap" || varName == "disableTrap") {
	DataBlock data = getVal(val);
	if (!data.length())
	    return;
	String valStr((const char*)data.data(),data.length());
	ASNObjId oid = valStr;
	AsnMib* mib = m_mibTree->find(oid);
	if (!mib || (mib && mib->getAccessValue() < AsnMib::accessibleForNotify)) {
	    Debug(this,DebugInfo,"::setValue('%s', [%p]), given oid value not found",varName.c_str(),val);
	    error = Snmp::PDU::s_noCreation_error_status;
	    return;
	}
	if (m_traps) {
	    if (varName == "enableTrap")
		m_traps->remove(mib->getName());
	    else if (varName == "disableTrap") {
		if (!m_traps->find(mib->getName()))
		    m_traps->append(new String(mib->getName()));
	    }
	}
    }
    else
	error = Snmp::PDU::s_notWritable_error_status;

}

// handle a GetBulkRequest
Snmp::PDU* SnmpAgent::decodeBulkPDU(int& reqType, Snmp::BulkPDU* pdu, const int& access)
{
    DDebug(&__plugin,DebugInfo,"::decodeBulkPDU() pdu [%p]", pdu);
    if (!(pdu && m_mibTree)) {
	Debug(&__plugin,DebugMild,"::decodeBulkPDU() : no pdu to decode");
	return 0;
    }

    int nonRepeaters = pdu->m_non_repeaters;
    int maxRepetitions = pdu->m_max_repetitions;
    Snmp::VarBindList* list = pdu->m_variable_bindings;
    if (!list)
	return 0;
    DDebug(&__plugin,DebugInfo,"decodeBulkPDU : PDU [%p] list has size %d, non-Repeaters %d, max-Repetitions %d",
	pdu,list->m_list.count(),nonRepeaters,maxRepetitions);

    Snmp::PDU* retPdu = new Snmp::PDU();
    retPdu->m_request_id = pdu->m_request_id;
    retPdu->m_error_status = Snmp::PDU::s_noError_error_status;
    retPdu->m_error_index = 0;

    int i = 0;
    int error = 0;
    AsnValue val;

    // handle non-repeaters
    ObjList* o = list->m_list.skipNull();
    for (; o; o = o->skipNext()) {
	if (i >= nonRepeaters)
	    break;
	Snmp::VarBind* var = static_cast<Snmp::VarBind*>(o->get());
	if (var) {
	    Snmp::VarBind* newVar = new Snmp::VarBind();
	    newVar->m_choiceType = Snmp::VarBind::VALUE;
	    newVar->m_name->m_ObjectName = var->m_name->m_ObjectName;
	    int res = processGetNextReq(newVar,&val,error,access);
	    if (res == 1 && error) {
		retPdu->m_error_index = i + 1;
		retPdu->m_error_status = error;
		break;
	    }
	    if (!res) {
		retPdu->m_error_status = Snmp::PDU::s_genErr_error_status;
		retPdu->m_error_index = i + 1;
		break;
	    }
	    if (newVar->m_choiceType == Snmp::VarBind::VALUE)
		assignValue(newVar,&val);
	    retPdu->m_variable_bindings->m_list.append(newVar);
	    i++;
	}
	if (retPdu->m_error_status)
	    break;
    }
    // handle repeaters
    int j = 0;
    bool endOfView = false;
    while (j < maxRepetitions) {
	int k = i;
	for (ObjList* l = o->skipNull(); l; l = l->skipNext()) {
	    Snmp::VarBind* var = static_cast<Snmp::VarBind*>(l->get());
	    k++;
	    if (var) {
		Snmp::VarBind* newVar = new Snmp::VarBind();
		newVar->m_choiceType = Snmp::VarBind::VALUE;
		newVar->m_name->m_ObjectName = var->m_name->m_ObjectName;
		int res = processGetNextReq(newVar,&val,error,access);
		if (res == 1 && error) {
		    retPdu->m_error_index = k;
		    retPdu->m_error_status = error;
		    break;
		}
		if (!res) {
		    retPdu->m_error_status =  Snmp::PDU::s_genErr_error_status;
		    retPdu->m_error_index = k;
		    break;
		}
		if (newVar->m_choiceType == Snmp::VarBind::VALUE)
		    assignValue(newVar,&val);
		retPdu->m_variable_bindings->m_list.append(newVar);
		var->m_name->m_ObjectName = newVar->m_name->m_ObjectName;
		l->set(var,false);
		if (newVar->m_choiceType == Snmp::VarBind::ENDOFMIBVIEW)
		    endOfView = true;
	    }
	}
	if (retPdu->m_error_status)
	    break;
	if (endOfView)
	    break;
	j++;
    }
    return retPdu;
}

// set the value for a variable binding
void SnmpAgent::assignValue(Snmp::VarBind* varBind, AsnValue* val)
{
    if (!varBind || !(val && val->getValue()))
	return;
    XDebug(&__plugin,DebugAll,"::assignValue([%p], [%p]) - data:%s, type:%d = %s",
	    varBind,val,val->getValue().c_str(),val->type(),lookup(val->type(),s_types,""));

    // set the type of the varbind and assign a value object for it
    varBind->m_choiceType = Snmp::VarBind::VALUE;

    if (!varBind->m_value)
	varBind->m_value = new Snmp::ObjectSyntax();
    Snmp::ObjectSyntax* objSyn = varBind->m_value;

    if (!objSyn->m_simple)
	objSyn->m_simple = new Snmp::SimpleSyntax();
    Snmp::SimpleSyntax* simple = objSyn->m_simple;

    if (!objSyn->m_application_wide)
	objSyn->m_application_wide = new Snmp::ApplicationSyntax();
    Snmp::ApplicationSyntax* app = objSyn->m_application_wide;
    // assign value according to type
    switch (val->type()) {
	case AsnValue::INTEGER:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::SIMPLE;
	    objSyn->m_simple = simple;
	    simple->m_choiceType = Snmp::SimpleSyntax::INTEGER_VALUE;
	    simple->m_integer_value =(int32_t)val->getValue().toInteger();
	    break;
	case AsnValue::STRING:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::SIMPLE;
	    objSyn->m_simple = simple;
	    simple->m_choiceType = Snmp::SimpleSyntax::STRING_VALUE;
	    simple->m_string_value = val->getValue();
	    break;
	case AsnValue::OBJECT_ID:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::SIMPLE;
	    objSyn->m_simple = simple;
	    simple->m_choiceType = Snmp::SimpleSyntax::OBJECTID_VALUE;
	    simple->m_objectID_value = (const char*) val->getValue().c_str();
	    break;
	case AsnValue::IPADDRESS:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::IPADDRESS_VALUE;
	    app->m_ipAddress_value->m_IpAddress = String(val->getValue());
	    break;
	case AsnValue::COUNTER:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::COUNTER_VALUE;
	    app->m_counter_value->m_Counter32 = (u_int32_t)val->getValue().toInteger();
	    break;
	case AsnValue::TIMETICKS:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::TIMETICKS_VALUE;
	    app->m_timeticks_value-> m_TimeTicks = (u_int32_t)val->getValue().toInteger();
	    break;
	case AsnValue::ARBITRARY:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::ARBITRARY_VALUE;
	    app->m_arbitrary_value->m_Opaque =  val->getValue();
	    break;
	case AsnValue::BIG_COUNTER:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::BIG_COUNTER_VALUE;
	    app->m_big_counter_value->m_Counter64 = (uint64_t)val->getValue().toInt64();
	    break;
	case AsnValue::UNSIGNED_INTEGER:
	    objSyn->m_choiceType = Snmp::ObjectSyntax::APPLICATION_WIDE;
	    objSyn->m_application_wide = app;
	    app->m_choiceType = Snmp::ApplicationSyntax::UNSIGNED_INTEGER_VALUE;
	    app->m_unsigned_integer_value->m_Unsigned32 = (u_int32_t)val->getValue().toInteger();
	    break;
	default:
	    Debug(&__plugin, DebugNote, "unknown value");
    }
}

/**
  * Function for handling a v3 SNMP message
  * Message structure
  *	msgVersion = 3
  *	msgGlobalData
  *		- msgId
  *		- msgMaxSize
  *		- msgFlags ( report, auth, priv)
  *		- msgSecurityModel
  * 	msgSecurityParameters - ! NOTE it's a string of octets which should be decoded according to the security model
  *	msgData
  * 	    - plain text
  *		- contextEngineID
  *		- contextName
  *		- data - PDU
  *	    or
  *	    - encrypted Data - string to be decrypted according the encryption methos (DES-CBC / AES-CFB)
  */
int SnmpAgent::processSnmpV3Msg(Snmp::SNMPv3Message& msg, const String& host, int& authRes)
{
    DDebug(&__plugin,DebugAll,"::processSnmpV3Msg() [%p]",&msg);
    // initialize a SNMPv3 container
    SnmpV3MsgContainer msgContainer;
    // message is valid?
    int secRes = msgContainer.validate(msg,authRes);
    if (secRes == MESSAGE_DROP) {
 	Debug(&__plugin,DebugNote, "SNMPv3 message from %s not validated, silent drop",host.c_str());
	return MESSAGE_DROP;
    }

    // if a error was found validating the message and the reportableFlag is set, generate a ReportPDU
    if (secRes) {
	if (msgContainer.reportable()) {
	    if (generateReport(msg,secRes,msgContainer) < 0)
		return MESSAGE_DROP;
	    return secRes;
	}
	else {
 	    Debug(&__plugin,DebugNote,"Error during SNMPv3 message from %s processing, further processing aborted",host.c_str());
	    return MESSAGE_DROP;
	}
    }
    // generate a ResponsePDU otherwise
    if (generateResponse(msg,msgContainer) == MESSAGE_DROP)
	return MESSAGE_DROP;
    return SUCCESS;
}

// build a ReportPDU
int SnmpAgent::generateReport(Snmp::SNMPv3Message& msg, const int& secRes, SnmpV3MsgContainer& cont)
{
    DDebug(&__plugin,DebugInfo,"::generateReport() - %s",lookup(secRes,s_errors,"unknown cause"));
    if (!m_mibTree)
	return MESSAGE_DROP;
    if (!msg.m_msgGlobalData)
  	return MESSAGE_DROP;
    // reset the message flags
    cont.setReportFlag(false);
    cont.setPrivFlag(false);
    if (secRes == WRONG_DIGEST || secRes == WRONG_USER)
	cont.setAuthFlag(false);

    // extract from the ScopedPDU the PDU
    if (!msg.m_msgData)
	msg.m_msgData = new Snmp::ScopedPduData();
    Snmp::ScopedPduData* data = msg.m_msgData;
    int choice = data->m_choiceType;
    data->m_choiceType = Snmp::ScopedPduData::PLAINTEXT;
    Snmp::ScopedPDU* pdu = 0;
    Snmp::PDUs p;
    TelEngine::destruct(p.m_report->m_Report_PDU);
    if (choice == Snmp::ScopedPduData::PLAINTEXT && data->m_plaintext) {
	pdu = data->m_plaintext;
	p.decode(pdu->m_data);
	p.m_report->m_Report_PDU = getPDU(p);
    }
    if (!pdu) {
	pdu = new Snmp::ScopedPDU();
	p.m_report->m_Report_PDU = new Snmp::PDU();
    }

    pdu->m_contextEngineID = m_engineId;
    pdu->m_contextName = pdu->m_contextName;
    // set the PDUs type
    p.m_choiceType = Snmp::PDUs::REPORT;
    // the error information
    p.m_report->m_Report_PDU->m_error_status = Snmp::PDU::s_noError_error_status;
    p.m_report->m_Report_PDU->m_error_index = 0;
    // clear the VarBindList
    p.m_report->m_Report_PDU->m_variable_bindings->m_list.clear();
    Snmp::VarBind* var = new Snmp::VarBind();

    AsnValue val;
    AsnMib* mib = 0;

    // look up the cause for non validating the message
    String stat = lookup(secRes,s_stats,"");
    if (!stat.null()) {
	// find its OID
	mib = m_mibTree->find(stat);
	// increase the counter for USM stats
	m_stats[secRes]++;
	// set the value
	if (mib) {
	    var->m_name->m_ObjectName = mib->getOID();
	    val.setValue(String(m_stats[secRes]));
	    val.setType(AsnValue::COUNTER);
	}
    }

    if (val.getValue().length() == 0)
      return MESSAGE_DROP;

    // set the value in the ReportPDU
    assignValue(var,&val);
    p.m_report->m_Report_PDU->m_variable_bindings->m_list.append(var);

    // build the message
    pdu->m_data.clear();
    p.encode(pdu->m_data);
    if (!msg.m_msgData)
          return MESSAGE_DROP;
    msg.m_msgData->m_plaintext = pdu;
    cont.prepareForSend(msg);
    return SUCCESS;
}

// build a ResponsePDU
int SnmpAgent::generateResponse(Snmp::SNMPv3Message& msg, SnmpV3MsgContainer& msgContainer)
{
    DDebug(&__plugin,DebugAll,"::generateResponse() for msg=%p",&msg);
    // process the request (Get/GetNext/SetRequest)
    msgContainer.processRequest(msg);

    // set the security parameters for the ResponsePDU
    msgContainer.prepareForSend(msg);

    // encode the message
    DataBlock ret;
    msg.encode(ret);
    // if it passes the maximum length for a SNMP message return a tooBig message
    if ((int)ret.length() > msgContainer.msgMaxSize())
	return msgContainer.generateTooBigMsg(msg);
    return SUCCESS;
}

// get a PDU
Snmp::PDU* SnmpAgent::getPDU(Snmp::PDUs& p)
{
    Snmp::PDU* pdu = 0;
    switch (p.m_choiceType) {
	case Snmp::PDUs::GET_REQUEST:
	    pdu = p.m_get_request->m_GetRequest_PDU;
	    p.m_get_request->m_GetRequest_PDU = 0;
	    return pdu;
	case Snmp::PDUs::GET_NEXT_REQUEST:
	    pdu = p.m_get_next_request->m_GetNextRequest_PDU;
	    p.m_get_next_request->m_GetNextRequest_PDU = 0;
	    return pdu;
	case Snmp::PDUs::SET_REQUEST:
	    pdu = p.m_set_request->m_SetRequest_PDU;
	    p.m_set_request->m_SetRequest_PDU = 0;
	    return pdu;
	case Snmp::PDUs::RESPONSE:
	    pdu = p.m_response->m_Response_PDU;
	    p.m_response->m_Response_PDU = 0;
	    return pdu;
	case Snmp::PDUs::INFORM_REQUEST:
	    pdu = p.m_inform_request->m_InformRequest_PDU;
	    p.m_inform_request->m_InformRequest_PDU = 0;
	    return pdu;
	case Snmp::PDUs::SNMPV2_TRAP:
	    pdu =  p.m_snmpV2_trap->m_SNMPv2_Trap_PDU;
	    p.m_snmpV2_trap->m_SNMPv2_Trap_PDU = 0;
	    return pdu;
	case Snmp::PDUs::REPORT:
	    pdu = p.m_report->m_Report_PDU;
	    p.m_report->m_Report_PDU = 0;
	    return pdu;
	default:
	    break;
    }
    return 0;
}

// build a remote destination
SocketAddr SnmpAgent::buildDestination(const String& ip, const String& port)
{
    SocketAddr dest(AF_INET);
    dest.host(ip);
    dest.port(port.toInteger());
    return dest;
}

// build a Variable Binding list containing the mandatory OIDs for a trap
Snmp::VarBindList* SnmpAgent::addTrapOIDs(const String& notifOID)
{
    if (!m_mibTree)
	return 0;

    // add sysUpTime
    AsnMib* mib = m_mibTree->find("sysUpTime");
    if (!mib)
	return 0;
    Snmp::VarBind* sysUpTime = new Snmp::VarBind();
    sysUpTime->m_name->m_ObjectName = mib->getOID();
    u_int32_t sysTime = (u_int32_t)((Time::msecNow() / 10) - (100 * (uint64_t)m_startTime)); // measured hundreths of a second
    AsnValue val(String(sysTime),AsnValue::TIMETICKS);
    assignValue(sysUpTime,&val);

    // add trapOID
    mib = m_mibTree->find("snmpTrapOID");
    if (!mib)
	return 0;
    Snmp::VarBind* trapOID = new Snmp::VarBind();
    trapOID->m_name->m_ObjectName = mib->getOID();
    AsnValue trOID(notifOID,AsnValue::OBJECT_ID);
    assignValue(trapOID,&trOID);

    Snmp::VarBindList* list = new Snmp::VarBindList();
    list->m_list.append(sysUpTime);
    list->m_list.append(trapOID);

    return list;
}

// build a trap PDU for SNMPv2
Snmp::SNMPv2_Trap_PDU SnmpAgent::buildTrapPDU(const String& name, const String* value, unsigned int index)
{
    DDebug(&__plugin,DebugAll,"::buildTrapPDU(notif='%s', value='%s', index='%u')",
	name.c_str(),TelEngine::c_str(value),index);
    Snmp::SNMPv2_Trap_PDU trapPDU;;
    Snmp::PDU* pdu = trapPDU.m_SNMPv2_Trap_PDU;
    if (!(m_mibTree && pdu))
	return trapPDU;
    // set a requestID and error information
    pdu->m_request_id = Time::secNow();// - m_startTime;
    pdu->m_error_status = Snmp::PDU::s_noError_error_status;
    pdu->m_error_index = 0;

    // try to find the OID of the notification received
    AsnMib* notifMib = m_mibTree->find(name);
    if (!notifMib) {
	DDebug(&__plugin,DebugInfo,"::buildTrapPDU(notif='%s', value='%s') - no such notification exists",
		    name.c_str(),TelEngine::c_str(value));
	return trapPDU;
    }

    // add the mandatory OIDs
    notifMib->setIndex(index);
    String oid = notifMib->getOID();
    TelEngine::destruct(pdu->m_variable_bindings);
    pdu->m_variable_bindings = addTrapOIDs(index ? oid : notifMib->toString());
    if (!pdu->m_variable_bindings) {
	Debug(&__plugin,DebugInfo,"::buildTrapPDU() - could not set sysUpTime and/or trapOID");
	return trapPDU;
    }

    // add the trap OID with index and its value if requested
    if (value) {
	Snmp::VarBind* trapVal = new Snmp::VarBind();
	trapVal->m_name->m_ObjectName = oid;
	String typeStr = notifMib->getType();
	int type = lookup(typeStr,s_types,0);
	AsnValue* v = new AsnValue(value,type);
	assignValue(trapVal,v);
	pdu->m_variable_bindings->m_list.append(trapVal);
	TelEngine::destruct(v);
    }
    // return the trapPDU
    return trapPDU;
}

// build a SNMPv3 TrapPDU
int SnmpAgent::buildTrapMsgV3(Snmp::SNMPv3Message& msg, DataBlock d)
{
    if (!m_trapUser)
	return -1;
    DDebug(&__plugin,DebugAll,"::buildTrapMsgV3() from msg=%p",&msg);
    // build header data
    msg.m_msgVersion = SNMP_VERSION_3;
    Snmp::HeaderData* header = msg.m_msgGlobalData;
    if (!header)
	return -1;
    header->m_msgID = Time::secNow() - m_startTime;
    header->m_msgMaxSize = MSG_MAX_SIZE;
    header->m_msgSecurityModel = USM_SEC_MODEL;

    // get data to fill
    Snmp::ScopedPduData* scopedData = msg.m_msgData;
    if (!scopedData) {
	scopedData = new Snmp::ScopedPduData();
	msg.m_msgData = scopedData;
    }
    scopedData->m_choiceType = Snmp::ScopedPduData::PLAINTEXT;
    Snmp::ScopedPDU* scopedPdu = scopedData->m_plaintext;
    if (!scopedPdu) {
	scopedPdu = new Snmp::ScopedPDU();
	scopedData->m_plaintext = scopedPdu;
    }
    scopedPdu->m_contextEngineID = m_engineId;
    scopedPdu->m_contextName = "";
    scopedPdu->m_data = d;

    SnmpV3MsgContainer msgWrapper;
    msgWrapper.setScopedPdu(scopedPdu);
    msgWrapper.setReportFlag(false);
    // get received security parameters
    msgWrapper.setUser(m_trapUser);
    // build the security parameters
    Snmp::UsmSecurityParameters& sec = msgWrapper.getSecurity();
    sec.m_msgAuthoritativeEngineID = m_engineId;
    sec.m_msgAuthoritativeEngineBoots = m_engineBoots;
    sec.m_msgAuthoritativeEngineTime = Time::secNow() - m_startTime;
    sec.m_msgUserName = m_trapUser->toString();
    sec.m_msgAuthenticationParameters = s_zeroKey;

    msgWrapper.prepareForSend(msg);
    msgWrapper.setScopedPdu(0);
    return 0;
}

// check to see if a trap is disabled
bool SnmpAgent::trapDisabled(const String& name)
{
    if (!m_mibTree)
	return true;
    AsnMib* mib = m_mibTree->find(name);
    if (!mib) {
	DDebug(&__plugin,DebugInfo,"Notification '%s' does not exist",name.c_str());
	return true;
    }
    if (m_traps && m_traps->find(name))
	return true;
    String trapOid;
    String oid = mib->toString();
    String disabledTraps = s_cfg.getValue("traps","disable_traps","");
    ObjList* list = disabledTraps.split(',');
    if (!list)
	return false;
    for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
	String* trap = static_cast<String*>(o->get());
	if (trap->endsWith(".*"))
	    *trap = trap->substr(0, trap->length() - 2);
	AsnMib* trapMib = m_mibTree->find(*trap);
	if (trapMib)
	    trapOid = trapMib->toString();
	if (trapOid == oid || oid.startsWith(trapOid)) {
	    TelEngine::destruct(list);
	    return true;
	}
    }
    TelEngine::destruct(list);
    return false;
}

// send a trap from a received notification
bool SnmpAgent::sendNotification(const String& name, const String* value, unsigned int index, const NamedList* extra)
{
    if (!(s_enabledTraps && m_msgQueue))
	return false;
    // check to see if the trap is enabled
    if (trapDisabled(name))
	return false;
    // check to see if trap handling has beed configured
    NamedList* params = s_cfg.getSection("traps");
    if (!params) {
	Debug(&__plugin,DebugMild,"::sendNotification('%s', '%s') -"
	      " traps have not been configured",name.c_str(),TelEngine::c_str(value));
	return false;
    }
    DDebug(&__plugin,DebugAll,"::sendNotification('%s', '%s')",name.c_str(),TelEngine::c_str(value));

    // check to see that the right version for SNMP traps are configured
    String protoStr = params->getValue("proto_version","SNMPv2c");
    int proto = lookup(protoStr,s_proto,0);
    if (proto < SNMP_VERSION_2C) {
	Debug(&__plugin,DebugStub,"::sendNotification() STUB : TRAPS FOR SNMPv1 NOT IMPLEMENTED");
	return false;
    }
    else {
	if (proto == SNMP_VERSION_2S) {
	    Debug(&__plugin,DebugStub,"::sendNotification() - SNMPv2S not supported");
	    return false;
	}
    }

    // build a trap pdu
    Snmp::SNMPv2_Trap_PDU trapPDU = buildTrapPDU(name,value,index);
    if (trapPDU.m_SNMPv2_Trap_PDU->m_variable_bindings->m_list.count() < 2) {
	Debug(&__plugin,DebugWarn,"::sendNotification() - trap PDU incorrectly built - aborting the send of the notification");
	return false;
    }

    // populate extra variables
    if (extra) {
	Snmp::PDU* pdu = trapPDU.m_SNMPv2_Trap_PDU;
	int count = extra->getIntValue("count",-1);
	bool anyDisabled = false;
	bool allDisabled = true;
	for (int i = 0; ; i++) {
	    // if count is set iterate up to it
	    if (count >= 0 && i >= count)
		break;
	    String extraName = "notify.";
	    extraName << i;
	    const String& xName = (*extra)[extraName];
	    if (xName.null()) {
		// if count not set stop at first missing name
		if (count < 0)
		    break;
		else
		    continue;
	    }
	    if (trapDisabled(xName)) {
		anyDisabled = true;
		continue;
	    }
	    allDisabled = false;
	    String extraVal = "value.";
	    extraVal << i;
	    const String& xVal = (*extra)[extraVal];
	    AsnMib* notifMib = m_mibTree->find(xName);
	    if (!notifMib) {
		DDebug(&__plugin,DebugInfo,"::sendNotification(notif.%d='%s', value.%d='%s') - no such notification exists",
		    i,xName.c_str(),i,xVal.c_str());
		continue;
	    }
	    Snmp::VarBind* trapVar = new Snmp::VarBind();
	    trapVar->m_name->m_ObjectName = notifMib->getOID();
	    int type = lookup(notifMib->getType(),s_types,0);
	    AsnValue* v = new AsnValue(&xVal,type);
	    assignValue(trapVar,v);
	    pdu->m_variable_bindings->m_list.append(trapVar);
	    TelEngine::destruct(v);
	}
	if (anyDisabled && allDisabled)
	    return false;
    }

    // build pdus
    Snmp::PDUs pdus;
    pdus.m_choiceType = Snmp::PDUs::SNMPV2_TRAP;
    TelEngine::destruct(pdus.m_snmpV2_trap);
    pdus.m_snmpV2_trap = &trapPDU;
    DataBlock d;
    pdus.encode(d);
    pdus.m_snmpV2_trap = 0;

    DataBlock data;
    // build the required version of a SNMP message
    if (proto == SNMP_VERSION_2C) {
	Snmp::Message msg;
	msg.m_version = Snmp::Message::s_version_2_version;
	msg.m_community = params->getValue("community","");
	msg.m_data = d;
	msg.encode(data);
    }
    else if (proto == SNMP_VERSION_3) {
	Snmp::SNMPv3Message msg;
	if (buildTrapMsgV3(msg,d) == -1)
	    return false;
	msg.encode(data);
    }

    // build a new SNMP message wrapper
    SnmpMessage* msgContainer = new SnmpMessage();
    msgContainer->setPeer(s_remote);

    // send the data of the message
    msgContainer->setData(data);
    bool ok = m_msgQueue && m_msgQueue->sendMsg(msgContainer);
    TelEngine::destruct(msgContainer);
    return ok;
}

// obtain from the openssl module a cipher for encryption
Cipher* SnmpAgent::getCipher(int cryptoType)
{
    DDebug(this,DebugAll,"::getCipher(%s)",lookup(cryptoType,s_crypto,""));
    if (cryptoType != SnmpUser::AES_ENCRYPT && cryptoType != SnmpUser::DES_ENCRYPT)
	return 0;

    if (cryptoType == SnmpUser::AES_ENCRYPT && m_cipherAES)
	return m_cipherAES;
    if (cryptoType == SnmpUser::DES_ENCRYPT && m_cipherDES)
	return m_cipherDES;

    Cipher* ret = 0;
    Message msg("engine.cipher");
    if (cryptoType == SnmpUser::AES_ENCRYPT)
	msg.addParam("cipher","aes_cfb");
    if (cryptoType == SnmpUser::DES_ENCRYPT)
	msg.addParam("cipher","des_cbc");
    CipherHolder* cHold = new CipherHolder;
    msg.userData(cHold);
    cHold->deref();

    if (Engine::dispatch(msg)) {
	ret = cHold->cipher();
	if (cryptoType == SnmpUser::AES_ENCRYPT)
	    m_cipherAES = ret;
	if (cryptoType == SnmpUser::DES_ENCRYPT)
	    m_cipherDES = ret;
    }
    return ret;
}

// get the value from a variable binding
DataBlock SnmpAgent::getVal(Snmp::VarBind* varBind)
{
    DDebug(this,DebugAll,"::getVal([%p])",varBind);
    if (!varBind)
	return DataBlock();
    Snmp::ObjectSyntax* objSyn = varBind->m_value;
    if (!objSyn)
	return DataBlock();
    int type = -1;
    switch (objSyn->m_choiceType) {
	case Snmp::ObjectSyntax::SIMPLE:
	    type = objSyn->m_simple->m_choiceType;
	    switch (type) {
		case Snmp::SimpleSyntax::INTEGER_VALUE:
		    return DataBlock((void*)&objSyn->m_simple->m_integer_value,sizeof(objSyn->m_simple->m_integer_value));
		case Snmp::SimpleSyntax::STRING_VALUE:
		    return DataBlock((void*)objSyn->m_simple->m_string_value.toString().c_str(),sizeof(objSyn->m_simple->m_string_value));
		case Snmp::SimpleSyntax::OBJECTID_VALUE:
		    return DataBlock((void*)objSyn->m_simple->m_objectID_value.toString().c_str(),
					    objSyn->m_simple->m_objectID_value.toString().length());
		default:
		  break;
	    }
	    break;
	case Snmp::ObjectSyntax::APPLICATION_WIDE:
	    type = objSyn->m_application_wide->m_choiceType;
	    switch (type) {
		case Snmp::ApplicationSyntax::IPADDRESS_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_ipAddress_value,
			    sizeof(objSyn->m_application_wide->m_ipAddress_value));
		case Snmp::ApplicationSyntax::COUNTER_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_counter_value,
			    sizeof(objSyn->m_application_wide->m_counter_value));
		case Snmp::ApplicationSyntax::TIMETICKS_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_timeticks_value,
			    sizeof(objSyn->m_application_wide->m_timeticks_value));
		case Snmp::ApplicationSyntax::ARBITRARY_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_timeticks_value,
			    sizeof(objSyn->m_application_wide->m_timeticks_value));
		case Snmp::ApplicationSyntax::BIG_COUNTER_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_big_counter_value,
			    sizeof(objSyn->m_application_wide->m_big_counter_value));
		case Snmp::ApplicationSyntax::UNSIGNED_INTEGER_VALUE:
		    return DataBlock((void*)objSyn->m_application_wide->m_unsigned_integer_value,
			    sizeof(objSyn->m_application_wide->m_unsigned_integer_value));
		default:
		    break;
	    }
	    break;
	default:
	  break;
    }
    Debug(&__plugin,DebugInfo,"SnmpAgent::getVal([%p]) - no value",varBind);
    return DataBlock();
}

// obtain the value for a query made through SNMP
AsnValue SnmpAgent::makeQuery(const String& query, unsigned int& index, AsnMib* mib)
{
    DDebug(&__plugin,DebugAll, "::makeQuery(query='%s', index='%d')",query.c_str(),index);
    AsnValue val;
    if (query == YSTRING("version")) {
	val.setValue(s_yateVersion);
	val.setType(AsnValue::STRING);
	return val;
    }
    if (query == YSTRING("runId")) {
	String v = Engine::runParams().getValue(YSTRING("runid"),"");
	val.setValue(v);
	val.setType(AsnValue::STRING);
	return val;
    }
    if (query == YSTRING("upTime")) {
	val.setValue(String(SysUsage::secRunTime()));
	val.setType(AsnValue::COUNTER);
	return val;
    }
    if (query == YSTRING("snmpEngineID")) {
	val.setValue(m_engineId.toHexString());
	val.setType(AsnValue::STRING);
	return val;
    }
    if (query == YSTRING("snmpEngineBoots")) {
	val.setValue(String(m_engineBoots));
	val.setType(AsnValue::INTEGER);
	return val;
    }

    if (query == YSTRING("yateMIBRevision") && m_mibTree) {
	String rev = m_mibTree->findRevision(query);
	val.setValue(rev);
	val.setType(AsnValue::STRING);
	return val;
    }
    if (!queryIsSupported(query,mib))
	return val;

    // ask the monitor module
    Message msg("monitor.query");
    msg.addParam("name",query);
    msg.addParam("index",String(index));
    if (Engine::dispatch(msg)) {
	const String* value = msg.getParam(YSTRING("value"));
	if (!value)
	    value = &msg.retValue();
	if (*value) {
	    val.setValue(*value);
	    val.setType(STRING);
	}
    }

    return val;
}

bool SnmpAgent::queryIsSupported(const String& query, AsnMib* mib)
{
    if (!m_mibTree || s_yateRoot.null())
	return false;
    if (!mib)
	mib = m_mibTree->find(query);
    if (!mib)
	return false;
    return (mib->toString().startsWith(s_yateRoot,false));
}

// generate snmpEngineID from configuration parameters
OctetString SnmpAgent::genEngineId(const int format, String& info)
{
    DDebug(&__plugin,DebugInfo,"::genEngineId(%d,%s)",format,info.c_str());
    OctetString aux;

    // set the first 4 bytes to the PEN number and first bit set to 1 (see RFC 3411)
    u_int32_t firstPart = s_pen | 0x80000000;
    for (unsigned int i = 0; i < sizeof(u_int32_t); i++) {
	u_int8_t byte = (firstPart >> (8 * i));
	DataBlock d;
	d.append(&byte,1);
	aux.insert(d);
    }
    int size = 1, base = 10;
    // add the format for of the 6 bytes remaining from the engine id
    aux.append((void*)&format,1);
    ObjList* list = 0;
    DataBlock db;
    // according to the given format, build the rest of 6 bytes
    switch (format) {
	case IPv4:
	    list = info.split('.');
	    base = 10;
	    break;
	case IPv6:
	    list = info.split(':');
	    size = 2;
	    break;
	case MAC:
	    list = info.split(':');
	    base = 16;
	    break;
	case TEXT:
	    aux.append(info);
	    break;
	case OCTETS:
	    db.unHexify(info);
	    aux.append(db);
	    break;
	case ENTERPRISE:
	    aux.append(info);
	    break;
	default:
	    break;
    }
    if (list) {
	for (ObjList* o = list->skipNull(); o; o = o->skipNext()) {
	    u_int8_t val;
	    String* str = static_cast<String*>(o->get());
	    if (size == 2) {
		int intVal = str->toInteger(0,16);
		DataBlock d,daux;
		val = intVal;
		intVal = intVal >> 8;
		d.append(&val,1);
		daux.insert(d);
		d.clear();
		val = intVal;
		d.append(&val,1);
		daux.insert(d);
		aux.append(daux);
	    }
	    else {
		val = str->toInteger(0,base);
		aux.append(&val,size);
	    }
	}
	TelEngine::destruct(list);
    }
    return aux;
}

void SnmpAgent::authFail(const SocketAddr& addr, int snmpVersion, int reason, int protocol)
{
    String rAddr = addr.host();
    String rPort(addr.port());

    Message* m = new Message("user.authfail");
    m->setParam(YSTRING("module"),name());
    m->setParam(YSTRING("address"),rAddr + ":" + rPort);
    m->setParam(YSTRING("ip_host"),rAddr);
    m->setParam(YSTRING("ip_port"),rPort);
    m->setParam(YSTRING("ip_transport"),lookup(protocol,TransportType::s_typeText,""));
    m->setParam(YSTRING("protocol"),lookup(snmpVersion,s_proto,""));
    m->setParam(YSTRING("reason"),lookup(reason,s_readableErrors,""));

    Engine::enqueue(m);
}

}; // anonymous namespace

/* vi: set ts=8 sw=4 sts=4 noet: */
