/* cfg.c - functions for configuration information This file contains parts that were part of the nss_ldap library which has been forked into the nss-pam-ldapd library. Copyright (C) 1997-2005 Luke Howard Copyright (C) 2007 West Consulting Copyright (C) 2007-2014 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_GSSAPI_H #include #endif /* HAVE_GSSAPI_H */ #ifdef HAVE_GSSAPI_GSSAPI_H #include #endif /* HAVE_GSSAPI_GSSAPI_H */ #ifdef HAVE_GSSAPI_GSSAPI_KRB5_H #include #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */ #ifdef HAVE_GSSAPI_GSSAPI_GENERIC_H #include #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */ #include #include #include #include "common.h" #include "log.h" #include "cfg.h" #include "common/expr.h" struct nslcd_config *nslcd_cfg = NULL; /* the maximum line length in the configuration file */ #define MAX_LINE_LENGTH 4096 /* the delimiters of tokens */ #define TOKEN_DELIM " \t\n\r" /* simple strdup wrapper */ static char *xstrdup(const char *s) { char *tmp; if (s == NULL) { log_log(LOG_CRIT, "xstrdup() called with NULL"); exit(EXIT_FAILURE); } tmp = strdup(s); if (tmp == NULL) { log_log(LOG_CRIT, "strdup() failed to allocate memory"); exit(EXIT_FAILURE); } return tmp; } /* check that the condition is true and otherwise log an error and bail out */ static inline void check_argumentcount(const char *filename, int lnr, const char *keyword, int condition) { if (!condition) { log_log(LOG_ERR, "%s:%d: %s: wrong number of arguments", filename, lnr, keyword); exit(EXIT_FAILURE); } } /* This function works like strtok() except that the original string is not modified and a pointer within str to where the next token begins is returned (this can be used to pass to the function on the next iteration). If no more tokens are found or the token will not fit in the buffer, NULL is returned. */ static char *get_token(char **line, char *buf, size_t buflen) { size_t len; if ((line == NULL) || (*line == NULL) || (**line == '\0') || (buf == NULL)) return NULL; /* find the beginning and length of the token */ *line += strspn(*line, TOKEN_DELIM); len = strcspn(*line, TOKEN_DELIM); /* check if there is a token */ if (len == 0) { *line = NULL; return NULL; } /* limit the token length */ if (len >= buflen) len = buflen - 1; /* copy the token */ strncpy(buf, *line, len); buf[len] = '\0'; /* skip to the next token */ *line += len; *line += strspn(*line, TOKEN_DELIM); /* return the token */ return buf; } static char *get_linedup(const char *filename, int lnr, const char *keyword, char **line) { char *var; check_argumentcount(filename, lnr, keyword, (*line != NULL) && (**line != '\0')); var = xstrdup(*line); /* mark that we are at the end of the line */ *line = NULL; return var; } static void get_eol(const char *filename, int lnr, const char *keyword, char **line) { if ((line != NULL) && (*line != NULL) && (**line != '\0')) { log_log(LOG_ERR, "%s:%d: %s: too may arguments", filename, lnr, keyword); exit(EXIT_FAILURE); } } static int get_int(const char *filename, int lnr, const char *keyword, char **line) { char token[32]; check_argumentcount(filename, lnr, keyword, get_token(line, token, sizeof(token)) != NULL); /* TODO: replace with correct numeric parse */ return atoi(token); } static bool parse_boolean(const char *filename, int lnr, const char *value) { if ((strcasecmp(value, "on") == 0) || (strcasecmp(value, "yes") == 0) || (strcasecmp(value, "true") == 0) || (strcasecmp(value, "1") == 0)) return true; else if ((strcasecmp(value, "off") == 0) || (strcasecmp(value, "no") == 0) || (strcasecmp(value, "false") == 0) || (strcasecmp(value, "0") == 0)) return false; else { log_log(LOG_ERR, "%s:%d: not a boolean argument: '%s'", filename, lnr, value); exit(EXIT_FAILURE); } } static bool get_boolean(const char *filename, int lnr, const char *keyword, char **line) { char token[32]; check_argumentcount(filename, lnr, keyword, get_token(line, token, sizeof(token)) != NULL); return parse_boolean(filename, lnr, token); } static const char *print_boolean(bool val) { if (val) return "yes"; else return "no"; } static int parse_loglevel(const char *filename, int lnr, const char *value) { if (strcasecmp(value, "crit") == 0) return LOG_CRIT; else if ((strcasecmp(value, "error") == 0) || (strcasecmp(value, "err") == 0)) return LOG_ERR; else if (strcasecmp(value, "warning")==0) return LOG_WARNING; else if (strcasecmp(value, "notice")==0) return LOG_NOTICE; else if (strcasecmp(value, "info")==0) return LOG_INFO; else if (strcasecmp(value, "debug")==0) return LOG_DEBUG; else { log_log(LOG_ERR, "%s:%d: not a log level '%s'", filename, lnr, value); exit(EXIT_FAILURE); } } static void handle_log(const char *filename, int lnr, const char *keyword, char *line) { int level = LOG_INFO; char loglevel[32]; check_argumentcount(filename, lnr, keyword, get_token(&line, loglevel, sizeof(loglevel)) != NULL); if (get_token(&line, loglevel, sizeof(loglevel)) != NULL) level = parse_loglevel(filename, lnr, loglevel); get_eol(filename, lnr, keyword, &line); log_setdefaultloglevel(level); } static enum nss_map_selector parse_map(const char *value) { if ((strcasecmp(value, "alias") == 0) || (strcasecmp(value, "aliases") == 0)) return LM_ALIASES; else if ((strcasecmp(value, "ether") == 0) || (strcasecmp(value, "ethers") == 0)) return LM_ETHERS; else if (strcasecmp(value, "group") == 0) return LM_GROUP; else if ((strcasecmp(value, "host") == 0) || (strcasecmp(value, "hosts") == 0)) return LM_HOSTS; else if (strcasecmp(value, "netgroup") == 0) return LM_NETGROUP; else if ((strcasecmp(value, "network") == 0) || (strcasecmp(value, "networks") == 0)) return LM_NETWORKS; else if (strcasecmp(value, "passwd") == 0) return LM_PASSWD; else if ((strcasecmp(value, "protocol") == 0) || (strcasecmp(value, "protocols") == 0)) return LM_PROTOCOLS; else if (strcasecmp(value, "rpc") == 0) return LM_RPC; else if ((strcasecmp(value, "service") == 0) || (strcasecmp(value, "services") == 0)) return LM_SERVICES; else if (strcasecmp(value, "shadow") == 0) return LM_SHADOW; else if (strcasecmp(value, "nfsidmap") == 0) return LM_NFSIDMAP; /* unknown map */ return LM_NONE; } static const char *print_map(enum nss_map_selector map) { switch (map) { case LM_ALIASES: return "aliases"; case LM_ETHERS: return "ethers"; case LM_GROUP: return "group"; case LM_HOSTS: return "hosts"; case LM_NETGROUP: return "netgroup"; case LM_NETWORKS: return "networks"; case LM_PASSWD: return "passwd"; case LM_PROTOCOLS: return "protocols"; case LM_RPC: return "rpc"; case LM_SERVICES: return "services"; case LM_SHADOW: return "shadow"; case LM_NFSIDMAP: return "nfsidmap"; case LM_NONE: default: return "???"; } } /* this function modifies the line argument passed */ static void handle_nss_initgroups_ignoreusers( const char *filename, int lnr, const char *keyword, char *line, struct nslcd_config *cfg) { char token[MAX_LINE_LENGTH]; char *username, *next; struct passwd *pwent; check_argumentcount(filename, lnr, keyword, (line != NULL) && (*line != '\0')); if (cfg->nss_initgroups_ignoreusers == NULL) cfg->nss_initgroups_ignoreusers = set_new(); while (get_token(&line, token, sizeof(token)) != NULL) { if (strcasecmp(token, "alllocal") == 0) { /* go over all users (this will work because nslcd is not yet running) */ setpwent(); while ((pwent = getpwent()) != NULL) set_add(cfg->nss_initgroups_ignoreusers, pwent->pw_name); endpwent(); } else { next = token; while (*next != '\0') { username = next; /* find the end of the current username */ while ((*next != '\0') && (*next != ',')) next++; if (*next == ',') { *next = '\0'; next++; } /* check if user exists (but add anyway) */ pwent = getpwnam(username); if (pwent == NULL) log_log(LOG_ERR, "%s:%d: user '%s' does not exist", filename, lnr, username); set_add(cfg->nss_initgroups_ignoreusers, username); } } } } static void handle_validnames(const char *filename, int lnr, const char *keyword, char *line, struct nslcd_config *cfg) { char *value; int i, l; int flags = REG_EXTENDED | REG_NOSUB; /* the rest of the line should be a regular expression */ value = get_linedup(filename, lnr, keyword, &line); if (cfg->validnames_str != NULL) { free(cfg->validnames_str); regfree(&cfg->validnames); } cfg->validnames_str = strdup(value); /* check formatting and update flags */ if (value[0] != '/') { log_log(LOG_ERR, "%s:%d: regular expression incorrectly delimited", filename, lnr); exit(EXIT_FAILURE); } l = strlen(value); if (value[l - 1] == 'i') { value[l - 1] = '\0'; l--; flags |= REG_ICASE; } if (value[l - 1] != '/') { log_log(LOG_ERR, "%s:%d: regular expression incorrectly delimited", filename, lnr); exit(EXIT_FAILURE); } value[l - 1] = '\0'; /* compile the regular expression */ if ((i = regcomp(&cfg->validnames, value + 1, flags)) != 0) { /* get the error message */ l = regerror(i, &cfg->validnames, NULL, 0); value = malloc(l); if (value == NULL) log_log(LOG_ERR, "%s:%d: invalid regular expression", filename, lnr); else { regerror(i, &cfg->validnames, value, l); log_log(LOG_ERR, "%s:%d: invalid regular expression: %s", filename, lnr, value); } exit(EXIT_FAILURE); } free(value); } static void handle_yamldir( const char *filename, int lnr, const char *keyword, char *line, struct nslcd_config *cfg) { char *value; int l; /* the rest of the line should be a message */ value = get_linedup(filename, lnr, keyword, &line); /* strip quotes if they are present */ l = strlen(value); if ((value[0] == '\"') && (value[l - 1] == '\"')) { value[l - 1] = '\0'; value++; } cfg->yamldir = value; } static void handle_pam_password_prohibit_message( const char *filename, int lnr, const char *keyword, char *line, struct nslcd_config *cfg) { char *value; int l; /* the rest of the line should be a message */ value = get_linedup(filename, lnr, keyword, &line); /* strip quotes if they are present */ l = strlen(value); if ((value[0] == '\"') && (value[l - 1] == '\"')) { value[l - 1] = '\0'; value++; } cfg->pam_password_prohibit_message = value; } static void handle_reconnect_invalidate( const char *filename, int lnr, const char *keyword, char *line, struct nslcd_config *cfg) { char token[MAX_LINE_LENGTH]; char *name, *next; enum nss_map_selector map; check_argumentcount(filename, lnr, keyword, (line != NULL) && (*line != '\0')); while (get_token(&line, token, sizeof(token)) != NULL) { next = token; while (*next != '\0') { name = next; /* find the end of the current map name */ while ((*next != '\0') && (*next != ',')) next++; if (*next == ',') { *next = '\0'; next++; } /* check if map name exists */ map = parse_map(name); if (map == LM_NONE) { log_log(LOG_ERR, "%s:%d: unknown map: '%s'", filename, lnr, name); exit(EXIT_FAILURE); } cfg->reconnect_invalidate[map] = 1; } } } /* set the configuration information to the defaults */ static void cfg_defaults(struct nslcd_config *cfg) { int i; memset(cfg, 0, sizeof(struct nslcd_config)); handle_yamldir(__FILE__, __LINE__, "", "/var/cache/parabola-hackers/users", cfg); cfg->threads = 5; cfg->pagesize = 0; cfg->nss_initgroups_ignoreusers = NULL; cfg->nss_min_uid = 0; cfg->nss_nested_groups = 0; cfg->validnames_str = NULL; handle_validnames(__FILE__, __LINE__, "", "/^[a-z0-9._@$()]([a-z0-9._@$() \\~-]*[a-z0-9._@$()~-])?$/i", cfg); cfg->ignorecase = 0; cfg->pam_password_prohibit_message = NULL; for (i = 0; i < LM_NONE; i++) cfg->reconnect_invalidate[i] = 0; } static void cfg_read(const char *filename, struct nslcd_config *cfg) { FILE *fp; int lnr = 0; char linebuf[MAX_LINE_LENGTH]; char *line; char keyword[32]; int i; /* open config file */ if ((fp = fopen(filename, "r")) == NULL) { log_log(LOG_ERR, "cannot open config file (%s): %s", filename, strerror(errno)); exit(EXIT_FAILURE); } /* read file and parse lines */ while (fgets(linebuf, sizeof(linebuf), fp) != NULL) { lnr++; line = linebuf; /* strip newline */ i = (int)strlen(line); if ((i <= 0) || (line[i - 1] != '\n')) { log_log(LOG_ERR, "%s:%d: line too long or last line missing newline", filename, lnr); exit(EXIT_FAILURE); } line[i - 1] = '\0'; /* ignore comment lines */ if (line[0] == '#') continue; /* strip trailing spaces */ for (i--; (i > 0) && isspace(line[i - 1]); i--) line[i - 1] = '\0'; /* get keyword from line and ignore empty lines */ if (get_token(&line, keyword, sizeof(keyword)) == NULL) continue; /* runtime options */ if (strcasecmp(keyword, "threads") == 0) { cfg->threads = get_int(filename, lnr, keyword, &line); get_eol(filename, lnr, keyword, &line); } else if (strcasecmp(keyword, "log") == 0) { handle_log(filename, lnr, keyword, line); } /* general connection options */ else if (strcasecmp(keyword, "yamldir") == 0) { handle_yamldir(filename, lnr, keyword, line, cfg); } /* other options */ else if (strcasecmp(keyword, "pagesize") == 0) { cfg->pagesize = get_int(filename, lnr, keyword, &line); get_eol(filename, lnr, keyword, &line); } else if (strcasecmp(keyword, "nss_initgroups_ignoreusers") == 0) { handle_nss_initgroups_ignoreusers(filename, lnr, keyword, line, cfg); } else if (strcasecmp(keyword, "nss_min_uid") == 0) { cfg->nss_min_uid = get_int(filename, lnr, keyword, &line); get_eol(filename, lnr, keyword, &line); } else if (strcasecmp(keyword, "nss_nested_groups") == 0) { cfg->nss_nested_groups = get_boolean(filename, lnr, keyword, &line); get_eol(filename, lnr, keyword, &line); } else if (strcasecmp(keyword, "validnames") == 0) { handle_validnames(filename, lnr, keyword, line, cfg); } else if (strcasecmp(keyword, "ignorecase") == 0) { cfg->ignorecase = get_boolean(filename, lnr, keyword, &line); get_eol(filename, lnr, keyword, &line); } else if (strcasecmp(keyword, "pam_password_prohibit_message") == 0) { handle_pam_password_prohibit_message(filename, lnr, keyword, line, cfg); } else if (strcasecmp(keyword, "reconnect_invalidate") == 0) { handle_reconnect_invalidate(filename, lnr, keyword, line, cfg); } #ifdef ENABLE_CONFIGFILE_CHECKING /* fallthrough */ else { log_log(LOG_ERR, "%s:%d: unknown keyword: '%s'", filename, lnr, keyword); exit(EXIT_FAILURE); } #endif } /* we're done reading file, close */ fclose(fp); } /* dump configuration */ static void cfg_dump(void) { int i; const char **strp; char buffer[1024]; log_log(LOG_DEBUG, "CFG: threads %d", nslcd_cfg->threads); log_log_config(); log_log(LOG_DEBUG, "CFG: pagesize %d", nslcd_cfg->pagesize); if (nslcd_cfg->nss_initgroups_ignoreusers != NULL) { /* allocate memory for a comma-separated list */ strp = set_tolist(nslcd_cfg->nss_initgroups_ignoreusers); if (strp == NULL) { log_log(LOG_CRIT, "malloc() failed to allocate memory"); exit(EXIT_FAILURE); } /* turn the set into a comma-separated list */ buffer[0] = '\0'; for (i = 0; strp[i] != NULL; i++) { if (i > 0) strncat(buffer, ",", sizeof(buffer) - 1 - strlen(buffer)); strncat(buffer, strp[i], sizeof(buffer) - 1 - strlen(buffer)); } free(strp); if (strlen(buffer) >= (sizeof(buffer) - 4)) strcpy(buffer + sizeof(buffer) - 4, "..."); log_log(LOG_DEBUG, "CFG: nss_initgroups_ignoreusers %s", buffer); } log_log(LOG_DEBUG, "CFG: nss_min_uid %d", (int)nslcd_cfg->nss_min_uid); log_log(LOG_DEBUG, "CFG: nss_nested_groups %s", print_boolean(nslcd_cfg->nss_nested_groups)); log_log(LOG_DEBUG, "CFG: validnames %s", nslcd_cfg->validnames_str); log_log(LOG_DEBUG, "CFG: ignorecase %s", print_boolean(nslcd_cfg->ignorecase)); if (nslcd_cfg->pam_password_prohibit_message != NULL) log_log(LOG_DEBUG, "CFG: pam_password_prohibit_message \"%s\"", nslcd_cfg->pam_password_prohibit_message); /* build a comma-separated list */ buffer[0] = '\0'; for (i = 0; i < LM_NONE ; i++) if (nslcd_cfg->reconnect_invalidate[i]) { if (buffer[0] != '\0') strncat(buffer, ",", sizeof(buffer) - 1 - strlen(buffer)); strncat(buffer, print_map(i), sizeof(buffer) - 1 - strlen(buffer)); } if (buffer[0] != '\0') log_log(LOG_DEBUG, "CFG: reconnect_invalidate %s", buffer); } void cfg_init(const char *fname) { /* check if we were called before */ if (nslcd_cfg != NULL) { log_log(LOG_CRIT, "cfg_init() may only be called once"); exit(EXIT_FAILURE); } /* allocate the memory (this memory is not freed anywhere) */ nslcd_cfg = (struct nslcd_config *)malloc(sizeof(struct nslcd_config)); if (nslcd_cfg == NULL) { log_log(LOG_CRIT, "malloc() failed to allocate memory"); exit(EXIT_FAILURE); } /* clear configuration */ cfg_defaults(nslcd_cfg); /* read configfile */ cfg_read(fname, nslcd_cfg); #ifdef NSLCD_BINDPW_PATH bindpw_read(NSLCD_BINDPW_PATH, nslcd_cfg); #endif /* NSLCD_BINDPW_PATH */ /* dump configuration */ cfg_dump(); }