diff --git a/snmplib/read_config.c b/snmplib/read_config.c index b8bae0e..a1ddb6c 100644 --- a/snmplib/read_config.c +++ b/snmplib/read_config.c @@ -1642,7 +1642,7 @@ snmp_save_persistent(const char *type) * 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", + "%s%s# Please save normal configuration tokens for %s in /etc/snmp/%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, diff --git a/snmplib/read_config.c.expand-SNMPCONFPATH b/snmplib/read_config.c.expand-SNMPCONFPATH new file mode 100644 index 0000000..b8bae0e --- /dev/null +++ b/snmplib/read_config.c.expand-SNMPCONFPATH @@ -0,0 +1,2390 @@ +/* + * 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 +#include + +#include +#include +#if HAVE_STDLIB_H +#include +#endif +#if HAVE_STRING_H +#include +#else +#include +#endif +#if HAVE_UNISTD_H +#include +#endif +#include +#if HAVE_SYS_PARAM_H +#include +#endif +#if TIME_WITH_SYS_TIME +# include +# include +#else +# if HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +#ifdef HAVE_SYS_STAT_H +#include +#endif +#if HAVE_NETINET_IN_H +#include +#endif +#if HAVE_ARPA_INET_H +#include +#endif +#if HAVE_SYS_SELECT_H +#include +#endif +#if HAVE_SYS_SOCKET_H +#include +#endif +#if HAVE_NETDB_H +#include +#endif +#include +#if HAVE_IO_H +#include +#endif + +#if HAVE_DIRENT_H +# include +# define NAMLEN(dirent) strlen((dirent)->d_name) +#else +# define dirent direct +# define NAMLEN(dirent) (dirent)->d_namlen +# if HAVE_SYS_NDIR_H +# include +# endif +# if HAVE_SYS_DIR_H +# include +# endif +# if HAVE_NDIR_H +# include +# endif +#endif + +#if HAVE_DMALLOC_H +#include +#endif + +#include +#include +#include +#include /* for "internal" definitions */ +#include + +#include +#include +#include +#include + +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) + +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 + * \.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); +} + +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); +} + +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 and process each line in accordance with the list of + * functions. + * + * + * For each line in , search the list of 's + * for an entry that matches the first token on the line. This comparison is + * case insensitive. + * + * For each match, check that is the designated time for the + * 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 */ + if (!newp) + return ret; + 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: + * + * /.conf -AND- + * /.local.conf + * + * where 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 + * "/.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 /.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)); + fflush(fout); +#if defined(HAVE_FSYNC) + fsync(fileno(fout)); +#elif defined(WIN32) && !defined(cygwin) + { + int fd; + void *h; + + fd = _fileno(fout); + netsnmp_assert(fd != -1); + h = _get_osfhandle(fd); + netsnmp_assert(h != INVALID_HANDLE_VALUE); + FlushFileBuffers(h); + } +#endif + fclose(fout); + } else { + if (strcmp(NETSNMP_APPLICATION_CONFIG_TYPE, type) != 0) { + /* + * Ignore this error in client utilities, they can run with random + * UID/GID and typically cannot write to /var. Error message just + * confuses people. + */ + 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 "/.conf" into a backup copy + * called "/.%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 "/.%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(struct counter64)) + return NULL; + *len = sizeof(struct counter64); + read64((struct counter64 *) 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; +} + +/** @} */