net-snmp/apps/snmptrapd_sql.c

1137 lines
33 KiB
C
Raw Normal View History

2022-06-27 15:01:12 +08:00
/*
* File : snmptrapd_sql
* Author : Robert Story
*
* Copyright © 2009 Science Logic, Inc. All rights reserved.
* Use is subject to license terms specified in the COPYING file
* distributed with the Net-SNMP package.
*
* This file implements a handler for snmptrapd which will cache incoming
* traps and then write them to a MySQL database.
*
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#ifdef NETSNMP_USE_MYSQL
/*
* SQL includes
*/
#undef PACKAGE_BUGREPORT
#undef PACKAGE_NAME
#undef PACKAGE_STRING
#undef PACKAGE_TARNAME
#undef PACKAGE_VERSION
#ifdef HAVE_MY_GLOBAL_H
#include <my_global.h>
#endif
#ifdef HAVE_MY_SYS_H
#include <my_sys.h>
#endif
#include <mysql.h>
#include <errmsg.h>
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdio.h>
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "snmptrapd_handlers.h"
#include "snmptrapd_auth.h"
#include "snmptrapd_log.h"
#include "snmptrapd_sql.h"
netsnmp_feature_require(container_fifo)
/*
* define a structure to hold all the file globals
*/
typedef struct netsnmp_sql_globals_t {
char *host_name; /* server host (def=localhost) */
char *user_name; /* username (def=login name) */
char *password; /* password (def=none) */
u_int port_num; /* port number (built-in value) */
char *socket_name; /* socket name (built-in value) */
const char *db_name; /* database name (def=none) */
u_int flags; /* connection flags (none) */
MYSQL *conn; /* connection */
u_char connected; /* connected flag */
const char *groups[3];
MYSQL_STMT *trap_stmt, *vb_stmt; /* prepared statements */
u_int alarm_id; /* id of periodic save alarm */
netsnmp_container *queue; /* container; traps pending database write */
u_int queue_max; /* auto save queue when it gets this big */
int queue_interval; /* auto save every N seconds */
} netsnmp_sql_globals;
static netsnmp_sql_globals _sql = {
NULL, /* host */
NULL, /* username */
NULL, /* password */
0, /* port */
NULL, /* socket */
"net_snmp", /* database */
0, /* conn flags */
NULL, /* connection */
0, /* connected */
{ "client", "snmptrapd", NULL }, /* groups to read from .my.cnf */
NULL, /* trap_stmt */
NULL, /* vb_stmt */
0, /* alarm_id */
NULL, /* queue */
1, /* queue_max */
-1 /* queue_interval */
};
/*
* log traps as text, or binary blobs?
*/
#define NETSNMP_MYSQL_TRAP_VALUE_TEXT 1
/*
* We will be using prepared statements for performance reasons. This
* requires a sql bind structure for each cell to be inserted in the
* database. We will be using 2 global static structures to bind to,
* and a netsnmp container to store the necessary data until it is
* written to the database. Fixed size buffers are also used to
* simplify memory management.
*/
/** enums for the trap fields to be bound */
enum{
TBIND_DATE = 0, /* time received */
TBIND_HOST, /* src ip */
TBIND_USER, /* auth/user information */
TBIND_TYPE, /* pdu type */
TBIND_VER, /* snmp version */
TBIND_REQID, /* request id */
TBIND_OID, /* trap OID */
TBIND_TRANSPORT, /* transport */
TBIND_SECURITY_MODEL, /* security model */
TBIND_v3_MSGID, /* v3 msg id */
TBIND_v3_SECURITY_LEVEL, /* security level */
TBIND_v3_CONTEXT_NAME, /* context */
TBIND_v3_CONTEXT_ENGINE, /* context engine id */
TBIND_v3_SECURITY_NAME, /* security name */
TBIND_v3_SECURITY_ENGINE, /* security engine id */
TBIND_MAX
};
/** enums for the varbind fields to be bound */
enum {
VBIND_ID = 0, /* trap_id */
VBIND_OID, /* varbind oid */
VBIND_TYPE, /* varbind type */
VBIND_VAL, /* varbind value */
VBIND_MAX
};
/** buffer struct for varbind data */
typedef struct sql_vb_buf_t {
char *oid;
u_long oid_len;
u_char *val;
u_long val_len;
uint16_t type;
} sql_vb_buf;
/** buffer struct for trap data */
typedef struct sql_buf_t {
char *host;
u_long host_len;
char *oid;
u_long oid_len;
char *user;
u_long user_len;
MYSQL_TIME time;
uint16_t version, type;
uint32_t reqid;
char *transport;
u_long transport_len;
uint16_t security_level, security_model;
uint32_t msgid;
char *context;
u_long context_len;
char *context_engine;
u_long context_engine_len;
char *security_name;
u_long security_name_len;
char *security_engine;
u_long security_engine_len;
netsnmp_container *varbinds;
char logged;
} sql_buf;
/*
* static bind structures, plus 2 static buffers to bind to.
*/
static MYSQL_BIND _tbind[TBIND_MAX], _vbind[VBIND_MAX];
static my_bool _no_v3;
static void _sql_process_queue(u_int dontcare, void *meeither);
/*
* parse the sqlMaxQueue configuration token
*/
static void
_parse_queue_fmt(const char *token, char *cptr)
{
_sql.queue_max = atoi(cptr);
DEBUGMSGTL(("sql:queue","queue max now %d\n", _sql.queue_max));
}
/*
* parse the sqlSaveInterval configuration token
*/
static void
_parse_interval_fmt(const char *token, char *cptr)
{
_sql.queue_interval = atoi(cptr);
DEBUGMSGTL(("sql:queue","queue interval now %d seconds\n",
_sql.queue_interval));
}
/*
* register sql related configuration tokens
*/
void
snmptrapd_register_sql_configs( void )
{
register_config_handler("snmptrapd", "sqlMaxQueue",
_parse_queue_fmt, NULL, "integer");
register_config_handler("snmptrapd", "sqlSaveInterval",
_parse_interval_fmt, NULL, "seconds");
}
static void
netsnmp_sql_disconnected(void)
{
DEBUGMSGTL(("sql:connection","disconnected\n"));
_sql.connected = 0;
/** release prepared statements */
if (_sql.trap_stmt) {
mysql_stmt_close(_sql.trap_stmt);
_sql.trap_stmt = NULL;
}
if (_sql.vb_stmt) {
mysql_stmt_close(_sql.vb_stmt);
_sql.vb_stmt = NULL;
}
}
/*
* convenience function to log mysql errors
*/
static void
netsnmp_sql_error(const char *message)
{
u_int err = mysql_errno(_sql.conn);
snmp_log(LOG_ERR, "%s\n", message);
if (_sql.conn != NULL) {
#if MYSQL_VERSION_ID >= 40101
snmp_log(LOG_ERR, "Error %u (%s): %s\n",
err, mysql_sqlstate(_sql.conn), mysql_error(_sql.conn));
#else
snmp(LOG_ERR, "Error %u: %s\n",
mysql_errno(_sql.conn), mysql_error(_sql.conn));
#endif
}
if (CR_SERVER_GONE_ERROR == err)
netsnmp_sql_disconnected();
}
/*
* convenience function to log mysql statement errors
*/
static void
netsnmp_sql_stmt_error (MYSQL_STMT *stmt, const char *message)
{
u_int err = mysql_errno(_sql.conn);
snmp_log(LOG_ERR, "%s\n", message);
if (stmt) {
snmp_log(LOG_ERR, "SQL Error %u (%s): %s\n",
mysql_stmt_errno(stmt), mysql_stmt_sqlstate(stmt),
mysql_stmt_error(stmt));
}
if (CR_SERVER_GONE_ERROR == err)
netsnmp_sql_disconnected();
}
/*
* sql cleanup function, called at exit
*/
static void
netsnmp_mysql_cleanup(void)
{
DEBUGMSGTL(("sql:cleanup"," called\n"));
/** unregister alarm */
if (_sql.alarm_id)
snmp_alarm_unregister(_sql.alarm_id);
/** save any queued traps */
if (CONTAINER_SIZE(_sql.queue))
_sql_process_queue(0,NULL);
CONTAINER_FREE(_sql.queue);
_sql.queue = NULL;
if (_sql.trap_stmt) {
mysql_stmt_close(_sql.trap_stmt);
_sql.trap_stmt = NULL;
}
if (_sql.vb_stmt) {
mysql_stmt_close(_sql.vb_stmt);
_sql.vb_stmt = NULL;
}
/** disconnect from server */
netsnmp_sql_disconnected();
if (_sql.conn) {
mysql_close(_sql.conn);
_sql.conn = NULL;
}
mysql_library_end();
}
/*
* setup (initialize, prepare and bind) a prepared statement
*/
static int
netsnmp_mysql_bind(const char *text, size_t text_size, MYSQL_STMT **stmt,
MYSQL_BIND *bind)
{
if ((NULL == text) || (NULL == stmt) || (NULL == bind)) {
snmp_log(LOG_ERR,"invalid paramaters to netsnmp_mysql_bind()\n");
return -1;
}
*stmt = mysql_stmt_init(_sql.conn);
if (NULL == *stmt) {
netsnmp_sql_error("could not initialize trap statement handler");
return -1;
}
if (mysql_stmt_prepare(*stmt, text, text_size) != 0) {
netsnmp_sql_stmt_error(*stmt, "Could not prepare INSERT");
mysql_stmt_close(*stmt);
*stmt = NULL;
return -1;
}
return 0;
}
/*
* connect to the database and do initial setup
*/
static int
netsnmp_mysql_connect(void)
{
char trap_stmt[] = "INSERT INTO notifications "
"(date_time, host, auth, type, version, request_id, snmpTrapOID, transport, security_model, v3msgid, v3security_level, v3context_name, v3context_engine, v3security_name, v3security_engine) "
"VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
char vb_stmt[] = "INSERT INTO varbinds "
"(trap_id, oid, type, value) VALUES (?,?,?,?)";
/** initialize connection handler */
if (_sql.connected)
return 0;
DEBUGMSGTL(("sql:connection","connecting\n"));
/** connect to server */
if (mysql_real_connect (_sql.conn, _sql.host_name, _sql.user_name,
_sql.password, _sql.db_name, _sql.port_num,
_sql.socket_name, _sql.flags) == NULL) {
netsnmp_sql_error("mysql_real_connect() failed");
goto err;
}
_sql.connected = 1;
/** disable autocommit */
if(0 != mysql_autocommit(_sql.conn, 0)) {
netsnmp_sql_error("mysql_autocommit(0) failed");
goto err;
}
netsnmp_assert((_sql.trap_stmt == NULL) && (_sql.vb_stmt == NULL));
/** prepared statement for inserts */
if (0 != netsnmp_mysql_bind(trap_stmt,sizeof(trap_stmt), &_sql.trap_stmt,
_tbind))
goto err;
if (0 != netsnmp_mysql_bind(vb_stmt,sizeof(vb_stmt),&_sql.vb_stmt,
_vbind)) {
mysql_stmt_close(_sql.trap_stmt);
_sql.trap_stmt = NULL;
goto err;
}
return 0;
err:
if (_sql.connected)
_sql.connected = 0;
return -1;
}
/** one-time initialization for mysql */
int
netsnmp_mysql_init(void)
{
int not_argc = 0, i;
char *not_args[] = { NULL };
char **not_argv = not_args;
netsnmp_trapd_handler *traph;
DEBUGMSGTL(("sql:init","called\n"));
/** negative or 0 interval disables sql logging */
if (_sql.queue_interval <= 0) {
DEBUGMSGTL(("sql:init",
"mysql not enabled (sqlSaveInterval is <= 0)\n"));
return 0;
}
/** create queue for storing traps til they are written to the db */
_sql.queue = netsnmp_container_find("fifo");
if (NULL == _sql.queue) {
snmp_log(LOG_ERR, "Could not allocate sql buf container\n");
return -1;
}
#if defined(HAVE_MYSQL_INIT)
mysql_init(NULL);
#elif defined(HAVE_MY_INIT)
MY_INIT("snmptrapd");
#else
my_init();
#endif
/** load .my.cnf values */
#if HAVE_MY_LOAD_DEFAULTS
my_load_defaults ("my", _sql.groups, &not_argc, &not_argv, 0);
#elif defined(HAVE_LOAD_DEFAULTS)
load_defaults ("my", _sql.groups, &not_argc, &not_argv);
#endif
for(i=0; i < not_argc; ++i) {
if (NULL == not_argv[i])
continue;
if (strncmp(not_argv[i],"--password=",11) == 0)
_sql.password = &not_argv[i][11];
else if (strncmp(not_argv[i],"--host=",7) == 0)
_sql.host_name = &not_argv[i][7];
else if (strncmp(not_argv[i],"--user=",7) == 0)
_sql.user_name = &not_argv[i][7];
else if (strncmp(not_argv[i],"--port=",7) == 0)
_sql.port_num = atoi(&not_argv[i][7]);
else if (strncmp(not_argv[i],"--socket=",9) == 0)
_sql.socket_name = &not_argv[i][9];
else if (strncmp(not_argv[i],"--database=",11) == 0)
_sql.db_name = &not_argv[i][11];
else
snmp_log(LOG_WARNING, "unknown argument[%d] %s\n", i, not_argv[i]);
}
/** init bind structures */
memset(_tbind, 0x0, sizeof(_tbind));
memset(_vbind, 0x0, sizeof(_vbind));
/** trap static bindings */
_tbind[TBIND_HOST].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_HOST].length = &_tbind[TBIND_HOST].buffer_length;
_tbind[TBIND_OID].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_OID].length = &_tbind[TBIND_OID].buffer_length;
_tbind[TBIND_REQID].buffer_type = MYSQL_TYPE_LONG;
_tbind[TBIND_REQID].is_unsigned = 1;
_tbind[TBIND_VER].buffer_type = MYSQL_TYPE_SHORT;
_tbind[TBIND_VER].is_unsigned = 1;
_tbind[TBIND_TYPE].buffer_type = MYSQL_TYPE_SHORT;
_tbind[TBIND_TYPE].is_unsigned = 1;
_tbind[TBIND_DATE].buffer_type = MYSQL_TYPE_DATETIME;
_tbind[TBIND_USER].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_USER].length = &_tbind[TBIND_USER].buffer_length;
_tbind[TBIND_TRANSPORT].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_TRANSPORT].length = &_tbind[TBIND_TRANSPORT].buffer_length;
_tbind[TBIND_SECURITY_MODEL].buffer_type = MYSQL_TYPE_SHORT;
_tbind[TBIND_SECURITY_MODEL].is_unsigned = 1;
_tbind[TBIND_v3_MSGID].buffer_type = MYSQL_TYPE_LONG;
_tbind[TBIND_v3_MSGID].is_unsigned = 1;
_tbind[TBIND_v3_SECURITY_LEVEL].buffer_type = MYSQL_TYPE_SHORT;
_tbind[TBIND_v3_SECURITY_LEVEL].is_unsigned = 1;
_tbind[TBIND_v3_CONTEXT_NAME].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_v3_CONTEXT_ENGINE].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_v3_SECURITY_NAME].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_v3_SECURITY_NAME].length =
&_tbind[TBIND_v3_SECURITY_NAME].buffer_length;
_tbind[TBIND_v3_CONTEXT_NAME].length =
&_tbind[TBIND_v3_CONTEXT_NAME].buffer_length;
_tbind[TBIND_v3_SECURITY_ENGINE].buffer_type = MYSQL_TYPE_STRING;
_tbind[TBIND_v3_SECURITY_ENGINE].length =
&_tbind[TBIND_v3_SECURITY_ENGINE].buffer_length;
_tbind[TBIND_v3_CONTEXT_ENGINE].length =
&_tbind[TBIND_v3_CONTEXT_ENGINE].buffer_length;
_tbind[TBIND_v3_MSGID].is_null =
_tbind[TBIND_v3_SECURITY_LEVEL].is_null =
_tbind[TBIND_v3_CONTEXT_NAME].is_null =
_tbind[TBIND_v3_CONTEXT_ENGINE].is_null =
_tbind[TBIND_v3_SECURITY_NAME].is_null =
_tbind[TBIND_v3_SECURITY_ENGINE].is_null = &_no_v3;
/** variable static bindings */
_vbind[VBIND_ID].buffer_type = MYSQL_TYPE_LONG;
_vbind[VBIND_ID].is_unsigned = 1;
_vbind[VBIND_OID].buffer_type = MYSQL_TYPE_STRING;
_vbind[VBIND_OID].length = &_vbind[VBIND_OID].buffer_length;
_vbind[VBIND_TYPE].buffer_type = MYSQL_TYPE_SHORT;
_vbind[VBIND_TYPE].is_unsigned = 1;
#ifdef NETSNMP_MYSQL_TRAP_VALUE_TEXT
_vbind[VBIND_VAL].buffer_type = MYSQL_TYPE_STRING;
#else
_vbind[VBIND_VAL].buffer_type = MYSQL_TYPE_BLOB;
#endif
_vbind[VBIND_VAL].length = &_vbind[VBIND_VAL].buffer_length;
_sql.conn = mysql_init (NULL);
if (_sql.conn == NULL) {
netsnmp_sql_error("mysql_init() failed (out of memory?)");
return -1;
}
#if MYSQL_VERSION_ID >= 100000
mysql_options(_sql.conn, MYSQL_READ_DEFAULT_GROUP, "snmptrapd");
#endif
/** try to connect; we'll try again later if we fail */
(void) netsnmp_mysql_connect();
/** register periodic queue save */
_sql.alarm_id = snmp_alarm_register(_sql.queue_interval, /* seconds */
1, /* repeat */
_sql_process_queue, /* function */
NULL); /* client args */
/** add handler */
traph = netsnmp_add_global_traphandler(NETSNMPTRAPD_PRE_HANDLER,
mysql_handler);
if (NULL == traph) {
snmp_log(LOG_ERR, "Could not allocate sql trap handler\n");
return -1;
}
traph->authtypes = TRAP_AUTH_LOG;
atexit(netsnmp_mysql_cleanup);
return 0;
}
/*
* log CSV version of trap.
* dontcare param is there so this function can be passed directly
* to CONTAINER_FOR_EACH.
*/
static void
_sql_log(sql_buf *sqlb, void* dontcare)
{
netsnmp_iterator *it;
sql_vb_buf *sqlvb;
if ((NULL == sqlb) || sqlb->logged)
return;
/*
* log trap info
* nothing done to protect against data insertion attacks with
* respect to bad data (commas, newlines, etc)
*/
snmp_log(LOG_ERR,
"trap:%d-%d-%d %d:%d:%d,%s,%d,%d,%d,%s,%s,%d,%d,%d,%s,%s,%s,%s\n",
sqlb->time.year,sqlb->time.month,sqlb->time.day,
sqlb->time.hour,sqlb->time.minute,sqlb->time.second,
sqlb->user,
sqlb->type, sqlb->version, sqlb->reqid, sqlb->oid,
sqlb->transport, sqlb->security_model, sqlb->msgid,
sqlb->security_level, sqlb->context,
sqlb->context_engine, sqlb->security_name,
sqlb->security_engine);
sqlb->logged = 1; /* prevent multiple logging */
it = CONTAINER_ITERATOR(sqlb->varbinds);
if (NULL == it) {
snmp_log(LOG_ERR,
"error creating iterator; incomplete trap logged\n");
return;
}
/** log varbind info */
for( sqlvb = ITERATOR_FIRST(it); sqlvb; sqlvb = ITERATOR_NEXT(it)) {
#ifdef NETSNMP_MYSQL_TRAP_VALUE_TEXT
snmp_log(LOG_ERR,"varbind:%s,%s\n", sqlvb->oid, sqlvb->val);
#else
char *hex;
int len = binary_to_hex(sqlvb->val, sqlvb->val_len, &hex);
if (hex) {
snmp_log(LOG_ERR,"varbind:%d,%s,%s\n", sqlvb->oid, hex);
free(hex);
}
else {
snmp_log(LOG_ERR,"malloc failed for varbind hex value\n");
snmp_log(LOG_ERR,"varbind:%s,\n", sqlvb->oid);
}
#endif
}
ITERATOR_RELEASE(it);
}
/*
* free a buffer
* dontcare param is there so this function can be passed directly
* to CONTAINER_FOR_EACH.
*/
static void
_sql_vb_buf_free(sql_vb_buf *sqlvb, void* dontcare)
{
if (NULL == sqlvb)
return;
SNMP_FREE(sqlvb->oid);
SNMP_FREE(sqlvb->val);
free(sqlvb);
}
/*
* free a buffer
* dontcare param is there so this function can be passed directly
* to CONTAINER_FOR_EACH.
*/
static void
_sql_buf_free(sql_buf *sqlb, void* dontcare)
{
if (NULL == sqlb)
return;
/** do varbinds first */
if (sqlb->varbinds) {
CONTAINER_CLEAR(sqlb->varbinds,
(netsnmp_container_obj_func*)_sql_vb_buf_free, NULL);
CONTAINER_FREE(sqlb->varbinds);
}
SNMP_FREE(sqlb->host);
SNMP_FREE(sqlb->oid);
SNMP_FREE(sqlb->user);
SNMP_FREE(sqlb->context);
SNMP_FREE(sqlb->security_name);
SNMP_FREE(sqlb->context_engine);
SNMP_FREE(sqlb->security_engine);
SNMP_FREE(sqlb->transport);
free(sqlb);
}
/*
* allocate buffer to store trap and varbinds
*/
static sql_buf *
_sql_buf_get(void)
{
sql_buf *sqlb;
/** buffer for trap info */
sqlb = SNMP_MALLOC_TYPEDEF(sql_buf);
if (NULL == sqlb)
return NULL;
/** fifo for varbinds */
sqlb->varbinds = netsnmp_container_find("fifo");
if (NULL == sqlb->varbinds) {
free(sqlb);
return NULL;
}
return sqlb;
}
/*
* save info from incoming trap
*
* return 0 on success, anything else is an error
*/
static int
_sql_save_trap_info(sql_buf *sqlb, netsnmp_pdu *pdu,
netsnmp_transport *transport)
{
static oid trapoids[] = { 1, 3, 6, 1, 6, 3, 1, 1, 5, 0 };
oid *trap_oid, tmp_oid[MAX_OID_LEN];
time_t now;
struct tm *cur_time;
size_t tmp_size;
size_t buf_host_len_t, buf_oid_len_t, buf_user_len_t;
int oid_overflow, trap_oid_len;
netsnmp_variable_list *vars;
if ((NULL == sqlb) || (NULL == pdu) || (NULL == transport))
return -1;
DEBUGMSGTL(("sql:queue", "queueing incoming trap\n"));
/** time */
(void) time(&now);
cur_time = localtime(&now);
sqlb->time.year = cur_time->tm_year + 1900;
sqlb->time.month = cur_time->tm_mon + 1;
sqlb->time.day = cur_time->tm_mday;
sqlb->time.hour = cur_time->tm_hour;
sqlb->time.minute = cur_time->tm_min;
sqlb->time.second = cur_time->tm_sec;
sqlb->time.second_part = 0;
sqlb->time.neg = 0;
/** host name */
buf_host_len_t = 0;
tmp_size = 0;
realloc_format_trap((u_char**)&sqlb->host, &tmp_size,
&buf_host_len_t, 1, "%B", pdu, transport);
sqlb->host_len = buf_host_len_t;
/* snmpTrapOID */
if (pdu->command == SNMP_MSG_TRAP) {
/*
* convert a v1 trap to a v2 varbind
*/
if (pdu->trap_type == SNMP_TRAP_ENTERPRISESPECIFIC) {
trap_oid_len = pdu->enterprise_length;
memcpy(tmp_oid, pdu->enterprise, sizeof(oid) * trap_oid_len);
if (tmp_oid[trap_oid_len - 1] != 0)
tmp_oid[trap_oid_len++] = 0;
tmp_oid[trap_oid_len++] = pdu->specific_type;
trap_oid = tmp_oid;
} else {
trapoids[9] = pdu->trap_type + 1;
trap_oid = trapoids;
trap_oid_len = OID_LENGTH(trapoids);
}
}
else {
vars = pdu->variables;
if (vars && vars->next_variable) {
trap_oid_len = vars->next_variable->val_len / sizeof(oid);
trap_oid = vars->next_variable->val.objid;
}
else {
static oid null_oid[] = { 0, 0 };
trap_oid_len = OID_LENGTH(null_oid);
trap_oid = null_oid;
}
}
tmp_size = 0;
buf_oid_len_t = oid_overflow = 0;
netsnmp_sprint_realloc_objid_tree((u_char**)&sqlb->oid,&tmp_size,
&buf_oid_len_t, 1, &oid_overflow,
trap_oid, trap_oid_len);
sqlb->oid_len = buf_oid_len_t;
if (oid_overflow)
snmp_log(LOG_WARNING,"OID truncated in sql buffer\n");
/** request id */
sqlb->reqid = pdu->reqid;
/** version (convert to 1 based, for sql enum) */
sqlb->version = pdu->version + 1;
/** command type (convert to 1 based, for sql enum) */
sqlb->type = pdu->command - 159;
/** community string/user name */
tmp_size = 0;
buf_user_len_t = 0;
realloc_format_trap((u_char**)&sqlb->user, &tmp_size,
&buf_user_len_t, 1, "%u", pdu, transport);
sqlb->user_len = buf_user_len_t;
/** transport */
sqlb->transport = transport->f_fmtaddr(transport, pdu->transport_data,
pdu->transport_data_length);
/** security model */
sqlb->security_model = pdu->securityModel;
if ((SNMP_MP_MODEL_SNMPv3+1) == sqlb->version) {
sqlb->msgid = pdu->msgid;
sqlb->security_level = pdu->securityLevel;
if (pdu->contextName) {
sqlb->context = netsnmp_strdup_and_null((u_char*)pdu->contextName,
pdu->contextNameLen);
sqlb->context_len = pdu->contextNameLen;
}
if (pdu->contextEngineID) {
sqlb->context_engine_len =
binary_to_hex(pdu->contextEngineID, pdu->contextEngineIDLen,
&sqlb->context_engine);
}
if (pdu->securityName) {
sqlb->security_name =
netsnmp_strdup_and_null((u_char*)pdu->securityName,
pdu->securityNameLen);
sqlb->security_name_len = pdu->securityNameLen;
}
if (pdu->securityEngineID) {
sqlb->security_engine_len =
binary_to_hex(pdu->securityEngineID, pdu->securityEngineIDLen,
&sqlb->security_engine);
}
}
return 0;
}
/*
* save varbind info from incoming trap
*
* return 0 on success, anything else is an error
*/
static int
_sql_save_varbind_info(sql_buf *sqlb, netsnmp_pdu *pdu)
{
netsnmp_variable_list *var;
sql_vb_buf *sqlvb;
size_t tmp_size, buf_oid_len_t;
int oid_overflow, rc;
#ifdef NETSNMP_MYSQL_TRAP_VALUE_TEXT
size_t buf_val_len_t;
#endif
if ((NULL == sqlb) || (NULL == pdu))
return -1;
var = pdu->variables;
while(var) {
sqlvb = SNMP_MALLOC_TYPEDEF(sql_vb_buf);
if (NULL == sqlvb)
break;
/** OID */
tmp_size = 0;
buf_oid_len_t = oid_overflow = 0;
netsnmp_sprint_realloc_objid_tree((u_char**)&sqlvb->oid, &tmp_size,
&buf_oid_len_t,
1, &oid_overflow, var->name,
var->name_length);
sqlvb->oid_len = buf_oid_len_t;
if (oid_overflow)
snmp_log(LOG_WARNING,"OID truncated in sql insert\n");
/** type */
if (var->type > ASN_OBJECT_ID)
/** convert application types to sql enum */
sqlvb->type = ASN_OBJECT_ID + 1 + (var->type & ~ASN_APPLICATION);
else
sqlvb->type = var->type;
/** value */
#ifdef NETSNMP_MYSQL_TRAP_VALUE_TEXT
tmp_size = 0;
buf_val_len_t = 0;
sprint_realloc_by_type((u_char**)&sqlvb->val, &tmp_size,
&buf_val_len_t, 1, var, NULL, NULL, NULL);
sqlvb->val_len = buf_val_len_t;
#else
sqlvb->val = netsnmp_memdup(var->val.string, var->val_len);
sqlvb->val_len = var->val_len;
#endif
var = var->next_variable;
/** insert into container */
rc = CONTAINER_INSERT(sqlb->varbinds,sqlvb);
if(rc)
snmp_log(LOG_ERR, "couldn't insert varbind into trap container\n");
}
return 0;
}
/*
* sql trap handler
*/
int
mysql_handler(netsnmp_pdu *pdu,
netsnmp_transport *transport,
netsnmp_trapd_handler *handler)
{
sql_buf *sqlb;
int old_format, rc;
DEBUGMSGTL(("sql:handler", "called\n"));
/** allocate a buffer to save data */
sqlb = _sql_buf_get();
if (NULL == sqlb) {
snmp_log(LOG_ERR, "Could not allocate trap sql buffer\n");
return syslog_handler( pdu, transport, handler );
}
/** save OID output format and change to numeric */
old_format = netsnmp_ds_get_int(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_OID_OUTPUT_FORMAT);
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
NETSNMP_OID_OUTPUT_NUMERIC);
rc = _sql_save_trap_info(sqlb, pdu, transport);
rc = _sql_save_varbind_info(sqlb, pdu);
/** restore previous OID output format */
netsnmp_ds_set_int(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_OID_OUTPUT_FORMAT,
old_format);
/** insert into queue */
rc = CONTAINER_INSERT(_sql.queue, sqlb);
if(rc) {
snmp_log(LOG_ERR, "Could not log queue sql trap buffer\n");
_sql_log(sqlb, NULL);
_sql_buf_free(sqlb, NULL);
return -1;
}
/** save queue if size is > max */
if (CONTAINER_SIZE(_sql.queue) >= _sql.queue_max)
_sql_process_queue(0,NULL);
return 0;
}
/*
* save a buffered trap to sql database
*/
static void
_sql_save(sql_buf *sqlb, void *dontcare)
{
netsnmp_iterator *it;
sql_vb_buf *sqlvb;
u_long trap_id;
/*
* don't even try if we don't have a database connection
*/
if (0 == _sql.connected) {
_sql_log(sqlb, NULL);
return;
}
/*
* the prepared statements are bound to the static buffer objects,
* so copy the queued data to the static version.
*/
_tbind[TBIND_HOST].buffer = sqlb->host;
_tbind[TBIND_HOST].buffer_length = sqlb->host_len;
_tbind[TBIND_OID].buffer = sqlb->oid;
_tbind[TBIND_OID].buffer_length = sqlb->oid_len;
_tbind[TBIND_REQID].buffer = (void *)&sqlb->reqid;
_tbind[TBIND_VER].buffer = (void *)&sqlb->version;
_tbind[TBIND_TYPE].buffer = (void *)&sqlb->type;
_tbind[TBIND_SECURITY_MODEL].buffer = (void *)&sqlb->security_model;
_tbind[TBIND_DATE].buffer = (void *)&sqlb->time;
_tbind[TBIND_USER].buffer = sqlb->user;
_tbind[TBIND_USER].buffer_length = sqlb->user_len;
_tbind[TBIND_TRANSPORT].buffer = sqlb->transport;
if (sqlb->transport)
_tbind[TBIND_TRANSPORT].buffer_length = strlen(sqlb->transport);
else
_tbind[TBIND_TRANSPORT].buffer_length = 0;
if ((SNMP_MP_MODEL_SNMPv3+1) == sqlb->version) {
_no_v3 = 0;
_tbind[TBIND_v3_MSGID].buffer = &sqlb->msgid;
_tbind[TBIND_v3_SECURITY_LEVEL].buffer = &sqlb->security_level;
_tbind[TBIND_v3_CONTEXT_NAME].buffer = sqlb->context;
_tbind[TBIND_v3_CONTEXT_NAME].buffer_length = sqlb->context_len;
_tbind[TBIND_v3_CONTEXT_ENGINE].buffer = sqlb->context_engine;
_tbind[TBIND_v3_CONTEXT_ENGINE].buffer_length =
sqlb->context_engine_len;
_tbind[TBIND_v3_SECURITY_NAME].buffer = sqlb->security_name;
_tbind[TBIND_v3_SECURITY_NAME].buffer_length = sqlb->security_name_len;
_tbind[TBIND_v3_SECURITY_ENGINE].buffer = sqlb->security_engine;
_tbind[TBIND_v3_SECURITY_ENGINE].buffer_length =
sqlb->security_engine_len;
}
else {
_no_v3 = 1;
}
if (mysql_stmt_bind_param(_sql.trap_stmt, _tbind) != 0) {
netsnmp_sql_stmt_error(_sql.trap_stmt,
"Could not bind parameters for INSERT");
_sql_log(sqlb, NULL);
return;
}
/** execute the prepared statement */
if (mysql_stmt_execute(_sql.trap_stmt) != 0) {
netsnmp_sql_stmt_error(_sql.trap_stmt,
"Could not execute insert statement for trap");
_sql_log(sqlb, NULL);
return;
}
trap_id = mysql_insert_id(_sql.conn);
/*
* iterate over the varbinds, copy data and insert
*/
it = CONTAINER_ITERATOR(sqlb->varbinds);
if (NULL == it) {
snmp_log(LOG_ERR,"Could not allocate iterator\n");
_sql_log(sqlb, NULL);
return;
}
for( sqlvb = ITERATOR_FIRST(it); sqlvb; sqlvb = ITERATOR_NEXT(it)) {
_vbind[VBIND_ID].buffer = (void *)&trap_id;
_vbind[VBIND_TYPE].buffer = (void *)&sqlvb->type;
_vbind[VBIND_OID].buffer = sqlvb->oid;
_vbind[VBIND_OID].buffer_length = sqlvb->oid_len;
_vbind[VBIND_VAL].buffer = sqlvb->val;
_vbind[VBIND_VAL].buffer_length = sqlvb->val_len;
if (mysql_stmt_bind_param(_sql.vb_stmt, _vbind) != 0) {
netsnmp_sql_stmt_error(_sql.vb_stmt,
"Could not bind parameters for INSERT");
_sql_log(sqlb, NULL);
break;
}
if (mysql_stmt_execute(_sql.vb_stmt) != 0) {
netsnmp_sql_stmt_error(_sql.vb_stmt,
"Could not execute insert statement for varbind");
_sql_log(sqlb, NULL);
break;
}
}
ITERATOR_RELEASE(it);
}
/*
* process (save) queued items to sql database.
*
* dontcare & meeither are dummy params so this function can be used
* as a netsnmp_alarm callback function.
*/
static void
_sql_process_queue(u_int dontcare, void *meeither)
{
int rc;
/** bail if the queue is empty */
if( 0 == CONTAINER_SIZE(_sql.queue))
return;
DEBUGMSGT(("sql:process", "processing %d queued traps\n",
(int)CONTAINER_SIZE(_sql.queue)));
/*
* if we don't have a database connection, try to reconnect. We
* don't care if we fail - traps will be logged in that case.
*/
if (0 == _sql.connected) {
DEBUGMSGT(("sql:process", "no sql connection; reconnecting\n"));
(void) netsnmp_mysql_connect();
}
CONTAINER_FOR_EACH(_sql.queue, (netsnmp_container_obj_func*)_sql_save,
NULL);
if (_sql.connected) {
rc = mysql_commit(_sql.conn);
if (rc) { /* nuts... now what? */
netsnmp_sql_error("commit failed");
CONTAINER_FOR_EACH(_sql.queue,
(netsnmp_container_obj_func*)_sql_log,
NULL);
}
}
CONTAINER_CLEAR(_sql.queue, (netsnmp_container_obj_func*)_sql_buf_free,
NULL);
}
#else
int unused; /* Suppress "empty translation unit" warning */
#endif /* NETSNMP_USE_MYSQL */