cmd_rec *pr_parser_parse_line(pool *p) { register unsigned int i; char buf[PR_TUNABLE_BUFFER_SIZE+1], *arg = "", *word = NULL; cmd_rec *cmd = NULL; pool *sub_pool = NULL; array_header *arr = NULL; if (p == NULL) { errno = EINVAL; return NULL; } memset(buf, '\0', sizeof(buf)); while (pr_parser_read_line(buf, sizeof(buf)-1) != NULL) { char *bufp = buf; pr_signals_handle(); /* Build a new pool for the command structure and array */ sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "parser cmd subpool"); cmd = pcalloc(sub_pool, sizeof(cmd_rec)); cmd->pool = sub_pool; cmd->stash_index = -1; cmd->stash_hash = 0; /* Add each word to the array */ arr = make_array(cmd->pool, 4, sizeof(char **)); while ((word = pr_str_get_word(&bufp, 0)) != NULL) { char *tmp; tmp = get_config_word(cmd->pool, word); *((char **) push_array(arr)) = tmp; cmd->argc++; } /* Terminate the array with a NULL. */ *((char **) push_array(arr)) = NULL; /* The array header's job is done, we can forget about it and * it will get purged when the command's pool is destroyed. */ cmd->argv = (char **) arr->elts; /* Perform a fixup on configuration directives so that: * * -argv[0]-- -argv[1]-- ----argv[2]----- * <Option /etc/adir /etc/anotherdir> * * becomes: * * -argv[0]-- -argv[1]- ----argv[2]---- * <Option> /etc/adir /etc/anotherdir */ if (cmd->argc && *(cmd->argv[0]) == '<') { char *cp = cmd->argv[cmd->argc-1]; if (*(cp + strlen(cp)-1) == '>' && cmd->argc > 1) { if (strncmp(cp, ">", 2) == 0) { cmd->argv[cmd->argc-1] = NULL; cmd->argc--; } else { *(cp + strlen(cp)-1) = '\0'; } cp = cmd->argv[0]; if (*(cp + strlen(cp)-1) != '>') { cmd->argv[0] = pstrcat(cmd->pool, cp, ">", NULL); } } } if (cmd->argc < 2) { arg = pstrdup(cmd->pool, arg); } for (i = 1; i < cmd->argc; i++) { arg = pstrcat(cmd->pool, arg, *arg ? " " : "", cmd->argv[i], NULL); } cmd->arg = arg; return cmd; } return NULL; }
int pr_parser_parse_file(pool *p, const char *path, config_rec *start, int flags) { pr_fh_t *fh; struct stat st; struct config_src *cs; cmd_rec *cmd; pool *tmp_pool; char *buf, *report_path; size_t bufsz; if (path == NULL) { errno = EINVAL; return -1; } if (parser_servstack == NULL) { errno = EPERM; return -1; } tmp_pool = make_sub_pool(p ? p : permanent_pool); pr_pool_tag(tmp_pool, "parser file pool"); report_path = (char *) path; if (session.chroot_path) { report_path = pdircat(tmp_pool, session.chroot_path, path, NULL); } if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_trace_msg(trace_channel, 3, "parsing '%s' configuration", report_path); } fh = pr_fsio_open(path, O_RDONLY); if (fh == NULL) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Stat the opened file to determine the optimal buffer size for IO. */ memset(&st, 0, sizeof(st)); if (pr_fsio_fstat(fh, &st) < 0) { int xerrno = errno; pr_fsio_close(fh); destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISDIR(st.st_mode)) { pr_fsio_close(fh); destroy_pool(tmp_pool); errno = EISDIR; return -1; } /* Advise the platform that we will be only reading this file * sequentially. */ pr_fs_fadvise(PR_FH_FD(fh), 0, 0, PR_FS_FADVISE_SEQUENTIAL); /* Check for world-writable files (and later, files in world-writable * directories). * * For now, just warn about these; later, we will be more draconian. */ if (st.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_WARNING, "warning: config file '%s' is world-writable", path); } fh->fh_iosz = st.st_blksize; /* Push the configuration information onto the stack of configuration * sources. */ cs = add_config_source(fh); if (start != NULL) { (void) pr_parser_config_ctxt_push(start); } bufsz = PR_TUNABLE_PARSER_BUFFER_SIZE; buf = pcalloc(tmp_pool, bufsz + 1); while (pr_parser_read_line(buf, bufsz) != NULL) { pr_signals_handle(); cmd = pr_parser_parse_line(tmp_pool, buf, 0); if (cmd == NULL) { continue; } if (cmd->argc) { conftable *conftab; char found = FALSE; cmd->server = *parser_curr_server; cmd->config = *parser_curr_config; conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], NULL, &cmd->stash_index, &cmd->stash_hash); while (conftab != NULL) { modret_t *mr; pr_signals_handle(); cmd->argv[0] = conftab->directive; pr_trace_msg(trace_channel, 7, "dispatching directive '%s' to module mod_%s", conftab->directive, conftab->m->name); mr = pr_module_call(conftab->m, conftab->handler, cmd); if (mr != NULL) { if (MODRET_ISERROR(mr)) { if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); destroy_pool(tmp_pool); errno = EPERM; return -1; } pr_log_pri(PR_LOG_WARNING, "warning: %s on line %u of '%s'", MODRET_ERRMSG(mr), cs->cs_lineno, report_path); } } if (!MODRET_ISDECLINED(mr)) { found = TRUE; } conftab = pr_stash_get_symbol2(PR_SYM_CONF, cmd->argv[0], conftab, &cmd->stash_index, &cmd->stash_hash); } if (cmd->tmp_pool) { destroy_pool(cmd->tmp_pool); } if (found == FALSE) { register unsigned int i; char *name; size_t namelen; int non_ascii = FALSE; /* I encountered a case where a particular configuration file had * what APPEARED to be a valid directive, but the parser kept reporting * that the directive was unknown. I now suspect that the file in * question had embedded UTF8 characters (spaces, perhaps), which * would appear as normal spaces in e.g. UTF8-aware editors/terminals, * but which the parser would rightly refuse. * * So to indicate that this might be the case, check for any non-ASCII * characters in the "unknown" directive name, and if found, log * about them. */ name = cmd->argv[0]; namelen = strlen(name); for (i = 0; i < namelen; i++) { if (!isascii((int) name[i])) { non_ascii = TRUE; break; } } if (!(flags & PR_PARSER_FL_DYNAMIC_CONFIG)) { pr_log_pri(PR_LOG_WARNING, "fatal: unknown configuration directive " "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path); if (non_ascii) { pr_log_pri(PR_LOG_WARNING, "fatal: malformed directive name " "'%s' (contains non-ASCII characters)", name); } else { array_header *directives, *similars; directives = get_all_directives(tmp_pool); similars = pr_str_get_similars(tmp_pool, name, directives, 0, PR_STR_FL_IGNORE_CASE); if (similars != NULL && similars->nelts > 0) { unsigned int nelts; const char **names, *msg; names = similars->elts; nelts = similars->nelts; if (nelts > 4) { nelts = 4; } msg = "fatal: Did you mean:"; if (nelts == 1) { msg = pstrcat(tmp_pool, msg, " ", names[0], NULL); } else { for (i = 0; i < nelts; i++) { msg = pstrcat(tmp_pool, msg, "\n ", names[i], NULL); } } pr_log_pri(PR_LOG_WARNING, "%s", msg); } } destroy_pool(tmp_pool); errno = EPERM; return -1; } pr_log_pri(PR_LOG_WARNING, "warning: unknown configuration directive " "'%s' on line %u of '%s'", name, cs->cs_lineno, report_path); if (non_ascii) { pr_log_pri(PR_LOG_WARNING, "warning: malformed directive name " "'%s' (contains non-ASCII characters)", name); } } } destroy_pool(cmd->pool); memset(buf, '\0', bufsz); } /* Pop this configuration stream from the stack. */ remove_config_source(); pr_fsio_close(fh); destroy_pool(tmp_pool); return 0; }
/* Usage: <IfVersion [!]op version-string|regex> */ MODRET start_ifversion(cmd_rec *cmd) { unsigned int ifversion_ctx_count = 1; int compared, matched = FALSE, negated = FALSE; char buf[PR_TUNABLE_BUFFER_SIZE], *config_line = NULL; char *error = NULL, *version_str = NULL, *op_str = NULL; size_t op_len; if (cmd->argc-1 == 0 || cmd->argc-1 > 2) { CONF_ERROR(cmd, "wrong number of parameters"); } if (cmd->argc-1 == 2) { op_str = cmd->argv[1]; if (*op_str == '!' && strlen(op_str) > 1) { negated = TRUE; op_str++; } op_len = strlen(op_str); version_str = cmd->argv[2]; } else { /* Assume that if only a version-string was supplied, the operator * is intended to be the equality operator. */ op_str = "="; op_len = 1; version_str = cmd->argv[1]; } switch (*op_str) { case '=': if (*version_str != '/') { /* Normal equality comparison */ compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } matched = (compared == 0); break; } /* Otherwise, it's a regular expression */ if (version_str[strlen(version_str)-1] != '/') { CONF_ERROR(cmd, "Missing terminating '/' of regular expression"); } /* Fall through to the next case in order to handle/evaluate the * regular expression. Be sure to remove the bracketing '/' characters * for the regex compilation. */ version_str[strlen(version_str)-1] = '\0'; version_str++; case '~': /* Regular expression */ matched = match_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } break; case '<': compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } if (compared == -1 || (op_len == 2 && compared == 0)) { matched = TRUE; } break; case '>': compared = compare_version(cmd->tmp_pool, version_str, &error); if (error != NULL) { CONF_ERROR(cmd, error); } if (compared == 1 || (op_len == 2 && compared == 0)) { matched = TRUE; } break; default: CONF_ERROR(cmd, pstrcat(cmd->tmp_pool, "unknown comparison operator '", op_str, "'", NULL)); } if ((matched && !negated) || (!matched && negated)) { pr_log_debug(DEBUG3, "%s: using '%s %s' section at line %u", cmd->argv[0], cmd->argv[1], cmd->argv[2], pr_parser_get_lineno()); return PR_HANDLED(cmd); } pr_log_debug(DEBUG3, "%s: skipping '%s %s' section at line %u", cmd->argv[0], cmd->argv[1], cmd->argv[2], pr_parser_get_lineno()); while (ifversion_ctx_count > 0 && (config_line = pr_parser_read_line(buf, sizeof(buf))) != NULL) { pr_signals_handle(); if (strncasecmp(config_line, "<IfVersion", 10) == 0) { ifversion_ctx_count++; } if (strcasecmp(config_line, "</IfVersion>") == 0) { ifversion_ctx_count--; } } /* If there are still unclosed <IfVersion> sections, signal an error. */ if (ifversion_ctx_count > 0) { CONF_ERROR(cmd, "unclosed <IfVersion> section"); } return PR_HANDLED(cmd); }