net-snmp/agent/mibgroup/ucd-snmp/pass_persist.c

831 lines
24 KiB
C

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#if HAVE_IO_H
#include <io.h>
#endif
#if HAVE_STDLIB_H
#include <stdlib.h>
#endif
#if HAVE_STRING_H
#include <string.h>
#else
#include <strings.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <ctype.h>
#include <sys/types.h>
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_SYS_WAIT_H
# include <sys/wait.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#ifdef WIN32
#include <limits.h>
#endif
#include <signal.h>
#include <errno.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "struct.h"
#include "pass_persist.h"
#include "pass_common.h"
#include "extensible.h"
#include "util_funcs.h"
netsnmp_feature_require(get_exten_instance)
netsnmp_feature_require(parse_miboid)
struct extensible *persistpassthrus = NULL;
int numpersistpassthrus = 0;
struct persist_pipe_type {
FILE *fIn, *fOut;
int fdIn, fdOut;
netsnmp_pid_t pid;
} *persist_pipes = (struct persist_pipe_type *) NULL;
static unsigned pipe_check_alarm_id;
static int init_persist_pipes(void);
static void close_persist_pipe(int iindex);
static int open_persist_pipe(int iindex, char *command);
static void check_persist_pipes(unsigned clientreg, void *clientarg);
static void destruct_persist_pipes(void);
static int write_persist_pipe(int iindex, const char *data);
/*
* the relocatable extensible commands variables
*/
struct variable2 extensible_persist_passthru_variables[] = {
/*
* bogus entry. Only some of it is actually used.
*/
{MIBINDEX, ASN_INTEGER, NETSNMP_OLDAPI_RWRITE,
var_extensible_pass_persist, 0, {MIBINDEX}},
};
void
init_pass_persist(void)
{
snmpd_register_config_handler("pass_persist",
pass_persist_parse_config,
pass_persist_free_config,
"miboid program");
pipe_check_alarm_id = snmp_alarm_register(10, SA_REPEAT, check_persist_pipes, NULL);
}
void
shutdown_pass_persist(void)
{
if (pipe_check_alarm_id) {
snmp_alarm_unregister(pipe_check_alarm_id);
pipe_check_alarm_id = 0;
}
/* Close any open pipes. */
destruct_persist_pipes();
}
#ifdef USING_SINGLE_COMMON_PASSPERSIST_INSTANCE
void
pass_persist_group(struct extensible *persistpassthrus)
{
struct extensible *ptmp, *ptmp1;
/*
* reset groupping
*/
for (ptmp = persistpassthrus; ptmp != NULL; ptmp = ptmp->next) {
ptmp->passpersist_inst = NULL;
}
/*
* group
*/
for (ptmp = persistpassthrus; ptmp != NULL; ptmp = ptmp->next) {
/* skip already groupped items */
if (ptmp->passpersist_inst != NULL) {
continue;
}
for (ptmp1 = persistpassthrus; ptmp1 != NULL; ptmp1 = ptmp1->next) {
if (ptmp1 == ptmp) {
continue;
}
if (strcmp(ptmp->command, ptmp1->command) == 0) {
ptmp1->passpersist_inst = ptmp;
}
}
}
}
#endif /* USING_SINGLE_COMMON_PASSPERSIST_INSTANCE */
void
pass_persist_parse_config(const char *token, char *cptr)
{
struct extensible **ppass = &persistpassthrus, **etmp, *ptmp;
char *tcptr, *endopt;
int i;
long int priority;
/*
* options
*/
priority = DEFAULT_MIB_PRIORITY;
while (*cptr == '-') {
cptr++;
switch (*cptr) {
case 'p':
/* change priority level */
cptr++;
cptr = skip_white(cptr);
if (! isdigit((unsigned char)(*cptr))) {
config_perror("priority must be an integer");
return;
}
priority = strtol((const char*) cptr, &endopt, 0);
if ((priority == LONG_MIN) || (priority == LONG_MAX)) {
config_perror("priority under/overflow");
return;
}
cptr = endopt;
cptr = skip_white(cptr);
break;
default:
config_perror("unknown option for pass directive");
return;
}
}
/*
* MIB
*/
if (*cptr == '.')
cptr++;
if (!isdigit((unsigned char)(*cptr))) {
config_perror("second token is not a OID");
return;
}
numpersistpassthrus++;
while (*ppass != NULL)
ppass = &((*ppass)->next);
*ppass = calloc(1, sizeof(**ppass));
if (*ppass == NULL)
return;
(*ppass)->type = PASSTHRU_PERSIST;
(*ppass)->mibpriority = priority;
(*ppass)->miblen = parse_miboid(cptr, (*ppass)->miboid);
while (isdigit((unsigned char)(*cptr)) || *cptr == '.')
cptr++;
/*
* path
*/
free((*ppass)->command);
(*ppass)->command = NULL;
cptr = skip_white(cptr);
if (cptr == NULL) {
config_perror("No command specified on pass_persist line");
if (asprintf(&(*ppass)->command, "%s", "") < 0) {
}
} else {
for (tcptr = cptr; *tcptr != 0 && *tcptr != '#' && *tcptr != ';';
tcptr++);
if (asprintf(&(*ppass)->command, "%.*s", (int)(tcptr - cptr), cptr)
< 0) {
}
}
strlcpy((*ppass)->name, (*ppass)->command, sizeof((*ppass)->name));
(*ppass)->next = NULL;
register_mib_priority("pass_persist",
(struct variable *) extensible_persist_passthru_variables,
sizeof(struct variable2), 1, (*ppass)->miboid,
(*ppass)->miblen, (*ppass)->mibpriority);
/*
* argggg -- pasthrus must be sorted
*/
if (numpersistpassthrus > 1) {
etmp = (struct extensible **)
malloc(((sizeof(struct extensible *)) * numpersistpassthrus));
if (etmp == NULL)
return;
for (i = 0, ptmp = (struct extensible *) persistpassthrus;
i < numpersistpassthrus && ptmp != NULL; i++, ptmp = ptmp->next)
etmp[i] = ptmp;
qsort(etmp, numpersistpassthrus, sizeof(struct extensible *),
pass_persist_compare);
persistpassthrus = (struct extensible *) etmp[0];
ptmp = (struct extensible *) etmp[0];
for (i = 0; i < numpersistpassthrus - 1; i++) {
ptmp->next = etmp[i + 1];
ptmp = ptmp->next;
}
ptmp->next = NULL;
free(etmp);
}
#ifdef USING_SINGLE_COMMON_PASSPERSIST_INSTANCE
pass_persist_group(persistpassthrus);
#endif /* USING_SINGLE_COMMON_PASSPERSIST_INSTANCE */
}
void
pass_persist_free_config(void)
{
struct extensible *etmp, *etmp2;
int i;
for (etmp = persistpassthrus; etmp != NULL;) {
etmp2 = etmp;
etmp = etmp->next;
unregister_mib_priority(etmp2->miboid, etmp2->miblen, etmp2->mibpriority);
free(etmp2);
}
if (persist_pipes) {
for (i = 0; i <= numpersistpassthrus; i++) {
close_persist_pipe(i);
}
}
persistpassthrus = NULL;
numpersistpassthrus = 0;
}
#ifdef USING_SINGLE_COMMON_PASSPERSIST_INSTANCE
int get_exten_group_id(struct extensible *persistpassthru,
int current_id)
{
struct extensible *ptmp;
int idx;
if (persistpassthru == NULL)
return current_id;
for (idx = 1, ptmp = persistpassthrus;
ptmp != NULL; ptmp = ptmp->next, idx++) {
if (ptmp == persistpassthru) {
return idx;
}
}
/* should never really come here, but safety doesn't hurt */
return current_id;
}
#endif /* USING_SINGLE_COMMON_PASSPERSIST_INSTANCE */
u_char *
var_extensible_pass_persist(struct variable *vp,
oid * name,
size_t * length,
int exact,
size_t * var_len, WriteMethod ** write_method)
{
oid newname[MAX_OID_LEN];
int i, rtest, newlen;
char buf[SNMP_MAXBUF];
static char buf2[SNMP_MAXBUF];
struct extensible *persistpassthru;
FILE *file;
int pipe_idx;
/*
* Make sure that our basic pipe structure is malloced
*/
init_persist_pipes();
for (i = 1; i <= numpersistpassthrus; i++) {
persistpassthru = get_exten_instance(persistpassthrus, i);
rtest = snmp_oidtree_compare(name, *length,
persistpassthru->miboid,
persistpassthru->miblen);
if ((exact && rtest == 0) || (!exact && rtest <= 0)) {
/*
* setup args
*/
if (persistpassthru->miblen >= *length || rtest < 0)
sprint_mib_oid(buf, persistpassthru->miboid,
persistpassthru->miblen);
else
sprint_mib_oid(buf, name, *length);
pipe_idx = i;
#ifdef USING_SINGLE_COMMON_PASSPERSIST_INSTANCE
pipe_idx =
get_exten_group_id(persistpassthru->passpersist_inst, i);
if (pipe_idx != i) {
persistpassthru = persistpassthru->passpersist_inst;
}
#endif /* USING_SINGLE_COMMON_PASSPERSIST_INSTANCE */
/*
* Open our pipe if necessary
*/
if (!open_persist_pipe(pipe_idx, persistpassthru->name)) {
return (NULL);
}
free(persistpassthru->command);
if (asprintf(&persistpassthru->command, "%s\n%s\n",
exact ? "get" : "getnext", buf) < 0) {
persistpassthru->command = NULL;
*var_len = 0;
return NULL;
}
DEBUGMSGTL(("ucd-snmp/pass_persist",
"persistpass-sending:\n%s",
persistpassthru->command));
if (!write_persist_pipe(pipe_idx, persistpassthru->command)) {
*var_len = 0;
/*
* close_persist_pipes is called in write_persist_pipe
*/
return (NULL);
}
/*
* valid call. Exec and get output
*/
if ((file = persist_pipes[pipe_idx].fIn)) {
if (fgets(buf, sizeof(buf), file) == NULL) {
*var_len = 0;
close_persist_pipe(pipe_idx);
return (NULL);
}
/*
* persistent scripts return "NONE\n" on invalid items
*/
if (!strncmp(buf, "NONE", 4)) {
if (exact) {
*var_len = 0;
return (NULL);
}
continue;
}
newlen = parse_miboid(buf, newname);
/*
* its good, so copy onto name/length
*/
memcpy((char *) name, (char *) newname,
(int) newlen * sizeof(oid));
*length = newlen;
/*
* set up return pointer for setable stuff
*/
*write_method = setPassPersist;
if (newlen == 0 || fgets(buf, sizeof(buf), file) == NULL
|| fgets(buf2, sizeof(buf2), file) == NULL) {
*var_len = 0;
close_persist_pipe(pipe_idx);
return (NULL);
}
return netsnmp_internal_pass_parse(buf, buf2, var_len, vp);
}
*var_len = 0;
return (NULL);
}
}
if (var_len)
*var_len = 0;
*write_method = NULL;
return (NULL);
}
int
setPassPersist(int action,
u_char * var_val,
u_char var_val_type,
size_t var_val_len,
u_char * statP, oid * name, size_t name_len)
{
int i, rtest;
struct extensible *persistpassthru;
char buf[SNMP_MAXBUF], buf2[SNMP_MAXBUF];
int pipe_idx;
/*
* Make sure that our basic pipe structure is malloced
*/
init_persist_pipes();
for (i = 1; i <= numpersistpassthrus; i++) {
persistpassthru = get_exten_instance(persistpassthrus, i);
rtest = snmp_oidtree_compare(name, name_len,
persistpassthru->miboid,
persistpassthru->miblen);
pipe_idx = i;
#ifdef USING_SINGLE_COMMON_PASSPERSIST_INSTANCE
pipe_idx =
get_exten_group_id(persistpassthru->passpersist_inst, i);
if (pipe_idx != i) {
persistpassthru = persistpassthru->passpersist_inst;
}
#endif /* USING_SINGLE_COMMON_PASSPERSIST_INSTANCE */
if (rtest <= 0) {
if (action != ACTION)
return SNMP_ERR_NOERROR;
/*
* setup args
*/
if (persistpassthru->miblen >= name_len || rtest < 0)
sprint_mib_oid(buf, persistpassthru->miboid,
persistpassthru->miblen);
else
sprint_mib_oid(buf, name, name_len);
netsnmp_internal_pass_set_format(buf2, var_val, var_val_type,
var_val_len);
free(persistpassthru->command);
if (asprintf(&persistpassthru->command, "set\n%s\n%s\n", buf,
buf2) < 0) {
persistpassthru->command = NULL;
return SNMP_ERR_GENERR;
}
if (!open_persist_pipe(pipe_idx, persistpassthru->name)) {
return SNMP_ERR_NOTWRITABLE;
}
DEBUGMSGTL(("ucd-snmp/pass_persist",
"persistpass-writing: %s\n",
persistpassthru->command));
if (!write_persist_pipe(pipe_idx, persistpassthru->command)) {
close_persist_pipe(pipe_idx);
return SNMP_ERR_NOTWRITABLE;
}
if (fgets(buf, sizeof(buf), persist_pipes[pipe_idx].fIn) == NULL) {
close_persist_pipe(pipe_idx);
return SNMP_ERR_NOTWRITABLE;
}
return netsnmp_internal_pass_str_to_errno(buf);
}
}
if (snmp_get_do_debugging()) {
sprint_mib_oid(buf2, name, name_len);
DEBUGMSGTL(("ucd-snmp/pass_persist", "persistpass-notfound: %s\n",
buf2));
}
return SNMP_ERR_NOSUCHNAME;
}
int
pass_persist_compare(const void *a, const void *b)
{
const struct extensible *const *ap, *const *bp;
ap = (const struct extensible * const *) a;
bp = (const struct extensible * const *) b;
return snmp_oid_compare((*ap)->miboid, (*ap)->miblen, (*bp)->miboid,
(*bp)->miblen);
}
/*
* Initialize our persistent pipes
* - Returns 1 on success, 0 on failure.
* - Initializes all FILE pointers to NULL to indicate "closed"
*/
static int
init_persist_pipes(void)
{
int i;
/*
* if we are already taken care of, just return
*/
if (persist_pipes) {
return persist_pipes ? 1 : 0;
}
/*
* Otherwise malloc and initialize
*/
persist_pipes = (struct persist_pipe_type *)
malloc(sizeof(struct persist_pipe_type) *
(numpersistpassthrus + 1));
if (persist_pipes) {
for (i = 0; i <= numpersistpassthrus; i++) {
persist_pipes[i].fIn = persist_pipes[i].fOut = (FILE *) 0;
persist_pipes[i].fdIn = persist_pipes[i].fdOut = -1;
persist_pipes[i].pid = NETSNMP_NO_SUCH_PROCESS;
}
}
return persist_pipes ? 1 : 0;
}
/**
* Return true if and only if the process associated with the persistent
* pipe has stopped.
*
* @param[in] idx Persistent pipe index.
*/
static int process_stopped(int idx)
{
if (persist_pipes[idx].pid != NETSNMP_NO_SUCH_PROCESS) {
#if HAVE_SYS_WAIT_H
return waitpid(persist_pipes[idx].pid, NULL, WNOHANG) > 0;
#endif
#if defined(WIN32) && !defined (mingw32) && !defined(HAVE_SIGNAL)
return WaitForSingleObject(persist_pipes[idx].pid, 0) == WAIT_OBJECT_0;
#endif
}
return 0;
}
/**
* Iterate over all persistent pipes and close those pipes of which the
* associated process has stopped.
*/
static void check_persist_pipes(unsigned clientreg, void *clientarg)
{
int i;
if (!persist_pipes)
return;
for (i = 0; i <= numpersistpassthrus; i++) {
if (process_stopped(i)) {
snmp_log(LOG_INFO, "pass_persist[%d]: child process stopped - closing pipe\n", i);
close_persist_pipe(i);
}
}
}
/*
* Destruct our persistent pipes
*
*/
static void
destruct_persist_pipes(void)
{
int i;
/*
* Return if there are no pipes
*/
if (!persist_pipes) {
return;
}
for (i = 0; i <= numpersistpassthrus; i++) {
close_persist_pipe(i);
}
free(persist_pipes);
persist_pipes = (struct persist_pipe_type *) 0;
}
/*
* returns 0 on failure, 1 on success
*/
static int
open_persist_pipe(int iindex, char *command)
{
static int recurse = 0; /* used to allow one level of recursion */
DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe(%d,'%s') recurse=%d\n",
iindex, command, recurse));
/*
* Open if it's not already open
*/
if (persist_pipes[iindex].pid == NETSNMP_NO_SUCH_PROCESS) {
int fdIn, fdOut;
netsnmp_pid_t pid;
/*
* Did we fail?
*/
if ((0 == get_exec_pipes(command, &fdIn, &fdOut, &pid)) ||
(pid == NETSNMP_NO_SUCH_PROCESS)) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"open_persist_pipe: pid == -1\n"));
recurse = 0;
return 0;
}
/*
* If not, fill out our structure
*/
persist_pipes[iindex].pid = pid;
persist_pipes[iindex].fdIn = fdIn;
persist_pipes[iindex].fdOut = fdOut;
persist_pipes[iindex].fIn = fdopen(fdIn, "r");
persist_pipes[iindex].fOut = fdopen(fdOut, "w");
/*
* Setup our -non-buffered-io-
*/
setbuf(persist_pipes[iindex].fOut, (char *) 0);
DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: opened the pipes\n"));
}
/*
* Send test packet always so we can self-catch
*/
{
char buf[SNMP_MAXBUF];
/*
* Should catch SIGPIPE around this call!
*/
if (!write_persist_pipe(iindex, "PING\n")) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"open_persist_pipe: Error writing PING\n"));
close_persist_pipe(iindex);
/*
* Recurse one time if we get a SIGPIPE
*/
if (!recurse) {
DEBUGMSGTL(("ucd-snmp/pass_persist", "open_persist_pipe: recursing to reopen\n"));
recurse = 1;
return open_persist_pipe(iindex, command);
}
recurse = 0;
return 0;
}
if (fgets(buf, sizeof(buf), persist_pipes[iindex].fIn) == NULL) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"open_persist_pipe: Error reading for PONG\n"));
close_persist_pipe(iindex);
recurse = 0;
return 0;
}
if (strncmp(buf, "PONG", 4)) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"open_persist_pipe: Got %s instead of PONG!\n", buf));
close_persist_pipe(iindex);
recurse = 0;
return 0;
}
}
recurse = 0;
return 1;
}
static int
write_persist_pipe(int iindex, const char *data)
{
#if HAVE_SIGNAL
struct sigaction sa, osa;
int wret = 0, werrno = 0;
/*
* Don't write to a non-existant process
*/
if (persist_pipes[iindex].pid == NETSNMP_NO_SUCH_PROCESS) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"write_persist_pipe: not writing %s, process is non-existent",
data));
return 0;
}
/*
* Setup our signal action to ignore SIGPIPEs
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGPIPE, &sa, &osa)) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"write_persist_pipe: sigaction failed: %d", errno));
}
/*
* Do the write
*/
wret = write(persist_pipes[iindex].fdOut, data, strlen(data));
werrno = errno;
/*
* Reset the signal handler
*/
sigaction(SIGPIPE, &osa, (struct sigaction *) 0);
if (wret < 0) {
if (werrno != EPIPE) {
DEBUGMSGTL(("ucd-snmp/pass_persist",
"write_persist_pipe: write returned unknown error %d (%s)\n",
werrno, strerror(werrno)));
}
close_persist_pipe(iindex);
return 0;
}
#endif /* HAVE_SIGNAL */
#if defined(WIN32) && !defined (mingw32) && !defined (HAVE_SIGNAL)
/* We have no signal here (maybe we can make a Thread?) so write may block,
* but probably never will.
*/
int wret = 0, werrno = 0;
/*
* Do the write
*/
wret = write(persist_pipes[iindex].fdOut, data,strlen(data));
werrno = errno;
if (wret < 0) {
if (werrno != EINTR) {
DEBUGMSGTL(("ucd-snmp/pass_persist", "write_persist_pipe: write returned unknown error %d\n",errno));
}
close_persist_pipe(iindex);
return 0;
}
#endif /* WIN32 */
return 1;
}
static void
close_persist_pipe(int iindex)
{
/* Alexander Prömel, alexander@proemel.de 08/24/2006
The hard coded pathnames, are temporary.
I'll fix it soon.
If you changed them here, you have to do it in ../util_funcs.c too.
*/
#ifdef __uClinux__
char fifo_in_path[256];
char fifo_out_path[256];
snprintf(fifo_in_path, 256, "/flash/cp_%d", persist_pipes[iindex].pid);
snprintf(fifo_out_path, 256, "/flash/pc_%d", persist_pipes[iindex].pid);
#endif
/*
* Check and nix every item
*/
if (persist_pipes[iindex].fOut) {
fclose(persist_pipes[iindex].fOut);
persist_pipes[iindex].fOut = (FILE *) 0;
}
if (persist_pipes[iindex].fdOut != -1) {
#ifndef WIN32
/*
* The sequence open()/fdopen()/fclose()/close() triggers an access
* violation with the MSVC runtime. Hence skip the close() call when
* using the MSVC runtime.
*/
close(persist_pipes[iindex].fdOut);
#endif
persist_pipes[iindex].fdOut = -1;
}
if (persist_pipes[iindex].fIn) {
fclose(persist_pipes[iindex].fIn);
persist_pipes[iindex].fIn = (FILE *) 0;
}
if (persist_pipes[iindex].fdIn != -1) {
#ifndef WIN32
/*
* The sequence open()/fdopen()/fclose()/close() triggers an access
* violation with the MSVC runtime. Hence skip the close() call when
* using the MSVC runtime.
*/
close(persist_pipes[iindex].fdIn);
#endif
persist_pipes[iindex].fdIn = -1;
}
#ifdef __uClinux__
/*remove the pipes*/
unlink(fifo_in_path);
unlink(fifo_out_path);
#endif
if (persist_pipes[iindex].pid != NETSNMP_NO_SUCH_PROCESS) {
/*
* kill the child, in case we got an error and the child is not
* cooperating. Ignore the return code.
*/
#ifdef HAVE_SIGNAL
(void)kill(persist_pipes[iindex].pid, SIGKILL);
#endif
#if HAVE_SYS_WAIT_H
waitpid(persist_pipes[iindex].pid, NULL, 0);
#endif
#if defined(WIN32) && !defined (mingw32) && !defined (HAVE_SIGNAL)
if (!CloseHandle(persist_pipes[iindex].pid)) {
DEBUGMSGTL(("ucd-snmp/pass_persist","close_persist_pipe pid: close error\n"));
}
#endif
persist_pipes[iindex].pid = NETSNMP_NO_SUCH_PROCESS;
}
}