#include "fileserv.h" #include #include #include "wordsplit.h" #ifndef SYSCONFDIR # define SYSCONFDIR "/etc" #endif #define DEFAULT_CONF_FILE "fileserv.conf" char *config_file; char *dotfile = ".fileserv"; static CONFIG *global_config; void readconfig(void) { if (!config_file) { config_file = catfile(SYSCONFDIR, DEFAULT_CONF_FILE); if (!config_file) xmalloc_fail(); if (access(config_file, F_OK)) { if (verbose) error("warning: no configuration file %s", config_file); free(config_file); config_file = NULL; return; } } global_config = config_init(); if (!global_config) exit(1); if (config_parse(config_file, global_config, CTXGLOB)) exit(1); } int dirconfig(char const *path, size_t prefix_len, CONFIG **ret) { CONFIG *conf; char *tmp = NULL; struct stat st; int rc = FSE_SUCCESS; if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { tmp = catfile(path, "."); if (!tmp) return FSE_ERROR; path = tmp; } conf = config_clone(global_config); if (conf) { while (path[prefix_len]) { char *name = catfile_n(path, prefix_len, dotfile); if (!name) { rc = FSE_ERROR; break; } if (access(name, F_OK) == 0) { rc = config_parse(name, conf, CTXDIR); free(name); if (rc) break; } if (path[prefix_len] == '/') prefix_len++; while (path[prefix_len] && path[prefix_len] != '/') prefix_len++; } } else rc = FSE_ERROR; free(tmp); if (rc == FSE_SUCCESS) *ret = conf; else config_free(conf); return rc; } static void index_files_free(CONFIG *conf) { size_t i; for (i = conf->index_files_n; i < conf->index_files_c; i++) free(conf->index_files_v[i]); conf->index_files_c = 0; conf->index_files_n = 0; free(conf->index_files_v); conf->index_files_v = NULL; } static void hidden_files_free(CONFIG *conf) { size_t i; for (i = conf->hidden_files_rxn; i < conf->hidden_files_rxc; i++) regfree(&conf->hidden_files_rxv[i]); conf->hidden_files_rxc = 0; conf->hidden_files_rxn = 0; free(conf->hidden_files_rxv); conf->hidden_files_rxv = NULL; } CONFIG * config_init(void) { CONFIG *p = calloc(1, sizeof(*p)); if (p) p->list_unreadable = 1; else alloc_warn(); return p; } CONFIG * config_clone(CONFIG const *source) { size_t i; CONFIG *p = config_init(); if (!p) return NULL; if (!source) return p; p->follow = source->follow; p->listing = source->listing; p->list_unreadable = source->list_unreadable; if (source->index_files_c) { p->index_files_v = xcalloc(source->index_files_c, sizeof(p->index_files_v[0])); for (i = 0; i < source->index_files_c; i++) p->index_files_v[i] = source->index_files_v[i]; p->index_files_v[i] = NULL; } p->index_files_c = source->index_files_c; p->index_files_n = source->index_files_c; if (source->hidden_files_rxc) { p->hidden_files_rxv = xcalloc(source->hidden_files_rxc, sizeof(p->hidden_files_rxv[0])); for (i = 0; i < source->hidden_files_rxc; i++) p->hidden_files_rxv[i] = source->hidden_files_rxv[i]; } p->hidden_files_rxc = source->hidden_files_rxc; p->hidden_files_rxn = source->hidden_files_rxc; return p; } void config_free(CONFIG *conf) { if (conf) { index_files_free(conf); hidden_files_free(conf); free(conf); } } static int bool_decode(char const *s) { static char *yes[] = { "1", "yes", "true", "on", NULL }; static char *no[] = { "0", "no", "false", "off", NULL }; int i; for (i = 0; yes[i]; i++) { if (strcasecmp(s, yes[i]) == 0) return 1; } for (i = 0; no[i]; i++) { if (strcasecmp(s, no[i]) == 0) return 0; } return -1; } static int bad_argc(const char *file, int line, char const *id) { error("%s:%d: bad number of arguments to '%s'", file, line, id); return FSE_SYNTAX; } static int not_a_bool(const char *file, int line) { error("%s:%d: invalid boolean value", file, line); return FSE_SYNTAX; } static int string_copy(char **dst, char const *src) { *dst = strdup(src); if (!*dst) { alloc_warn(); return FSE_ERROR; } return FSE_SUCCESS; } static int set_follow(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { int n; if (argc != 2) return bad_argc(file, line, argv[0]); n = bool_decode(argv[1]); if (n == -1) return not_a_bool(file, line); conf->follow = n; return FSE_SUCCESS; } static int set_listing(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { int n; if (argc != 2) return bad_argc(file, line, argv[0]); n = bool_decode(argv[1]); if (n == -1) return not_a_bool(file, line); conf->listing = n; return FSE_SUCCESS; } static int set_list_unreadable(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { int n; if (argc != 2) return bad_argc(file, line, argv[0]); n = bool_decode(argv[1]); if (n == -1) return not_a_bool(file, line); conf->list_unreadable = n; return FSE_SUCCESS; } static int set_index_files(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; void *p; if (argc < 2) return bad_argc(file, line, argv[0]); i = 1; if (strcmp(argv[i], "+") == 0) i++; else index_files_free(conf); p = nrealloc(conf->index_files_v, argc + conf->index_files_c, sizeof(conf->index_files_v[0])); if (!p) { alloc_warn(); return FSE_ERROR; } conf->index_files_v = p; for (; argv[i]; i++) conf->index_files_v[conf->index_files_c++] = xstrdup(argv[i]); conf->index_files_v[conf->index_files_c] = NULL; return FSE_SUCCESS; } static int set_hidden_files(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; void *p; int rc; if (argc < 2) return bad_argc(file, line, argv[0]); i = 1; if (strcmp(argv[i], "+") == 0) i++; else hidden_files_free(conf); p = nrealloc(conf->hidden_files_rxv, argc + conf->hidden_files_rxc, sizeof(conf->hidden_files_rxv[0])); if (!p) { alloc_warn(); return FSE_ERROR; } conf->hidden_files_rxv = p; rc = FSE_SUCCESS; for (; argv[i]; i++) { int ec; ec = regcomp(&conf->hidden_files_rxv[conf->hidden_files_rxc], argv[i], REG_EXTENDED|REG_NOSUB); if (ec) { char buf[512]; regerror(ec, &conf->hidden_files_rxv[conf->hidden_files_rxc], buf, sizeof buf); error("%s: %s", argv[i], buf); rc = FSE_ERROR; break; } else conf->hidden_files_rxc++; } return rc; } static int set_user(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&user, argv[1]); } static int set_group(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&group, argv[1]); } static int set_listen(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&address, argv[1]); } static int set_index_template(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } if (parse_template_file(argv[1])) { error("%s:%d: invalid template", file, line); return FSE_SYNTAX; } return FSE_SUCCESS; } static int set_index_css(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&index_css, argv[1]); } static int set_dotfile(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&dotfile, argv[1]); } static int set_forwarded_header(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&forwarded_header, argv[1]); } static int set_temp_dir(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&tmpdir, argv[1]); } static int set_mime_magic_file(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&mime_types_file, argv[1]); } static int set_trusted_proxy(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; if (argc == 1) { error("%s:%d: expected one or more arguments", file, line); return FSE_SYNTAX; } for (i = 1; i < argc; i++) trusted_ip_add(argv[i]); return FSE_SUCCESS; } static int set_pidfile(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&pidfile, argv[1]); } static int set_error_dir(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } return string_copy(&error_dir, argv[1]); } static int set_syslog(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { if (argc != 2) { error("%s:%d: expected exactly one argument", file, line); return FSE_SYNTAX; } if (set_log_facility(argv[1])) { error("%s:%d: not a valid syslog facility", file, line); return FSE_SYNTAX; } return FSE_SUCCESS; } static int set_mapping(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; if (argc == 1) { error("%s:%d: expected one or more arguments", file, line); return FSE_SYNTAX; } for (i = 1; i < argc; i++) urimap_add(argv[i]); return FSE_SUCCESS; } /* set-mime-icon icon [ALT=X] mime-type ... */ static int set_mime_icon(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; char const *icon; char const *alt = NULL; if (argc < 3) { error("%s:%d: expected two or more arguments", file, line); return FSE_SYNTAX; } icon = argv[1]; i = 2; if (strncasecmp(argv[i], "alt=", 4) == 0) { alt = argv[i] + 4; i++; } if (i == argc) { error("%s:%d: expected one or more mime types", file, line); return FSE_SYNTAX; } for (; i < argc; i++) add_icon_by_mime(argv[i], icon, alt); return FSE_SUCCESS; } static int set_name_icon(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; char const *icon; char const *alt = NULL; if (argc < 3) { error("%s:%d: expected two or more arguments", file, line); return FSE_SYNTAX; } icon = argv[1]; i = 2; if (strncasecmp(argv[i], "alt=", 4) == 0) { alt = argv[i] + 4; i++; } if (i == argc) { error("%s:%d: expected one or more mime types", file, line); return FSE_SYNTAX; } for (; i < argc; i++) add_icon_by_name(argv[i], icon, alt); return FSE_SUCCESS; } static int set_type_icon(size_t argc, char **argv, CONFIG *conf, char const *file, int line) { size_t i; char const *icon; char const *alt = NULL; if (argc < 3) { error("%s:%d: expected two or three arguments", file, line); return FSE_SYNTAX; } icon = argv[1]; i = 2; if (strncasecmp(argv[i], "alt=", 4) == 0) { alt = argv[i] + 4; i++; } if (i == argc) { error("%s:%d: expected file type", file, line); return FSE_SYNTAX; } add_icon_by_type(argv[i], icon, alt); return FSE_SUCCESS; } struct config_keyword { int context; char const *ident; int (*setter)(size_t argc, char **argv, CONFIG *conf, char const *file, int line); }; static struct config_keyword keywords[] = { { CTXGLOB, "user", set_user }, { CTXGLOB, "group", set_group }, { CTXGLOB, "listen", set_listen }, { CTXGLOB, "index-template", set_index_template }, { CTXGLOB, "index-css", set_index_css }, { CTXGLOB, "mime-icon", set_mime_icon }, { CTXGLOB, "name-icon", set_name_icon }, { CTXGLOB, "type-icon", set_type_icon }, { CTXGLOB, "access-file-name", set_dotfile }, { CTXGLOB, "forwarded-header", set_forwarded_header }, { CTXGLOB, "trusted-proxy", set_trusted_proxy }, { CTXGLOB, "temp-dir", set_temp_dir }, { CTXGLOB, "mime-magic-file", set_mime_magic_file }, { CTXGLOB, "mime-types-file", set_mime_magic_file }, { CTXGLOB, "pid-file", set_pidfile }, { CTXGLOB, "pidfile", set_pidfile }, { CTXGLOB, "syslog", set_syslog }, { CTXGLOB, "mapping", set_mapping }, { CTXGLOB, "error-dir", set_error_dir }, { CTXGLOB | CTXDIR, "directory-index", set_index_files }, { CTXGLOB | CTXDIR, "follow", set_follow }, { CTXGLOB | CTXDIR, "listing", set_listing }, { CTXGLOB | CTXDIR, "list-unreadable", set_list_unreadable }, { CTXGLOB | CTXDIR, "hidden-files", set_hidden_files }, { CTXNONE, NULL } }; static int line_interpret(struct wordsplit const *ws, CONFIG *conf, int ctx, char const *file, int line) { struct config_keyword *kw; for (kw = keywords; kw->ident; kw++) { if ((kw->context & ctx) && strcmp(kw->ident, ws->ws_wordv[0]) == 0) return kw->setter(ws->ws_wordc, ws->ws_wordv, conf, file, line); } error("%s:%d: unrecognized keyword", file, line); return FSE_SYNTAX; } int wrdse_to_fse(int wse) { switch (wse) { case WRDSE_OK: return FSE_SUCCESS; case WRDSE_NOSPACE: case WRDSE_USAGE: return FSE_ERROR; case WRDSE_USERERR: /* FIXME: This one can map to any FSE_ code. In the lack of more specific information, it is mapped to syntax error */ default: return FSE_SYNTAX; } } int config_parse(const char *file, CONFIG *conf, int ctx) { FILE *fp; char buf[1024]; unsigned ln = 0; int wsflags = WRDSF_DEFFLAGS; struct wordsplit ws; int err = 0; fp = fopen(file, "r"); if (!fp) { if (ctx == CTXGLOB || errno != ENOENT) error("can't open file %s: %s", file, strerror(errno)); return FSE_ERROR; } ws.ws_comment = "#"; wsflags |= WRDSF_COMMENT; while (fgets(buf, sizeof(buf), fp)) { int len; ln++; len = strlen(buf); if (len == 0) continue; if (buf[len-1] == '\n') buf[--len] = 0; else if (!feof(fp)) { error("%s:%d: line too long", file, ln); err = FSE_SYNTAX; break; } if (wordsplit(buf, &ws, wsflags)) { error("%s:%d: %s", file, ln, wordsplit_strerror(&ws)); err = wrdse_to_fse(ws.ws_errno); break; } wsflags |= WRDSF_REUSE; if (ws.ws_wordc == 0) continue; err = line_interpret(&ws, conf, ctx, file, ln); if (err) break; } if (wsflags & WRDSF_REUSE) wordsplit_free(&ws); if (ferror(fp)) { err = FSE_ERROR; error("%s: %s", file, strerror(errno)); } fclose(fp); return err; } int filename_is_valid(char const *name) { enum { a_chr, a_sla, a_dot, a_end, A_MAX }; enum { s_ini, s_cur, s_sla, s_dot, s_dt2, s_err, s_end, S_MAX = s_end } state = s_ini; static char trans[A_MAX][S_MAX] = { /* ini cur sla dot dt2 err */ /* chr */ { s_cur, s_cur, s_cur, s_cur, s_cur, s_err }, /* sla */ { s_sla, s_sla, s_err, s_err, s_err, s_err }, /* dot */ { s_dot, s_cur, s_dot, s_dt2, s_cur, s_err }, /* end */ { s_end, s_end, s_end, s_err, s_err, s_err }, }; int c; while (state != s_err && state != s_end) { switch (*name++) { case '/': c = a_sla; break; case '.': c = a_dot; break; case 0: c = a_end; break; default: c = a_chr; } state = trans[c][state]; } return state == s_end; } int filename_is_special(char const *name) { return (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))); } int filename_is_hidden(char const *name, CONFIG const *conf) { if (filename_is_special(name) || strcmp(name, dotfile) == 0) return 1; if (conf && conf->hidden_files_rxv) { size_t i; for (i = 0; i < conf->hidden_files_rxc; i++) if (regexec(&conf->hidden_files_rxv[i], name, 0, NULL, 0) == 0) return 1; } return 0; }