blob: 31676742cd769fa5370e386b9562092e1360aa78 [file] [log] [blame]
/*
* read_config.c
*/
/* Portions of this file are subject to the following copyright(s). See
* the Net-SNMP's COPYING file for more details and other copyrights
* that may apply:
*/
/*
* Portions of this file are copyrighted by:
* Copyright © 2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms specified in the COPYING file
* distributed with the Net-SNMP package.
*/
/** @defgroup read_config parsing various configuration files at run time
* @ingroup library
*
* The read_config related functions are a fairly extensible system of
* parsing various configuration files at the run time.
*
* The idea is that the calling application is able to register
* handlers for certain tokens specified in certain types
* of files. The read_configs function can then be called
* to look for all the files that it has registrations for,
* find the first word on each line, and pass the remainder
* to the appropriately registered handler.
*
* For persistent configuration storage you will need to use the
* read_config_read_data, read_config_store, and read_config_store_data
* APIs in conjunction with first registering a
* callback so when the agent shutsdown for whatever reason data is written
* to your configuration files. The following explains in more detail the
* sequence to make this happen.
*
* This is the callback registration API, you need to call this API with
* the appropriate parameters in order to configure persistent storage needs.
*
* int snmp_register_callback(int major, int minor,
* SNMPCallback *new_callback,
* void *arg);
*
* You will need to set major to SNMP_CALLBACK_LIBRARY, minor to
* SNMP_CALLBACK_STORE_DATA. arg is whatever you want.
*
* Your callback function's prototype is:
* int (SNMPCallback) (int majorID, int minorID, void *serverarg,
* void *clientarg);
*
* The majorID, minorID and clientarg are what you passed in the callback
* registration above. When the callback is called you have to essentially
* transfer all your state from memory to disk. You do this by generating
* configuration lines into a buffer. The lines are of the form token
* followed by token parameters.
*
* Finally storing is done using read_config_store(type, buffer);
* type is the application name this can be obtained from:
*
* netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_APPTYPE);
*
* Now, reading back the data: This is done by registering a config handler
* for your token using the register_config_handler function. Your
* handler will be invoked and you can parse in the data using the
* read_config_read APIs.
*
* @{
*/
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-features.h>
#include <stdio.h>
#include <ctype.h>
#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 <sys/types.h>
#if HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#include <errno.h>
#if HAVE_DIRENT_H
# include <dirent.h>
# define NAMLEN(dirent) strlen((dirent)->d_name)
#else
# define dirent direct
# define NAMLEN(dirent) (dirent)->d_namlen
# if HAVE_SYS_NDIR_H
# include <sys/ndir.h>
# endif
# if HAVE_SYS_DIR_H
# include <sys/dir.h>
# endif
# if HAVE_NDIR_H
# include <ndir.h>
# endif
#endif
#if HAVE_DMALLOC_H
#include <dmalloc.h>
#endif
#include <net-snmp/types.h>
#include <net-snmp/output_api.h>
#include <net-snmp/config_api.h>
#include <net-snmp/library/read_config.h> /* for "internal" definitions */
#include <net-snmp/utilities.h>
#include <net-snmp/library/mib.h>
#include <net-snmp/library/parse.h>
#include <net-snmp/library/snmp_api.h>
#include <net-snmp/library/callback.h>
netsnmp_feature_child_of(read_config_all, libnetsnmp)
netsnmp_feature_child_of(unregister_app_config_handler, read_config_all)
netsnmp_feature_child_of(read_config_register_app_prenetsnmp_mib_handler, netsnmp_unused)
netsnmp_feature_child_of(read_config_register_const_config_handler, netsnmp_unused)
static int config_errors;
struct config_files *config_files = NULL;
static struct config_line *
internal_register_config_handler(const char *type_param,
const char *token,
void (*parser) (const char *, char *),
void (*releaser) (void), const char *help,
int when)
{
struct config_files **ctmp = &config_files;
struct config_line **ltmp;
const char *type = type_param;
if (type == NULL || *type == '\0') {
type = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE);
}
/*
* Handle multiple types (recursively)
*/
if (strchr(type, ':')) {
struct config_line *ltmp2 = NULL;
char buf[STRINGMAX];
char *cptr = buf;
strlcpy(buf, type, STRINGMAX);
while (cptr) {
char* c = cptr;
cptr = strchr(cptr, ':');
if(cptr) {
*cptr = '\0';
++cptr;
}
ltmp2 = internal_register_config_handler(c, token, parser,
releaser, help, when);
}
return ltmp2;
}
/*
* Find type in current list -OR- create a new file type.
*/
while (*ctmp != NULL && strcmp((*ctmp)->fileHeader, type)) {
ctmp = &((*ctmp)->next);
}
if (*ctmp == NULL) {
*ctmp = (struct config_files *)
calloc(1, sizeof(struct config_files));
if (!*ctmp) {
return NULL;
}
(*ctmp)->fileHeader = strdup(type);
DEBUGMSGTL(("9:read_config:type", "new type %s\n", type));
}
DEBUGMSGTL(("9:read_config:register_handler", "registering %s %s\n",
type, token));
/*
* Find parser type in current list -OR- create a new
* line parser entry.
*/
ltmp = &((*ctmp)->start);
while (*ltmp != NULL && strcmp((*ltmp)->config_token, token)) {
ltmp = &((*ltmp)->next);
}
if (*ltmp == NULL) {
*ltmp = (struct config_line *)
calloc(1, sizeof(struct config_line));
if (!*ltmp) {
return NULL;
}
(*ltmp)->config_time = when;
(*ltmp)->config_token = strdup(token);
if (help != NULL)
(*ltmp)->help = strdup(help);
}
/*
* Add/Replace the parse/free functions for the given line type
* in the given file type.
*/
(*ltmp)->parse_line = parser;
(*ltmp)->free_func = releaser;
return (*ltmp);
} /* end register_config_handler() */
struct config_line *
register_prenetsnmp_mib_handler(const char *type,
const char *token,
void (*parser) (const char *, char *),
void (*releaser) (void), const char *help)
{
return internal_register_config_handler(type, token, parser, releaser,
help, PREMIB_CONFIG);
}
#ifndef NETSNMP_FEATURE_REMOVE_READ_CONFIG_REGISTER_APP_PRENETSNMP_MIB_HANDLER
struct config_line *
register_app_prenetsnmp_mib_handler(const char *token,
void (*parser) (const char *, char *),
void (*releaser) (void),
const char *help)
{
return (register_prenetsnmp_mib_handler
(NULL, token, parser, releaser, help));
}
#endif /* NETSNMP_FEATURE_REMOVE_READ_CONFIG_REGISTER_APP_PRENETSNMP_MIB_HANDLER */
/**
* register_config_handler registers handlers for certain tokens specified in
* certain types of files.
*
* Allows a module writer use/register multiple configuration files based off
* of the type parameter. A module writer may want to set up multiple
* configuration files to separate out related tasks/variables or just for
* management of where to put tokens as the module or modules get more complex
* in regard to handling token registrations.
*
* @param type the configuration file used, e.g., if snmp.conf is the
* file where the token is located use "snmp" here.
* Multiple colon separated tokens might be used.
* If NULL or "" then the configuration file used will be
* \<application\>.conf.
*
* @param token the token being parsed from the file. Must be non-NULL.
*
* @param parser the handler function pointer that use the specified
* token and the rest of the line to do whatever is required
* Should be non-NULL in order to make use of this API.
*
* @param releaser if non-NULL, the function specified is called when
* unregistering config handler or when configuration
* files are re-read.
* This function should free any resources allocated by
* the token handler function.
*
* @param help if non-NULL, used to display help information on the
* expected arguments after the token.
*
* @return Pointer to a new config line entry or NULL on error.
*/
struct config_line *
register_config_handler(const char *type,
const char *token,
void (*parser) (const char *, char *),
void (*releaser) (void), const char *help)
{
return internal_register_config_handler(type, token, parser, releaser,
help, NORMAL_CONFIG);
}
#ifndef NETSNMP_FEATURE_REMOVE_READ_CONFIG_REGISTER_CONST_CONFIG_HANDLER
struct config_line *
register_const_config_handler(const char *type,
const char *token,
void (*parser) (const char *, const char *),
void (*releaser) (void), const char *help)
{
return internal_register_config_handler(type, token,
(void(*)(const char *, char *))
parser, releaser,
help, NORMAL_CONFIG);
}
#endif /* NETSNMP_FEATURE_REMOVE_READ_CONFIG_REGISTER_CONST_CONFIG_HANDLER */
struct config_line *
register_app_config_handler(const char *token,
void (*parser) (const char *, char *),
void (*releaser) (void), const char *help)
{
return (register_config_handler(NULL, token, parser, releaser, help));
}
/**
* uregister_config_handler un-registers handlers given a specific type_param
* and token.
*
* @param type_param the configuration file used where the token is located.
* Used to lookup the config file entry
*
* @param token the token that is being unregistered
*
* @return void
*/
void
unregister_config_handler(const char *type_param, const char *token)
{
struct config_files **ctmp = &config_files;
struct config_line **ltmp;
const char *type = type_param;
if (type == NULL || *type == '\0') {
type = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE);
}
/*
* Handle multiple types (recursively)
*/
if (strchr(type, ':')) {
char buf[STRINGMAX];
char *cptr = buf;
strlcpy(buf, type, STRINGMAX);
while (cptr) {
char* c = cptr;
cptr = strchr(cptr, ':');
if(cptr) {
*cptr = '\0';
++cptr;
}
unregister_config_handler(c, token);
}
return;
}
/*
* find type in current list
*/
while (*ctmp != NULL && strcmp((*ctmp)->fileHeader, type)) {
ctmp = &((*ctmp)->next);
}
if (*ctmp == NULL) {
/*
* Not found, return.
*/
return;
}
ltmp = &((*ctmp)->start);
if (*ltmp == NULL) {
/*
* Not found, return.
*/
return;
}
if (strcmp((*ltmp)->config_token, token) == 0) {
/*
* found it at the top of the list
*/
struct config_line *ltmp2 = (*ltmp)->next;
if ((*ltmp)->free_func)
(*ltmp)->free_func();
SNMP_FREE((*ltmp)->config_token);
SNMP_FREE((*ltmp)->help);
SNMP_FREE(*ltmp);
(*ctmp)->start = ltmp2;
return;
}
while ((*ltmp)->next != NULL
&& strcmp((*ltmp)->next->config_token, token)) {
ltmp = &((*ltmp)->next);
}
if ((*ltmp)->next != NULL) {
struct config_line *ltmp2 = (*ltmp)->next->next;
if ((*ltmp)->next->free_func)
(*ltmp)->next->free_func();
SNMP_FREE((*ltmp)->next->config_token);
SNMP_FREE((*ltmp)->next->help);
SNMP_FREE((*ltmp)->next);
(*ltmp)->next = ltmp2;
}
}
#ifndef NETSNMP_FEATURE_REMOVE_UNREGISTER_APP_CONFIG_HANDLER
void
unregister_app_config_handler(const char *token)
{
unregister_config_handler(NULL, token);
}
#endif /* NETSNMP_FEATURE_REMOVE_UNREGISTER_APP_CONFIG_HANDLER */
void
unregister_all_config_handlers(void)
{
struct config_files *ctmp, *save;
struct config_line *ltmp;
/*
* Keep using config_files until there are no more!
*/
for (ctmp = config_files; ctmp;) {
for (ltmp = ctmp->start; ltmp; ltmp = ctmp->start) {
unregister_config_handler(ctmp->fileHeader,
ltmp->config_token);
}
SNMP_FREE(ctmp->fileHeader);
save = ctmp->next;
SNMP_FREE(ctmp);
ctmp = save;
config_files = save;
}
}
#ifdef TESTING
void
print_config_handlers(void)
{
struct config_files *ctmp = config_files;
struct config_line *ltmp;
for (; ctmp != NULL; ctmp = ctmp->next) {
DEBUGMSGTL(("read_config", "read_conf: %s\n", ctmp->fileHeader));
for (ltmp = ctmp->start; ltmp != NULL; ltmp = ltmp->next)
DEBUGMSGTL(("read_config", " %s\n",
ltmp->config_token));
}
}
#endif
static unsigned int linecount;
static const char *curfilename;
struct config_line *
read_config_get_handlers(const char *type)
{
struct config_files *ctmp = config_files;
for (; ctmp != NULL && strcmp(ctmp->fileHeader, type);
ctmp = ctmp->next);
if (ctmp)
return ctmp->start;
return NULL;
}
int
read_config_with_type_when(const char *filename, const char *type, int when)
{
struct config_line *ctmp = read_config_get_handlers(type);
if (ctmp)
return read_config(filename, ctmp, when);
else
DEBUGMSGTL(("read_config",
"read_config: I have no registrations for type:%s,file:%s\n",
type, filename));
return SNMPERR_GENERR; /* No config files read */
}
int
read_config_with_type(const char *filename, const char *type)
{
return read_config_with_type_when(filename, type, EITHER_CONFIG);
}
struct config_line *
read_config_find_handler(struct config_line *line_handlers,
const char *token)
{
struct config_line *lptr;
for (lptr = line_handlers; lptr != NULL; lptr = lptr->next) {
if (!strcasecmp(token, lptr->config_token)) {
return lptr;
}
}
return NULL;
}
/*
* searches a config_line linked list for a match
*/
int
run_config_handler(struct config_line *lptr,
const char *token, char *cptr, int when)
{
char *cp;
lptr = read_config_find_handler(lptr, token);
if (lptr != NULL) {
if (when == EITHER_CONFIG || lptr->config_time == when) {
char tmpbuf[1];
DEBUGMSGTL(("read_config:parser",
"Found a parser. Calling it: %s / %s\n", token,
cptr));
/*
* Make sure cptr is non-null
*/
if (!cptr) {
tmpbuf[0] = '\0';
cptr = tmpbuf;
}
/*
* Stomp on any trailing whitespace
*/
cp = &(cptr[strlen(cptr)-1]);
while ((cp > cptr) && isspace((unsigned char)(*cp))) {
*(cp--) = '\0';
}
(*(lptr->parse_line)) (token, cptr);
}
else
DEBUGMSGTL(("9:read_config:parser",
"%s handler not registered for this time\n", token));
} else if (when != PREMIB_CONFIG &&
!netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_NO_TOKEN_WARNINGS)) {
netsnmp_config_warn("Unknown token: %s.", token);
return SNMPERR_GENERR;
}
return SNMPERR_SUCCESS;
}
/*
* takens an arbitrary string and tries to intepret it based on the
* known configuration handlers for all registered types. May produce
* inconsistent results when multiple tokens of the same name are
* registered under different file types.
*/
/*
* we allow = delimeters here
*/
#define SNMP_CONFIG_DELIMETERS " \t="
int
snmp_config_when(char *line, int when)
{
char *cptr, buf[STRINGMAX];
struct config_line *lptr = NULL;
struct config_files *ctmp = config_files;
char *st;
if (line == NULL) {
config_perror("snmp_config() called with a null string.");
return SNMPERR_GENERR;
}
strlcpy(buf, line, STRINGMAX);
cptr = strtok_r(buf, SNMP_CONFIG_DELIMETERS, &st);
if (!cptr) {
netsnmp_config_warn("Wrong format: %s", line);
return SNMPERR_GENERR;
}
if (cptr[0] == '[') {
if (cptr[strlen(cptr) - 1] != ']') {
netsnmp_config_error("no matching ']' for type %s.", cptr + 1);
return SNMPERR_GENERR;
}
cptr[strlen(cptr) - 1] = '\0';
lptr = read_config_get_handlers(cptr + 1);
if (lptr == NULL) {
netsnmp_config_error("No handlers regestered for type %s.",
cptr + 1);
return SNMPERR_GENERR;
}
cptr = strtok_r(NULL, SNMP_CONFIG_DELIMETERS, &st);
lptr = read_config_find_handler(lptr, cptr);
} else {
/*
* we have to find a token
*/
for (; ctmp != NULL && lptr == NULL; ctmp = ctmp->next)
lptr = read_config_find_handler(ctmp->start, cptr);
}
if (lptr == NULL && netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_NO_TOKEN_WARNINGS)) {
netsnmp_config_warn("Unknown token: %s.", cptr);
return SNMPERR_GENERR;
}
/*
* use the original string instead since strtok_r messed up the original
*/
line = skip_white(line + (cptr - buf) + strlen(cptr) + 1);
return (run_config_handler(lptr, cptr, line, when));
}
int
netsnmp_config(char *line)
{
int ret = SNMP_ERR_NOERROR;
DEBUGMSGTL(("snmp_config", "remembering line \"%s\"\n", line));
netsnmp_config_remember(line); /* always remember it so it's read
* processed after a free_config()
* call */
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_HAVE_READ_CONFIG)) {
DEBUGMSGTL(("snmp_config", " ... processing it now\n"));
ret = snmp_config_when(line, NORMAL_CONFIG);
}
return ret;
}
void
netsnmp_config_remember_in_list(char *line,
struct read_config_memory **mem)
{
if (mem == NULL)
return;
while (*mem != NULL)
mem = &((*mem)->next);
*mem = SNMP_MALLOC_STRUCT(read_config_memory);
if (*mem != NULL) {
if (line)
(*mem)->line = strdup(line);
}
}
void
netsnmp_config_remember_free_list(struct read_config_memory **mem)
{
struct read_config_memory *tmpmem;
while (*mem) {
SNMP_FREE((*mem)->line);
tmpmem = (*mem)->next;
SNMP_FREE(*mem);
*mem = tmpmem;
}
}
void
netsnmp_config_process_memory_list(struct read_config_memory **memp,
int when, int clear)
{
struct read_config_memory *mem;
if (!memp)
return;
mem = *memp;
while (mem) {
DEBUGMSGTL(("read_config:mem", "processing memory: %s\n", mem->line));
snmp_config_when(mem->line, when);
mem = mem->next;
}
if (clear)
netsnmp_config_remember_free_list(memp);
}
/*
* default storage location implementation
*/
static struct read_config_memory *memorylist = NULL;
void
netsnmp_config_remember(char *line)
{
netsnmp_config_remember_in_list(line, &memorylist);
}
void
netsnmp_config_process_memories(void)
{
netsnmp_config_process_memory_list(&memorylist, EITHER_CONFIG, 1);
}
void
netsnmp_config_process_memories_when(int when, int clear)
{
netsnmp_config_process_memory_list(&memorylist, when, clear);
}
/*******************************************************************-o-******
* read_config
*
* Parameters:
* *filename
* *line_handler
* when
*
* Read <filename> and process each line in accordance with the list of
* <line_handler> functions.
*
*
* For each line in <filename>, search the list of <line_handler>'s
* for an entry that matches the first token on the line. This comparison is
* case insensitive.
*
* For each match, check that <when> is the designated time for the
* <line_handler> function to be executed before processing the line.
*
* Returns SNMPERR_SUCCESS if the file is processed successfully.
* Returns SNMPERR_GENERR if it cannot.
* Note that individual config token errors do not trigger SNMPERR_GENERR
* It's only if the whole file cannot be processed for some reason.
*/
int
read_config(const char *filename,
struct config_line *line_handler, int when)
{
static int depth = 0;
static int files = 0;
const char * const prev_filename = curfilename;
const unsigned int prev_linecount = linecount;
FILE *ifile;
char *line = NULL; /* current line buffer */
size_t linesize = 0; /* allocated size of line */
/* reset file counter when recursion depth is 0 */
if (depth == 0)
files = 0;
if ((ifile = fopen(filename, "r")) == NULL) {
#ifdef ENOENT
if (errno == ENOENT) {
DEBUGMSGTL(("read_config", "%s: %s\n", filename,
strerror(errno)));
} else
#endif /* ENOENT */
#ifdef EACCES
if (errno == EACCES) {
DEBUGMSGTL(("read_config", "%s: %s\n", filename,
strerror(errno)));
} else
#endif /* EACCES */
{
snmp_log_perror(filename);
}
return SNMPERR_GENERR;
}
#define CONFIG_MAX_FILES 4096
if (files > CONFIG_MAX_FILES) {
netsnmp_config_error("maximum conf file count (%d) exceeded\n",
CONFIG_MAX_FILES);
fclose(ifile);
return SNMPERR_GENERR;
}
#define CONFIG_MAX_RECURSE_DEPTH 16
if (depth > CONFIG_MAX_RECURSE_DEPTH) {
netsnmp_config_error("nested include depth > %d\n",
CONFIG_MAX_RECURSE_DEPTH);
fclose(ifile);
return SNMPERR_GENERR;
}
linecount = 0;
curfilename = filename;
++files;
++depth;
DEBUGMSGTL(("read_config:file", "Reading configuration %s (%d)\n",
filename, when));
while (ifile) {
size_t linelen = 0; /* strlen of the current line */
char *cptr;
struct config_line *lptr = line_handler;
for (;;) {
if (linesize <= linelen + 1) {
char *tmp = realloc(line, linesize + 256);
if (tmp) {
line = tmp;
linesize += 256;
} else {
netsnmp_config_error("Failed to allocate memory\n");
free(line);
fclose(ifile);
return SNMPERR_GENERR;
}
}
if (fgets(line + linelen, linesize - linelen, ifile) == NULL) {
line[linelen] = '\0';
fclose (ifile);
ifile = NULL;
break;
}
linelen += strlen(line + linelen);
if (line[linelen - 1] == '\n') {
line[linelen - 1] = '\0';
break;
}
}
++linecount;
DEBUGMSGTL(("9:read_config:line", "%s:%d examining: %s\n",
filename, linecount, line));
/*
* check blank line or # comment
*/
if ((cptr = skip_white(line))) {
char token[STRINGMAX];
cptr = copy_nword(cptr, token, sizeof(token));
if (token[0] == '[') {
if (token[strlen(token) - 1] != ']') {
netsnmp_config_error("no matching ']' for type %s.",
&token[1]);
continue;
}
token[strlen(token) - 1] = '\0';
lptr = read_config_get_handlers(&token[1]);
if (lptr == NULL) {
netsnmp_config_error("No handlers regestered for type %s.",
&token[1]);
continue;
}
DEBUGMSGTL(("read_config:context",
"Switching to new context: %s%s\n",
((cptr) ? "(this line only) " : ""),
&token[1]));
if (cptr == NULL) {
/*
* change context permanently
*/
line_handler = lptr;
continue;
} else {
/*
* the rest of this line only applies.
*/
cptr = copy_nword(cptr, token, sizeof(token));
}
} else if ((token[0] == 'i') && (strncasecmp(token,"include", 7 )==0)) {
if ( strcasecmp( token, "include" )==0) {
if (when != PREMIB_CONFIG &&
!netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_NO_TOKEN_WARNINGS)) {
netsnmp_config_warn("Ambiguous token '%s' - use 'includeSearch' (or 'includeFile') instead.", token);
}
continue;
} else if ( strcasecmp( token, "includedir" )==0) {
DIR *d;
struct dirent *entry;
char fname[SNMP_MAXPATH];
int len;
if (cptr == NULL) {
if (when != PREMIB_CONFIG)
netsnmp_config_error("Blank line following %s token.", token);
continue;
}
if ((d=opendir(cptr)) == NULL ) {
if (when != PREMIB_CONFIG)
netsnmp_config_error("Can't open include dir '%s'.", cptr);
continue;
}
while ((entry = readdir( d )) != NULL ) {
if ( entry->d_name[0] != '.') {
len = NAMLEN(entry);
if ((len > 5) && (strcmp(&(entry->d_name[len-5]),".conf") == 0)) {
snprintf(fname, SNMP_MAXPATH, "%s/%s",
cptr, entry->d_name);
(void)read_config(fname, line_handler, when);
}
}
}
closedir(d);
continue;
} else if ( strcasecmp( token, "includefile" )==0) {
char fname[SNMP_MAXPATH], *cp;
if (cptr == NULL) {
if (when != PREMIB_CONFIG)
netsnmp_config_error("Blank line following %s token.", token);
continue;
}
if ( cptr[0] == '/' ) {
strlcpy(fname, cptr, SNMP_MAXPATH);
} else {
strlcpy(fname, filename, SNMP_MAXPATH);
cp = strrchr(fname, '/');
if (!cp)
fname[0] = '\0';
else
*(++cp) = '\0';
strlcat(fname, cptr, SNMP_MAXPATH);
}
if (read_config(fname, line_handler, when) !=
SNMPERR_SUCCESS && when != PREMIB_CONFIG)
netsnmp_config_error("Included file '%s' not found.",
fname);
continue;
} else if ( strcasecmp( token, "includesearch" )==0) {
struct config_files ctmp;
int len, ret;
if (cptr == NULL) {
if (when != PREMIB_CONFIG)
netsnmp_config_error("Blank line following %s token.", token);
continue;
}
len = strlen(cptr);
ctmp.fileHeader = cptr;
ctmp.start = line_handler;
ctmp.next = NULL;
if ((len > 5) && (strcmp(&cptr[len-5],".conf") == 0))
cptr[len-5] = 0; /* chop off .conf */
ret = read_config_files_of_type(when,&ctmp);
if ((len > 5) && (cptr[len-5] == 0))
cptr[len-5] = '.'; /* restore .conf */
if (( ret != SNMPERR_SUCCESS ) && (when != PREMIB_CONFIG))
netsnmp_config_error("Included config '%s' not found.", cptr);
continue;
} else {
lptr = line_handler;
}
} else {
lptr = line_handler;
}
if (cptr == NULL) {
netsnmp_config_error("Blank line following %s token.", token);
} else {
DEBUGMSGTL(("read_config:line", "%s:%d examining: %s\n",
filename, linecount, line));
run_config_handler(lptr, token, cptr, when);
}
}
}
free(line);
linecount = prev_linecount;
curfilename = prev_filename;
--depth;
return SNMPERR_SUCCESS;
} /* end read_config() */
void
free_config(void)
{
struct config_files *ctmp = config_files;
struct config_line *ltmp;
for (; ctmp != NULL; ctmp = ctmp->next)
for (ltmp = ctmp->start; ltmp != NULL; ltmp = ltmp->next)
if (ltmp->free_func)
(*(ltmp->free_func)) ();
}
/*
* Return SNMPERR_SUCCESS if any config files are processed
* Return SNMPERR_GENERR if _no_ config files are processed
* Whether this is actually an error is left to the application
*/
int
read_configs_optional(const char *optional_config, int when)
{
char *newp, *cp, *st = NULL;
int ret = SNMPERR_GENERR;
char *type = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE);
if ((NULL == optional_config) || (NULL == type))
return ret;
DEBUGMSGTL(("read_configs_optional",
"reading optional configuration tokens for %s\n", type));
newp = strdup(optional_config); /* strtok_r messes it up */
cp = strtok_r(newp, ",", &st);
while (cp) {
struct stat statbuf;
if (stat(cp, &statbuf)) {
DEBUGMSGTL(("read_config",
"Optional File \"%s\" does not exist.\n", cp));
snmp_log_perror(cp);
} else {
DEBUGMSGTL(("read_config:opt",
"Reading optional config file: \"%s\"\n", cp));
if ( read_config_with_type_when(cp, type, when) == SNMPERR_SUCCESS )
ret = SNMPERR_SUCCESS;
}
cp = strtok_r(NULL, ",", &st);
}
free(newp);
return ret;
}
void
read_configs(void)
{
char *optional_config = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_OPTIONALCONFIG);
snmp_call_callbacks(SNMP_CALLBACK_LIBRARY,
SNMP_CALLBACK_PRE_READ_CONFIG, NULL);
DEBUGMSGTL(("read_config", "reading normal configuration tokens\n"));
if ((NULL != optional_config) && (*optional_config == '-')) {
(void)read_configs_optional(++optional_config, NORMAL_CONFIG);
optional_config = NULL; /* clear, so we don't read them twice */
}
(void)read_config_files(NORMAL_CONFIG);
/*
* do this even when the normal above wasn't done
*/
if (NULL != optional_config)
(void)read_configs_optional(optional_config, NORMAL_CONFIG);
netsnmp_config_process_memories_when(NORMAL_CONFIG, 1);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_HAVE_READ_CONFIG, 1);
snmp_call_callbacks(SNMP_CALLBACK_LIBRARY,
SNMP_CALLBACK_POST_READ_CONFIG, NULL);
}
void
read_premib_configs(void)
{
char *optional_config = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_OPTIONALCONFIG);
snmp_call_callbacks(SNMP_CALLBACK_LIBRARY,
SNMP_CALLBACK_PRE_PREMIB_READ_CONFIG, NULL);
DEBUGMSGTL(("read_config", "reading premib configuration tokens\n"));
if ((NULL != optional_config) && (*optional_config == '-')) {
(void)read_configs_optional(++optional_config, PREMIB_CONFIG);
optional_config = NULL; /* clear, so we don't read them twice */
}
(void)read_config_files(PREMIB_CONFIG);
if (NULL != optional_config)
(void)read_configs_optional(optional_config, PREMIB_CONFIG);
netsnmp_config_process_memories_when(PREMIB_CONFIG, 0);
netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_HAVE_READ_PREMIB_CONFIG, 1);
snmp_call_callbacks(SNMP_CALLBACK_LIBRARY,
SNMP_CALLBACK_POST_PREMIB_READ_CONFIG, NULL);
}
/*******************************************************************-o-******
* set_configuration_directory
*
* Parameters:
* char *dir - value of the directory
* Sets the configuration directory. Multiple directories can be
* specified, but need to be seperated by 'ENV_SEPARATOR_CHAR'.
*/
void
set_configuration_directory(const char *dir)
{
netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CONFIGURATION_DIR, dir);
}
/*******************************************************************-o-******
* get_configuration_directory
*
* Parameters: -
* Retrieve the configuration directory or directories.
* (For backwards compatibility that is:
* SNMPCONFPATH, SNMPSHAREPATH, SNMPLIBPATH, HOME/.snmp
* First check whether the value is set.
* If not set give it the default value.
* Return the value.
* We always retrieve it new, since we have to do it anyway if it is just set.
*/
const char *
get_configuration_directory(void)
{
char defaultPath[SPRINT_MAX_LEN];
char *homepath;
if (NULL == netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CONFIGURATION_DIR)) {
homepath = netsnmp_getenv("HOME");
snprintf(defaultPath, sizeof(defaultPath), "%s%c%s%c%s%s%s%s",
SNMPCONFPATH, ENV_SEPARATOR_CHAR,
SNMPSHAREPATH, ENV_SEPARATOR_CHAR, SNMPLIBPATH,
((homepath == NULL) ? "" : ENV_SEPARATOR),
((homepath == NULL) ? "" : homepath),
((homepath == NULL) ? "" : "/.snmp"));
defaultPath[ sizeof(defaultPath)-1 ] = 0;
set_configuration_directory(defaultPath);
}
return (netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CONFIGURATION_DIR));
}
/*******************************************************************-o-******
* set_persistent_directory
*
* Parameters:
* char *dir - value of the directory
* Sets the configuration directory.
* No multiple directories may be specified.
* (However, this is not checked)
*/
void
set_persistent_directory(const char *dir)
{
netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_PERSISTENT_DIR, dir);
}
/*******************************************************************-o-******
* get_persistent_directory
*
* Parameters: -
* Function will retrieve the persisten directory value.
* First check whether the value is set.
* If not set give it the default value.
* Return the value.
* We always retrieve it new, since we have to do it anyway if it is just set.
*/
const char *
get_persistent_directory(void)
{
if (NULL == netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_PERSISTENT_DIR)) {
const char *persdir = netsnmp_getenv("SNMP_PERSISTENT_DIR");
if (NULL == persdir)
persdir = NETSNMP_PERSISTENT_DIRECTORY;
set_persistent_directory(persdir);
}
return (netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_PERSISTENT_DIR));
}
/*******************************************************************-o-******
* set_temp_file_pattern
*
* Parameters:
* char *pattern - value of the file pattern
* Sets the temp file pattern.
* Multiple patterns may not be specified.
* (However, this is not checked)
*/
void
set_temp_file_pattern(const char *pattern)
{
netsnmp_ds_set_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_TEMP_FILE_PATTERN, pattern);
}
/*******************************************************************-o-******
* get_temp_file_pattern
*
* Parameters: -
* Function will retrieve the temp file pattern value.
* First check whether the value is set.
* If not set give it the default value.
* Return the value.
* We always retrieve it new, since we have to do it anyway if it is just set.
*/
const char *
get_temp_file_pattern(void)
{
if (NULL == netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_TEMP_FILE_PATTERN)) {
set_temp_file_pattern(NETSNMP_TEMP_FILE_PATTERN);
}
return (netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_TEMP_FILE_PATTERN));
}
/**
* utility routine for read_config_files
*
* Return SNMPERR_SUCCESS if any config files are processed
* Return SNMPERR_GENERR if _no_ config files are processed
* Whether this is actually an error is left to the application
*/
static int
read_config_files_in_path(const char *path, struct config_files *ctmp,
int when, const char *perspath, const char *persfile)
{
int done, j;
char configfile[300];
char *cptr1, *cptr2, *envconfpath;
struct stat statbuf;
int ret = SNMPERR_GENERR;
if ((NULL == path) || (NULL == ctmp))
return SNMPERR_GENERR;
envconfpath = strdup(path);
DEBUGMSGTL(("read_config:path", " config path used for %s:%s (persistent path:%s)\n",
ctmp->fileHeader, envconfpath, perspath));
cptr1 = cptr2 = envconfpath;
done = 0;
while ((!done) && (*cptr2 != 0)) {
while (*cptr1 != 0 && *cptr1 != ENV_SEPARATOR_CHAR)
cptr1++;
if (*cptr1 == 0)
done = 1;
else
*cptr1 = 0;
DEBUGMSGTL(("read_config:dir", " config dir: %s\n", cptr2 ));
if (stat(cptr2, &statbuf) != 0) {
/*
* Directory not there, continue
*/
DEBUGMSGTL(("read_config:dir", " Directory not present: %s\n", cptr2 ));
cptr2 = ++cptr1;
continue;
}
#ifdef S_ISDIR
if (!S_ISDIR(statbuf.st_mode)) {
/*
* Not a directory, continue
*/
DEBUGMSGTL(("read_config:dir", " Not a directory: %s\n", cptr2 ));
cptr2 = ++cptr1;
continue;
}
#endif
/*
* for proper persistent storage retrieval, we need to read old backup
* copies of the previous storage files. If the application in
* question has died without the proper call to snmp_clean_persistent,
* then we read all the configuration files we can, starting with
* the oldest first.
*/
if (strncmp(cptr2, perspath, strlen(perspath)) == 0 ||
(persfile != NULL &&
strncmp(cptr2, persfile, strlen(persfile)) == 0)) {
DEBUGMSGTL(("read_config:persist", " persist dir: %s\n", cptr2 ));
/*
* limit this to the known storage directory only
*/
for (j = 0; j <= NETSNMP_MAX_PERSISTENT_BACKUPS; j++) {
snprintf(configfile, sizeof(configfile),
"%s/%s.%d.conf", cptr2,
ctmp->fileHeader, j);
configfile[ sizeof(configfile)-1 ] = 0;
if (stat(configfile, &statbuf) != 0) {
/*
* file not there, continue
*/
break;
} else {
/*
* backup exists, read it
*/
DEBUGMSGTL(("read_config_files",
"old config file found: %s, parsing\n",
configfile));
if (read_config(configfile, ctmp->start, when) == SNMPERR_SUCCESS)
ret = SNMPERR_SUCCESS;
}
}
}
snprintf(configfile, sizeof(configfile),
"%s/%s.conf", cptr2, ctmp->fileHeader);
configfile[ sizeof(configfile)-1 ] = 0;
if (read_config(configfile, ctmp->start, when) == SNMPERR_SUCCESS)
ret = SNMPERR_SUCCESS;
snprintf(configfile, sizeof(configfile),
"%s/%s.local.conf", cptr2, ctmp->fileHeader);
configfile[ sizeof(configfile)-1 ] = 0;
if (read_config(configfile, ctmp->start, when) == SNMPERR_SUCCESS)
ret = SNMPERR_SUCCESS;
if(done)
break;
cptr2 = ++cptr1;
}
SNMP_FREE(envconfpath);
return ret;
}
/*******************************************************************-o-******
* read_config_files
*
* Parameters:
* when == PREMIB_CONFIG, NORMAL_CONFIG -or- EITHER_CONFIG
*
*
* Traverse the list of config file types, performing the following actions
* for each --
*
* First, build a search path for config files. If the contents of
* environment variable SNMPCONFPATH are NULL, then use the following
* path list (where the last entry exists only if HOME is non-null):
*
* SNMPSHAREPATH:SNMPLIBPATH:${HOME}/.snmp
*
* Then, In each of these directories, read config files by the name of:
*
* <dir>/<fileHeader>.conf -AND-
* <dir>/<fileHeader>.local.conf
*
* where <fileHeader> is taken from the config file type structure.
*
*
* PREMIB_CONFIG causes free_config() to be invoked prior to any other action.
*
*
* EXITs if any 'config_errors' are logged while parsing config file lines.
*
* Return SNMPERR_SUCCESS if any config files are processed
* Return SNMPERR_GENERR if _no_ config files are processed
* Whether this is actually an error is left to the application
*/
int
read_config_files_of_type(int when, struct config_files *ctmp)
{
const char *confpath, *persfile, *envconfpath;
char *perspath;
int ret = SNMPERR_GENERR;
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_PERSIST_STATE)
|| netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DISABLE_CONFIG_LOAD)
|| (NULL == ctmp)) return ret;
/*
* these shouldn't change
*/
confpath = get_configuration_directory();
persfile = netsnmp_getenv("SNMP_PERSISTENT_FILE");
envconfpath = netsnmp_getenv("SNMPCONFPATH");
/*
* read the config files. strdup() the result of
* get_persistent_directory() to avoid that parsing the "persistentDir"
* keyword transforms the perspath pointer into a dangling pointer.
*/
perspath = strdup(get_persistent_directory());
if (envconfpath == NULL) {
/*
* read just the config files (no persistent stuff), since
* persistent path can change via conf file. Then get the
* current persistent directory, and read files there.
*/
if ( read_config_files_in_path(confpath, ctmp, when, perspath,
persfile) == SNMPERR_SUCCESS )
ret = SNMPERR_SUCCESS;
free(perspath);
perspath = strdup(get_persistent_directory());
if ( read_config_files_in_path(perspath, ctmp, when, perspath,
persfile) == SNMPERR_SUCCESS )
ret = SNMPERR_SUCCESS;
}
else {
/*
* only read path specified by user
*/
if ( read_config_files_in_path(envconfpath, ctmp, when, perspath,
persfile) == SNMPERR_SUCCESS )
ret = SNMPERR_SUCCESS;
}
free(perspath);
return ret;
}
/*
* Return SNMPERR_SUCCESS if any config files are processed
* Return SNMPERR_GENERR if _no_ config files are processed
* Whether this is actually an error is left to the application
*/
int
read_config_files(int when) {
struct config_files *ctmp = config_files;
int ret = SNMPERR_GENERR;
config_errors = 0;
if (when == PREMIB_CONFIG)
free_config();
/*
* read all config file types
*/
for (; ctmp != NULL; ctmp = ctmp->next) {
if ( read_config_files_of_type(when, ctmp) == SNMPERR_SUCCESS )
ret = SNMPERR_SUCCESS;
}
if (config_errors) {
snmp_log(LOG_ERR, "net-snmp: %d error(s) in config file(s)\n",
config_errors);
}
return ret;
}
void
read_config_print_usage(const char *lead)
{
struct config_files *ctmp = config_files;
struct config_line *ltmp;
if (lead == NULL)
lead = "";
for (ctmp = config_files; ctmp != NULL; ctmp = ctmp->next) {
snmp_log(LOG_INFO, "%sIn %s.conf and %s.local.conf:\n", lead,
ctmp->fileHeader, ctmp->fileHeader);
for (ltmp = ctmp->start; ltmp != NULL; ltmp = ltmp->next) {
DEBUGIF("read_config_usage") {
if (ltmp->config_time == PREMIB_CONFIG)
DEBUGMSG(("read_config_usage", "*"));
else
DEBUGMSG(("read_config_usage", " "));
}
if (ltmp->help) {
snmp_log(LOG_INFO, "%s%s%-24s %s\n", lead, lead,
ltmp->config_token, ltmp->help);
} else {
DEBUGIF("read_config_usage") {
snmp_log(LOG_INFO, "%s%s%-24s [NO HELP]\n", lead, lead,
ltmp->config_token);
}
}
}
}
}
/**
* read_config_store intended for use by applications to store permenant
* configuration information generated by sets or persistent counters.
* Appends line to a file named either ENV(SNMP_PERSISTENT_FILE) or
* "<NETSNMP_PERSISTENT_DIRECTORY>/<type>.conf".
* Adds a trailing newline to the stored file if necessary.
*
* @param type is the application name
* @param line is the configuration line written to the application name's
* configuration file
*
* @return void
*/
void
read_config_store(const char *type, const char *line)
{
#ifdef NETSNMP_PERSISTENT_DIRECTORY
char file[512], *filep;
FILE *fout;
#ifdef NETSNMP_PERSISTENT_MASK
mode_t oldmask;
#endif
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_PERSIST_STATE)
|| netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DISABLE_PERSISTENT_LOAD)) return;
/*
* store configuration directives in the following order of preference:
* 1. ENV variable SNMP_PERSISTENT_FILE
* 2. configured <NETSNMP_PERSISTENT_DIRECTORY>/<type>.conf
*/
if ((filep = netsnmp_getenv("SNMP_PERSISTENT_FILE")) == NULL) {
snprintf(file, sizeof(file),
"%s/%s.conf", get_persistent_directory(), type);
file[ sizeof(file)-1 ] = 0;
filep = file;
}
#ifdef NETSNMP_PERSISTENT_MASK
oldmask = umask(NETSNMP_PERSISTENT_MASK);
#endif
if (mkdirhier(filep, NETSNMP_AGENT_DIRECTORY_MODE, 1)) {
snmp_log(LOG_ERR,
"Failed to create the persistent directory for %s\n",
file);
}
if ((fout = fopen(filep, "a")) != NULL) {
fprintf(fout, "%s", line);
if (line[strlen(line)] != '\n')
fprintf(fout, "\n");
DEBUGMSGTL(("read_config:store", "storing: %s\n", line));
fclose(fout);
} else {
snmp_log(LOG_ERR, "read_config_store open failure on %s\n", filep);
}
#ifdef NETSNMP_PERSISTENT_MASK
umask(oldmask);
#endif
#endif
} /* end read_config_store() */
void
read_app_config_store(const char *line)
{
read_config_store(netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_APPTYPE), line);
}
/*******************************************************************-o-******
* snmp_save_persistent
*
* Parameters:
* *type
*
*
* Save the file "<NETSNMP_PERSISTENT_DIRECTORY>/<type>.conf" into a backup copy
* called "<NETSNMP_PERSISTENT_DIRECTORY>/<type>.%d.conf", which %d is an
* incrementing number on each call, but less than NETSNMP_MAX_PERSISTENT_BACKUPS.
*
* Should be called just before all persistent information is supposed to be
* written to move aside the existing persistent cache.
* snmp_clean_persistent should then be called afterward all data has been
* saved to remove these backup files.
*
* Note: on an rename error, the files are removed rather than saved.
*
*/
void
snmp_save_persistent(const char *type)
{
char file[512], fileold[SPRINT_MAX_LEN];
struct stat statbuf;
int j;
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_PERSIST_STATE)
|| netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE)) return;
DEBUGMSGTL(("snmp_save_persistent", "saving %s files...\n", type));
snprintf(file, sizeof(file),
"%s/%s.conf", get_persistent_directory(), type);
file[ sizeof(file)-1 ] = 0;
if (stat(file, &statbuf) == 0) {
for (j = 0; j <= NETSNMP_MAX_PERSISTENT_BACKUPS; j++) {
snprintf(fileold, sizeof(fileold),
"%s/%s.%d.conf", get_persistent_directory(), type, j);
fileold[ sizeof(fileold)-1 ] = 0;
if (stat(fileold, &statbuf) != 0) {
DEBUGMSGTL(("snmp_save_persistent",
" saving old config file: %s -> %s.\n", file,
fileold));
if (rename(file, fileold)) {
snmp_log(LOG_ERR, "Cannot rename %s to %s\n", file, fileold);
/* moving it failed, try nuking it, as leaving
* it around is very bad. */
if (unlink(file) == -1)
snmp_log(LOG_ERR, "Cannot unlink %s\n", file);
}
break;
}
}
}
/*
* save a warning header to the top of the new file
*/
snprintf(fileold, sizeof(fileold),
"%s%s# Please save normal configuration tokens for %s in SNMPCONFPATH/%s.conf.\n# Only \"createUser\" tokens should be placed here by %s administrators.\n%s",
"#\n# net-snmp (or ucd-snmp) persistent data file.\n#\n############################################################################\n# STOP STOP STOP STOP STOP STOP STOP STOP STOP \n",
"#\n# **** DO NOT EDIT THIS FILE ****\n#\n# STOP STOP STOP STOP STOP STOP STOP STOP STOP \n############################################################################\n#\n# DO NOT STORE CONFIGURATION ENTRIES HERE.\n",
type, type, type,
"# (Did I mention: do not edit this file?)\n#\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
fileold[ sizeof(fileold)-1 ] = 0;
read_config_store(type, fileold);
}
/*******************************************************************-o-******
* snmp_clean_persistent
*
* Parameters:
* *type
*
*
* Unlink all backup files called "<NETSNMP_PERSISTENT_DIRECTORY>/<type>.%d.conf".
*
* Should be called just after we successfull dumped the last of the
* persistent data, to remove the backup copies of previous storage dumps.
*
* XXX Worth overwriting with random bytes first? This would
* ensure that the data is destroyed, even a buffer containing the
* data persists in memory or swap. Only important if secrets
* will be stored here.
*/
void
snmp_clean_persistent(const char *type)
{
char file[512];
struct stat statbuf;
int j;
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DONT_PERSIST_STATE)
|| netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_DISABLE_PERSISTENT_SAVE)) return;
DEBUGMSGTL(("snmp_clean_persistent", "cleaning %s files...\n", type));
snprintf(file, sizeof(file),
"%s/%s.conf", get_persistent_directory(), type);
file[ sizeof(file)-1 ] = 0;
if (stat(file, &statbuf) == 0) {
for (j = 0; j <= NETSNMP_MAX_PERSISTENT_BACKUPS; j++) {
snprintf(file, sizeof(file),
"%s/%s.%d.conf", get_persistent_directory(), type, j);
file[ sizeof(file)-1 ] = 0;
if (stat(file, &statbuf) == 0) {
DEBUGMSGTL(("snmp_clean_persistent",
" removing old config file: %s\n", file));
if (unlink(file) == -1)
snmp_log(LOG_ERR, "Cannot unlink %s\n", file);
}
}
}
}
/*
* config_perror: prints a warning string associated with a file and
* line number of a .conf file and increments the error count.
*/
static void
config_vlog(int level, const char *levelmsg, const char *str, va_list args)
{
char tmpbuf[256];
char* buf = tmpbuf;
int len = snprintf(tmpbuf, sizeof(tmpbuf), "%s: line %d: %s: %s\n",
curfilename, linecount, levelmsg, str);
if (len >= (int)sizeof(tmpbuf)) {
buf = (char*)malloc(len + 1);
sprintf(buf, "%s: line %d: %s: %s\n",
curfilename, linecount, levelmsg, str);
}
snmp_vlog(level, buf, args);
if (buf != tmpbuf)
free(buf);
}
void
netsnmp_config_error(const char *str, ...)
{
va_list args;
va_start(args, str);
config_vlog(LOG_ERR, "Error", str, args);
va_end(args);
config_errors++;
}
void
netsnmp_config_warn(const char *str, ...)
{
va_list args;
va_start(args, str);
config_vlog(LOG_WARNING, "Warning", str, args);
va_end(args);
}
void
config_perror(const char *str)
{
netsnmp_config_error("%s", str);
}
void
config_pwarn(const char *str)
{
netsnmp_config_warn("%s", str);
}
/*
* skip all white spaces and return 1 if found something either end of
* line or a comment character
*/
char *
skip_white(char *ptr)
{
return NETSNMP_REMOVE_CONST(char *, skip_white_const(ptr));
}
const char *
skip_white_const(const char *ptr)
{
if (ptr == NULL)
return (NULL);
while (*ptr != 0 && isspace((unsigned char)*ptr))
ptr++;
if (*ptr == 0 || *ptr == '#')
return (NULL);
return (ptr);
}
char *
skip_not_white(char *ptr)
{
return NETSNMP_REMOVE_CONST(char *, skip_not_white_const(ptr));
}
const char *
skip_not_white_const(const char *ptr)
{
if (ptr == NULL)
return (NULL);
while (*ptr != 0 && !isspace((unsigned char)*ptr))
ptr++;
if (*ptr == 0 || *ptr == '#')
return (NULL);
return (ptr);
}
char *
skip_token(char *ptr)
{
return NETSNMP_REMOVE_CONST(char *, skip_token_const(ptr));
}
const char *
skip_token_const(const char *ptr)
{
ptr = skip_white_const(ptr);
ptr = skip_not_white_const(ptr);
ptr = skip_white_const(ptr);
return (ptr);
}
/*
* copy_word
* copies the next 'token' from 'from' into 'to', maximum len-1 characters.
* currently a token is anything seperate by white space
* or within quotes (double or single) (i.e. "the red rose"
* is one token, \"the red rose\" is three tokens)
* a '\' character will allow a quote character to be treated
* as a regular character
* It returns a pointer to first non-white space after the end of the token
* being copied or to 0 if we reach the end.
* Note: Partially copied words (greater than len) still returns a !NULL ptr
* Note: partially copied words are, however, null terminated.
*/
char *
copy_nword(char *from, char *to, int len)
{
return NETSNMP_REMOVE_CONST(char *, copy_nword_const(from, to, len));
}
const char *
copy_nword_const(const char *from, char *to, int len)
{
char quote;
if (!from || !to)
return NULL;
if ((*from == '\"') || (*from == '\'')) {
quote = *(from++);
while ((*from != quote) && (*from != 0)) {
if ((*from == '\\') && (*(from + 1) != 0)) {
if (len > 0) { /* don't copy beyond len bytes */
*to++ = *(from + 1);
if (--len == 0)
*(to - 1) = '\0'; /* null protect the last spot */
}
from = from + 2;
} else {
if (len > 0) { /* don't copy beyond len bytes */
*to++ = *from++;
if (--len == 0)
*(to - 1) = '\0'; /* null protect the last spot */
} else
from++;
}
}
if (*from == 0) {
DEBUGMSGTL(("read_config_copy_word",
"no end quote found in config string\n"));
} else
from++;
} else {
while (*from != 0 && !isspace((unsigned char)(*from))) {
if ((*from == '\\') && (*(from + 1) != 0)) {
if (len > 0) { /* don't copy beyond len bytes */
*to++ = *(from + 1);
if (--len == 0)
*(to - 1) = '\0'; /* null protect the last spot */
}
from = from + 2;
} else {
if (len > 0) { /* don't copy beyond len bytes */
*to++ = *from++;
if (--len == 0)
*(to - 1) = '\0'; /* null protect the last spot */
} else
from++;
}
}
}
if (len > 0)
*to = 0;
from = skip_white_const(from);
return (from);
} /* copy_nword */
/*
* copy_word
* copies the next 'token' from 'from' into 'to'.
* currently a token is anything seperate by white space
* or within quotes (double or single) (i.e. "the red rose"
* is one token, \"the red rose\" is three tokens)
* a '\' character will allow a quote character to be treated
* as a regular character
* It returns a pointer to first non-white space after the end of the token
* being copied or to 0 if we reach the end.
*/
static int have_warned = 0;
char *
copy_word(char *from, char *to)
{
if (!have_warned) {
snmp_log(LOG_INFO,
"copy_word() called. Use copy_nword() instead.\n");
have_warned = 1;
}
return copy_nword(from, to, SPRINT_MAX_LEN);
} /* copy_word */
/**
* Stores an quoted version of the first len bytes from str into saveto.
*
* If all octets in str are in the set [[:alnum:] ] then the quotation
* is to enclose the string in quotation marks ("str") otherwise the
* quotation is to prepend the string 0x and then add the hex representation
* of all characters from str (0x737472)
*
* @param[in] saveto pointer to output stream, is assumed to be big enough.
* @param[in] str pointer of the data that is to be stored.
* @param[in] len length of the data that is to be stored.
* @return A pointer to saveto after str is added to it.
*/
char *
read_config_save_octet_string(char *saveto, const u_char * str, size_t len)
{
size_t i;
const u_char *cp;
/*
* is everything easily printable
*/
for (i = 0, cp = str; i < len && cp &&
(isalpha(*cp) || isdigit(*cp) || *cp == ' '); cp++, i++);
if (len != 0 && i == len) {
*saveto++ = '"';
memcpy(saveto, str, len);
saveto += len;
*saveto++ = '"';
*saveto = '\0';
} else {
if (str != NULL) {
sprintf(saveto, "0x");
saveto += 2;
for (i = 0; i < len; i++) {
sprintf(saveto, "%02x", str[i]);
saveto = saveto + 2;
}
} else {
sprintf(saveto, "\"\"");
saveto += 2;
}
}
return saveto;
}
/**
* Reads an octet string that was saved by the
* read_config_save_octet_string() function.
*
* @param[in] readfrom Pointer to the input data to be parsed.
* @param[in,out] str Pointer to the output buffer pointer. The data
* written to the output buffer will be '\0'-terminated. If *str == NULL,
* an output buffer will be allocated that is one byte larger than the
* data stored.
* @param[in,out] len If str != NULL, *len is the size of the buffer *str
* points at. If str == NULL, the value passed via *len is ignored.
* Before this function returns the number of bytes read will be stored
* in *len. If a buffer overflow occurs, *len will be set to 0.
*
* @return A pointer to the next character in the input to be parsed if
* parsing succeeded; NULL when the end of the input string has been reached
* or if an error occurred.
*/
char *
read_config_read_octet_string(const char *readfrom, u_char ** str,
size_t * len)
{
return NETSNMP_REMOVE_CONST(char *,
read_config_read_octet_string_const(readfrom, str, len));
}
const char *
read_config_read_octet_string_const(const char *readfrom, u_char ** str,
size_t * len)
{
u_char *cptr;
const char *cptr1;
u_int tmp;
size_t i, ilen;
if (readfrom == NULL || str == NULL || len == NULL)
return NULL;
if (strncasecmp(readfrom, "0x", 2) == 0) {
/*
* A hex string submitted. How long?
*/
readfrom += 2;
cptr1 = skip_not_white_const(readfrom);
if (cptr1)
ilen = (cptr1 - readfrom);
else
ilen = strlen(readfrom);
if (ilen % 2) {
snmp_log(LOG_WARNING,"invalid hex string: wrong length\n");
DEBUGMSGTL(("read_config_read_octet_string",
"invalid hex string: wrong length"));
return NULL;
}
ilen = ilen / 2;
/*
* malloc data space if needed (+1 for good measure)
*/
if (*str == NULL) {
*str = (u_char *) malloc(ilen + 1);
if (!*str)
return NULL;
} else {
/*
* require caller to have +1, and bail if not enough space.
*/
if (ilen >= *len) {
snmp_log(LOG_WARNING,"buffer too small to read octet string (%lu < %lu)\n",
(unsigned long)*len, (unsigned long)ilen);
DEBUGMSGTL(("read_config_read_octet_string",
"buffer too small (%lu < %lu)", (unsigned long)*len, (unsigned long)ilen));
*len = 0;
cptr1 = skip_not_white_const(readfrom);
return skip_white_const(cptr1);
}
}
/*
* copy validated data
*/
cptr = *str;
for (i = 0; i < ilen; i++) {
if (1 == sscanf(readfrom, "%2x", &tmp))
*cptr++ = (u_char) tmp;
else {
/*
* we may lose memory, but don't know caller's buffer XX free(cptr);
*/
return (NULL);
}
readfrom += 2;
}
/*
* Terminate the output buffer.
*/
*cptr++ = '\0';
*len = ilen;
readfrom = skip_white_const(readfrom);
} else {
/*
* Normal string
*/
/*
* malloc string space if needed (including NULL terminator)
*/
if (*str == NULL) {
char buf[SNMP_MAXBUF];
readfrom = copy_nword_const(readfrom, buf, sizeof(buf));
*len = strlen(buf);
*str = (u_char *) malloc(*len + 1);
if (*str == NULL)
return NULL;
memcpy(*str, buf, *len + 1);
} else {
readfrom = copy_nword_const(readfrom, (char *) *str, *len);
if (*len)
*len = strlen((char *) *str);
}
}
return readfrom;
}
/*
* read_config_save_objid(): saves an objid as a numerical string
*/
char *
read_config_save_objid(char *saveto, oid * objid, size_t len)
{
int i;
if (len == 0) {
strcat(saveto, "NULL");
saveto += strlen(saveto);
return saveto;
}
/*
* in case len=0, this makes it easier to read it back in
*/
for (i = 0; i < (int) len; i++) {
sprintf(saveto, ".%" NETSNMP_PRIo "d", objid[i]);
saveto += strlen(saveto);
}
return saveto;
}
/*
* read_config_read_objid(): reads an objid from a format saved by the above
*/
char *
read_config_read_objid(char *readfrom, oid ** objid, size_t * len)
{
return NETSNMP_REMOVE_CONST(char *,
read_config_read_objid_const(readfrom, objid, len));
}
const char *
read_config_read_objid_const(const char *readfrom, oid ** objid, size_t * len)
{
if (objid == NULL || readfrom == NULL || len == NULL)
return NULL;
if (*objid == NULL) {
*len = 0;
if ((*objid = (oid *) malloc(MAX_OID_LEN * sizeof(oid))) == NULL)
return NULL;
*len = MAX_OID_LEN;
}
if (strncmp(readfrom, "NULL", 4) == 0) {
/*
* null length oid
*/
*len = 0;
} else {
/*
* qualify the string for read_objid
*/
char buf[SPRINT_MAX_LEN];
copy_nword_const(readfrom, buf, sizeof(buf));
if (!read_objid(buf, *objid, len)) {
DEBUGMSGTL(("read_config_read_objid", "Invalid OID"));
*len = 0;
return NULL;
}
}
readfrom = skip_token_const(readfrom);
return readfrom;
}
/**
* read_config_read_data reads data of a given type from a token(s) on a
* configuration line. The supported types are:
*
* - ASN_INTEGER
* - ASN_TIMETICKS
* - ASN_UNSIGNED
* - ASN_OCTET_STR
* - ASN_BIT_STR
* - ASN_OBJECT_ID
*
* @param type the asn data type to be read in.
*
* @param readfrom the configuration line data to be read.
*
* @param dataptr an allocated pointer expected to match the type being read
* (int *, u_int *, char **, oid **)
*
* @param len is the length of an asn oid or octet/bit string, not required
* for the asn integer, unsigned integer, and timeticks types
*
* @return the next token in the configuration line. NULL if none left or
* if an unknown type.
*
*/
char *
read_config_read_data(int type, char *readfrom, void *dataptr,
size_t * len)
{
int *intp;
char **charpp;
oid **oidpp;
unsigned int *uintp;
if (dataptr && readfrom)
switch (type) {
case ASN_INTEGER:
intp = (int *) dataptr;
*intp = atoi(readfrom);
readfrom = skip_token(readfrom);
return readfrom;
case ASN_TIMETICKS:
case ASN_UNSIGNED:
uintp = (unsigned int *) dataptr;
*uintp = strtoul(readfrom, NULL, 0);
readfrom = skip_token(readfrom);
return readfrom;
case ASN_IPADDRESS:
intp = (int *) dataptr;
*intp = inet_addr(readfrom);
if ((*intp == -1) &&
(strncmp(readfrom, "255.255.255.255", 15) != 0))
return NULL;
readfrom = skip_token(readfrom);
return readfrom;
case ASN_OCTET_STR:
case ASN_BIT_STR:
charpp = (char **) dataptr;
return read_config_read_octet_string(readfrom,
(u_char **) charpp, len);
case ASN_OBJECT_ID:
oidpp = (oid **) dataptr;
return read_config_read_objid(readfrom, oidpp, len);
default:
DEBUGMSGTL(("read_config_read_data", "Fail: Unknown type: %d",
type));
return NULL;
}
return NULL;
}
/*
* read_config_read_memory():
*
* similar to read_config_read_data, but expects a generic memory
* pointer rather than a specific type of pointer. Len is expected to
* be the amount of available memory.
*/
char *
read_config_read_memory(int type, char *readfrom,
char *dataptr, size_t * len)
{
int *intp;
unsigned int *uintp;
char buf[SPRINT_MAX_LEN];
if (!dataptr || !readfrom)
return NULL;
switch (type) {
case ASN_INTEGER:
if (*len < sizeof(int))
return NULL;
intp = (int *) dataptr;
readfrom = copy_nword(readfrom, buf, sizeof(buf));
*intp = atoi(buf);
*len = sizeof(int);
return readfrom;
case ASN_COUNTER:
case ASN_TIMETICKS:
case ASN_UNSIGNED:
if (*len < sizeof(unsigned int))
return NULL;
uintp = (unsigned int *) dataptr;
readfrom = copy_nword(readfrom, buf, sizeof(buf));
*uintp = strtoul(buf, NULL, 0);
*len = sizeof(unsigned int);
return readfrom;
case ASN_IPADDRESS:
if (*len < sizeof(int))
return NULL;
intp = (int *) dataptr;
readfrom = copy_nword(readfrom, buf, sizeof(buf));
*intp = inet_addr(buf);
if ((*intp == -1) && (strcmp(buf, "255.255.255.255") != 0))
return NULL;
*len = sizeof(int);
return readfrom;
case ASN_OCTET_STR:
case ASN_BIT_STR:
case ASN_PRIV_IMPLIED_OCTET_STR:
return read_config_read_octet_string(readfrom,
(u_char **) & dataptr, len);
case ASN_PRIV_IMPLIED_OBJECT_ID:
case ASN_OBJECT_ID:
readfrom =
read_config_read_objid(readfrom, (oid **) & dataptr, len);
*len *= sizeof(oid);
return readfrom;
case ASN_COUNTER64:
if (*len < sizeof(U64))
return NULL;
*len = sizeof(U64);
read64((U64 *) dataptr, readfrom);
readfrom = skip_token(readfrom);
return readfrom;
}
DEBUGMSGTL(("read_config_read_memory", "Fail: Unknown type: %d", type));
return NULL;
}
/**
* read_config_store_data stores data of a given type to a configuration line
* into the storeto buffer.
* Calls read_config_store_data_prefix with the prefix parameter set to a char
* space. The supported types are:
*
* - ASN_INTEGER
* - ASN_TIMETICKS
* - ASN_UNSIGNED
* - ASN_OCTET_STR
* - ASN_BIT_STR
* - ASN_OBJECT_ID
*
* @param type the asn data type to be stored
*
* @param storeto a pre-allocated char buffer which will contain the data
* to be stored
*
* @param dataptr contains the value to be stored, the supported pointers:
* (int *, u_int *, char **, oid **)
*
* @param len is the length of the value to be stored
* (not required for the asn integer, unsigned integer,
* and timeticks types)
*
* @return character pointer to the end of the line. NULL if an unknown type.
*/
char *
read_config_store_data(int type, char *storeto, void *dataptr, size_t * len)
{
return read_config_store_data_prefix(' ', type, storeto, dataptr,
(len ? *len : 0));
}
char *
read_config_store_data_prefix(char prefix, int type, char *storeto,
void *dataptr, size_t len)
{
int *intp;
u_char **charpp;
unsigned int *uintp;
struct in_addr in;
oid **oidpp;
if (dataptr && storeto)
switch (type) {
case ASN_INTEGER:
intp = (int *) dataptr;
sprintf(storeto, "%c%d", prefix, *intp);
return (storeto + strlen(storeto));
case ASN_TIMETICKS:
case ASN_UNSIGNED:
uintp = (unsigned int *) dataptr;
sprintf(storeto, "%c%u", prefix, *uintp);
return (storeto + strlen(storeto));
case ASN_IPADDRESS:
in.s_addr = *(unsigned int *) dataptr;
sprintf(storeto, "%c%s", prefix, inet_ntoa(in));
return (storeto + strlen(storeto));
case ASN_OCTET_STR:
case ASN_BIT_STR:
*storeto++ = prefix;
charpp = (u_char **) dataptr;
return read_config_save_octet_string(storeto, *charpp, len);
case ASN_OBJECT_ID:
*storeto++ = prefix;
oidpp = (oid **) dataptr;
return read_config_save_objid(storeto, *oidpp, len);
default:
DEBUGMSGTL(("read_config_store_data_prefix",
"Fail: Unknown type: %d", type));
return NULL;
}
return NULL;
}
/** @} */