/* * 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 /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, "# (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; } /** @} */