diff options
Diffstat (limited to 'apps/snmptrapd_sql.c')
-rw-r--r-- | apps/snmptrapd_sql.c | 1118 |
1 files changed, 1118 insertions, 0 deletions
diff --git a/apps/snmptrapd_sql.c b/apps/snmptrapd_sql.c new file mode 100644 index 0000000..ccba258 --- /dev/null +++ b/apps/snmptrapd_sql.c @@ -0,0 +1,1118 @@ +/* + * 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 + +#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" + +/* + * SQL includes + */ +#undef PACKAGE_BUGREPORT +#undef PACKAGE_NAME +#undef PACKAGE_STRING +#undef PACKAGE_TARNAME +#undef PACKAGE_VERSION +#include <mysql/my_global.h> +#include <mysql/my_sys.h> +#include <mysql/mysql.h> +#include <mysql/errmsg.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; + } + +#ifdef HAVE_BROKEN_LIBMYSQLCLIENT + my_init(); +#else + MY_INIT("snmptrapd"); +#endif + + /** load .my.cnf values */ + load_defaults ("my", _sql.groups, ¬_argc, ¬_argv); + for(i=0; i < not_argc; ++i) { + if (NULL == not_argv[i]) + continue; + if (strncmp(not_argv[i],"--password=",11) == 0) + _sql.password = ¬_argv[i][11]; + else if (strncmp(not_argv[i],"--host=",7) == 0) + _sql.host_name = ¬_argv[i][7]; + else if (strncmp(not_argv[i],"--user=",7) == 0) + _sql.user_name = ¬_argv[i][7]; + else if (strncmp(not_argv[i],"--port=",7) == 0) + _sql.port_num = atoi(¬_argv[i][7]); + else if (strncmp(not_argv[i],"--socket=",9) == 0) + _sql.socket_name = ¬_argv[i][9]; + 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; + } + + /** 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 = sizeof(sqlb->host); + 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, 0, 0, 0); + sqlvb->val_len = buf_val_len_t; +#else + memdup(&sqlvb->val, 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, 0); + 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 */ |