version 0.2.1
[fms.git] / libs / shttpd / config.c
index dcc6dad..bd290fb 100644 (file)
 
 #include "defs.h"
 
-/*
- * Configuration parameters setters
- */
-static void
-set_int(struct shttpd_ctx *ctx, void *ptr, const char *string)
-{
-       ctx = NULL;     /* Unused */
-       * (int *) ptr = atoi(string);
-}
+static int isbyte(int n) { return (n >= 0 && n <= 255); }
 
 static void
-set_str(struct shttpd_ctx *ctx, void *ptr, const char *string)
+set_acl(struct shttpd_ctx *ctx, const char *s)
 {
-       ctx = NULL;     /* Unused */
-       * (char **) ptr = my_strdup(string);
-}
-
-static void
-set_log_file(struct shttpd_ctx *ctx, void *ptr, const char *string)
-{
-       FILE    **fp = ptr;
-       ctx = NULL;
+       struct acl      *acl = NULL;
+       char            flag;
+       int             len, a, b, c, d, n, mask;
+       struct llhead   *lp, *tmp;
+
+       /* Delete the old ACLs if any */
+       LL_FOREACH_SAFE(&ctx->acl, lp, tmp)
+               free(LL_ENTRY(lp, struct acl, link));
+
+       FOR_EACH_WORD_IN_LIST(s, len) {
+
+               mask = 32;
+
+               if (sscanf(s, "%c%d.%d.%d.%d%n",&flag,&a,&b,&c,&d,&n) != 5) {
+                       elog(E_FATAL, NULL, "[%s]: subnet must be "
+                           "[+|-]x.x.x.x[/x]", s);
+               } else if (flag != '+' && flag != '-') {
+                       elog(E_FATAL, NULL, "flag must be + or -: [%s]", s);
+               } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
+                       elog(E_FATAL, NULL, "bad ip address: [%s]", s);
+               } else  if ((acl = malloc(sizeof(*acl))) == NULL) {
+                       elog(E_FATAL, NULL, "%s", "cannot malloc subnet");
+               } else if (sscanf(s + n, "/%d", &mask) == 0) { 
+                       /* Do nothing, no mask specified */
+               } else if (mask < 0 || mask > 32) {
+                       elog(E_FATAL, NULL, "bad subnet mask: %d [%s]", n, s);
+               }
 
-       if ((*fp = fopen(string, "a")) == NULL)
-               elog(E_FATAL, NULL, "cannot open log file %s: %s",
-                   string, strerror(errno));
+               acl->ip = (a << 24) | (b << 16) | (c << 8) | d;
+               acl->mask = mask ? 0xffffffffU << (32 - mask) : 0;
+               acl->flag = flag;
+               LL_TAIL(&ctx->acl, &acl->link);
+       }
 }
 
 #ifndef NO_SSL
@@ -43,14 +55,12 @@ set_log_file(struct shttpd_ctx *ctx, void *ptr, const char *string)
  * Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
  */
 static void
-set_ssl(struct shttpd_ctx *ctx, void *arg, const char *pem)
+set_ssl(struct shttpd_ctx *ctx, const char *pem)
 {
        SSL_CTX         *CTX;
        void            *lib;
        struct ssl_func *fp;
 
-       arg = NULL;     /* Unused */
-
        /* Load SSL library dynamically */
        if ((lib = dlopen(SSL_LIB, RTLD_LAZY)) == NULL)
                elog(E_FATAL, NULL, "set_ssl: cannot load %s", SSL_LIB);
@@ -73,262 +83,225 @@ set_ssl(struct shttpd_ctx *ctx, void *arg, const char *pem)
 #endif /* NO_SSL */
 
 static void
-set_mime(struct shttpd_ctx *ctx, void *arg, const char *string)
+open_log_file(FILE **fpp, const char *path)
 {
-       arg = NULL;
-       set_mime_types(ctx, string);
-}
+       if (*fpp != NULL)
+               (void) fclose(*fpp);
 
-#define        OFS(x)  offsetof(struct shttpd_ctx, x)
-#define BOOL_OPT       "0|1"
-const struct opt options[] = {
-       {'d', "document_root", "Web root directory", set_str,
-               OFS(document_root), "directory", NULL, OPT_DIR},
-       {'i', "index_files", "Index files", set_str, OFS(index_files),
-               "file_list", INDEX_FILES, OPT_ADVANCED},
-       {'p', "listen_ports", "Listening ports", set_str,
-               OFS(ports), "ports", LISTENING_PORTS, OPT_ADVANCED},
-       {'D', "list_directories", "Directory listing", set_int,
-               OFS(dirlist), BOOL_OPT, "1", OPT_BOOL | OPT_ADVANCED},
-#ifndef NO_CGI
-       {'c', "cgi_extensions", "CGI extensions", set_str,
-               OFS(cgi_extensions), "ext_list", CGI_EXT, OPT_ADVANCED},
-       {'C', "cgi_interpreter", "CGI interpreter", set_str,
-               OFS(cgi_interpreter), "file", NULL, OPT_FILE | OPT_ADVANCED},
-       {'V', "cgi_envvar", "CGI envir variables", set_str,
-               OFS(cgi_vars), "X=Y,....", NULL, OPT_ADVANCED},
-#endif /* NO_CGI */
-#if !defined(NO_SSI)
-       {'S', "ssi_extensions", "SSI extensions", set_str,
-               OFS(ssi_extensions), "ext_list", SSI_EXT, OPT_ADVANCED},
-#endif /* NO_SSI */
-       {'N', "auth_realm", "Authentication realm", set_str,
-               OFS(auth_realm), "auth_realm", REALM, OPT_ADVANCED},
-       {'l', "access_log", "Access log file", set_log_file,
-               OFS(access_log), "file", NULL, OPT_FILE | OPT_ADVANCED},
-       {'e', "error_log", "Error log file", set_log_file,
-               OFS(error_log), "file", NULL, OPT_FILE | OPT_ADVANCED},
-       {'m', "mime_types", "Mime types file", set_mime,
-               OFS(mime_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
-       {'P', "global_htpasswd", "Global passwords file", set_str,
-               OFS(global_passwd_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
-#ifndef NO_SSL
-       {'s', "ssl_certificate", "SSL certificate file", set_ssl,
-               OFS(ssl_ctx), "pem_file", NULL, OPT_FILE | OPT_ADVANCED},
-#endif /* NO_SSL */
-       {'U', "put_auth", "PUT,DELETE auth file",set_str,
-               OFS(put_auth_file), "file", NULL, OPT_FILE | OPT_ADVANCED},
-       {'a', "aliases", "Aliases", set_str,
-               OFS(aliases), "X=Y,...", NULL, OPT_ADVANCED},
-       {'b', "io_buf_size", "IO buffer size", set_int, OFS(io_buf_size),
-               "bytes", DFLT_IO_SIZ, OPT_INT | OPT_ADVANCED},
-#ifdef _WIN32
-       {'B', "auto_start", "Autostart with Windows", set_int,
-               OFS(auto_start), BOOL_OPT, "1", OPT_BOOL},
-#else
-       {'I', "inetd_mode", "Inetd mode", set_int,
-               OFS(inetd_mode), BOOL_OPT, NULL, OPT_BOOL       },
-       {'u', "runtime_uid", "Run as user", set_str,
-               OFS(uid), "user_name", NULL, 0          },
-#endif /* _WIN32 */
-       {0,   NULL, NULL, NULL, 0, NULL, NULL, 0        }
-};
+       if (path == NULL) {
+               *fpp = NULL;
+       } else if ((*fpp = fopen(path, "a")) == NULL) {
+               elog(E_FATAL, NULL, "cannot open log file %s: %s",
+                   path, strerror(errno));
+       }
+}
 
-static const struct opt *
-find_option(int sw, const char *name)
+static void
+set_alog(struct shttpd_ctx *ctx, const char *path)
 {
-       const struct opt        *opt;
-
-       for (opt = options; opt->sw != 0; opt++)
-               if (sw == opt->sw || (name && strcmp(opt->name, name) == 0))
-                       return (opt);
-
-       return (NULL);
+       open_log_file(&ctx->access_log, path);
 }
 
 static void
-set_option(const struct opt *opt, const char *val, char **tmpvars)
+set_elog(struct shttpd_ctx *ctx, const char *path)
 {
-       tmpvars += opt - options;
+       open_log_file(&ctx->error_log, path);
+}
+
+static void show_cfg_page(struct shttpd_arg *arg);
 
-       if (*tmpvars != NULL)
-               free(*tmpvars);
+static void
+set_cfg_uri(struct shttpd_ctx *ctx, const char *uri)
+{
+       free_list(&ctx->registered_uris, &registered_uri_destructor);
 
-       *tmpvars = my_strdup(val);
+       if (uri != NULL) {
+               shttpd_register_uri(ctx, uri, &show_cfg_page, ctx);
+       }
 }
 
-/*
- * Initialize shttpd context
- */
 static void
-initialize_context(struct shttpd_ctx *ctx, const char *config_file,
-               int argc, char *argv[], char **tmpvars)
+set_ports(struct shttpd_ctx *ctx, const char *p)
 {
-       char                    line[FILENAME_MAX], root[FILENAME_MAX],
-                                       var[sizeof(line)], val[sizeof(line)];
-       const char              *arg;
-       size_t                  i;
-       const struct opt        *opt;
-       FILE                    *fp;
-       struct tm               *tm;
+       int             len, is_ssl;
 
-       current_time = time(NULL);
-       tm = localtime(&current_time);
-       tz_offset = 0;
-#if 0
-       tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0);
-#endif
+       free_list(&ctx->listeners, &listener_destructor);
 
-       (void) memset(ctx, 0, sizeof(*ctx));
+       FOR_EACH_WORD_IN_LIST(p, len) {
+               is_ssl = p[len - 1] == 's' ? 1 : 0;
+               if (shttpd_listen(ctx, atoi(p), is_ssl) == -1)
+                       elog(E_FATAL, NULL,
+                           "Cannot open socket on port %d", atoi(p));
+       }
+}
 
-       ctx->start_time = current_time;
-       InitializeCriticalSection(&ctx->mutex);
+static const struct opt {
+       int             index;          /* Index in shttpd_ctx          */
+       const char      *name;          /* Option name in config file   */
+       const char      *description;   /* Description                  */
+       const char      *default_value; /* Default option value         */
+       void (*setter)(struct shttpd_ctx *, const char *);
+} known_options[] = {
+       {OPT_ROOT, "root", "\tWeb root directory", ".", NULL},
+       {OPT_INDEX_FILES, "index_files", "Index files", INDEX_FILES, NULL},
+       {OPT_PORTS, "ports", "Listening ports", LISTENING_PORTS, set_ports},
+       {OPT_DIR_LIST, "dir_list", "Directory listing", "1", NULL},
+       {OPT_CFG_URI, "cfg_uri", "Config uri", NULL, set_cfg_uri},
+       {OPT_PROTECT, "protect", "URI to htpasswd mapping", NULL, NULL},
+#ifndef NO_CGI
+       {OPT_CGI_EXTENSIONS, "cgi_ext", "CGI extensions", CGI_EXT, NULL},
+       {OPT_CGI_INTERPRETER, "cgi_interp", "CGI interpreter", NULL, NULL},
+       {OPT_CGI_ENVIRONMENT, "cgi_env", "Additional CGI env vars", NULL, NULL},
+#endif /* NO_CGI */
+       {OPT_SSI_EXTENSIONS, "ssi_ext", "SSI extensions", SSI_EXT, NULL},
+#ifndef NO_AUTH
+       {OPT_AUTH_REALM, "auth_realm", "Authentication domain name",REALM,NULL},
+       {OPT_AUTH_GPASSWD, "auth_gpass", "Global passwords file", NULL, NULL},
+       {OPT_AUTH_PUT, "auth_PUT", "PUT,DELETE auth file", NULL, NULL},
+#endif /* !NO_AUTH */
+       {OPT_ACCESS_LOG, "access_log", "Access log file", NULL, set_alog},
+       {OPT_ERROR_LOG, "error_log", "Error log file", NULL, set_elog},
+       {OPT_MIME_TYPES, "mime_types", "Additional mime types list", NULL,NULL},
+#ifndef NO_SSL
+       {OPT_SSL_CERTIFICATE, "ssl_cert", "SSL certificate file", NULL,set_ssl},
+#endif /* NO_SSL */
+       {OPT_ALIASES, "aliases", "Path=URI mappings", NULL, NULL},
+       {OPT_ACL, "acl", "\tAllow/deny IP addresses/subnets", NULL, set_acl},
+#ifdef _WIN32
+#else
+       {OPT_INETD, "inetd", "Inetd mode", "0", NULL},
+       {OPT_UID, "uid", "\tRun as user", NULL, NULL},
+#endif /* _WIN32 */
+       {-1, NULL, NULL, NULL, NULL}
+};
 
-       LL_INIT(&ctx->connections);
-       LL_INIT(&ctx->mime_types);
-       LL_INIT(&ctx->registered_uris);
-       LL_INIT(&ctx->uri_auths);
-       LL_INIT(&ctx->error_handlers);
+void shttpd_set_option(struct shttpd_ctx *ctx, const char *opt, const char *val)
+{
+       const struct opt        *o;
 
-#if !defined(NO_SSI)
-       LL_INIT(&ctx->ssi_funcs);
-#endif /* NO_SSI */
+       for (o = known_options; o->name != NULL; o++)
+               if (!strcmp(opt, o->name))
+                       break;
 
-       /* First pass: set the defaults */
-       for (opt = options; opt->sw != 0; opt++)
-               if (tmpvars[opt - options] == NULL && opt->def != NULL)
-                       tmpvars[opt - options] = my_strdup(opt->def);
+       if (o->name == NULL)
+               elog(E_FATAL, NULL, "no such option: [%s]", opt);
 
-       /* Second pass: load config file  */
-       if (config_file != NULL && (fp = fopen(config_file, "r")) != NULL) {
-               DBG(("init_ctx: config file %s", config_file));
+       /* Call option setter first, so it can use both new and old values */
+       if (o->setter != NULL)
+               o->setter(ctx, val);
 
-               /* Loop through the lines in config file */
-               while (fgets(line, sizeof(line), fp) != NULL) {
+       /* Free old value if any */
+       if (ctx->options[o->index] != NULL)
+               free(ctx->options[o->index]);
+       
+       /* Set new option value */
+       ctx->options[o->index] = val ? my_strdup(val) : NULL;
+}
 
-                       /* Skip comments and empty lines */
-                       if (line[0] == '#' || line[0] == '\n')
-                               continue;
+static void
+show_cfg_page(struct shttpd_arg *arg)
+{
+       struct shttpd_ctx       *ctx = arg->user_data;
+       char                    opt_name[20], value[BUFSIZ];
+       const struct opt        *o;
+
+       opt_name[0] = value[0] = '\0';
+
+       if (!strcmp(shttpd_get_env(arg, "REQUEST_METHOD"), "POST")) {
+               if (arg->flags & SHTTPD_MORE_POST_DATA)
+                       return;
+               (void) shttpd_get_var("o", arg->in.buf, arg->in.len,
+                   opt_name, sizeof(opt_name));
+               (void) shttpd_get_var("v", arg->in.buf, arg->in.len,
+                   value, sizeof(value));
+               shttpd_set_option(ctx, opt_name, value[0] ? value : NULL);
+       }
 
-                       /* Trim trailing newline character */
-                       line[strlen(line) - 1] = '\0';
+       shttpd_printf(arg, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
+           "<html><body><h1>SHTTPD v. %s</h1>", shttpd_version());
 
-                       if (sscanf(line, "%s %[^#\n]", var, val) != 2)
-                               elog(E_FATAL,0,"init_ctx: bad line: [%s]",line);
+       shttpd_printf(arg, "%s", "<table border=1"
+           "<tr><th>Option</th><th>Description</th>"
+           "<th colspan=2>Value</th></tr>");
 
-                       if ((opt = find_option(0, var)) == NULL)
-                               elog(E_FATAL, NULL, 
-                                   "set_option: unknown variable [%s]", var);
-                       set_option(opt, val, tmpvars);
-               }
-               (void) fclose(fp);
-       }
+       if (opt_name[0] != '\0' && value[0] != '\0')
+               shttpd_printf(arg, "<p style='color: green'>Saved: %s=%s</p>",
+                   opt_name, value[0] ? value : "NULL");
 
-       /* Third pass: process command line args */
-       for (i = 1; i < (size_t) argc && argv[i][0] == '-'; i++)
-               if ((opt = find_option(argv[i][1], NULL)) != NULL) {
-                       arg = argv[i][2] ? &argv[i][2] : argv[++i];
-                       
-                       if (arg == NULL)
-                               usage(argv[0]);
-
-                       set_option(opt, arg, tmpvars);
-               } else {
-                       usage(argv[0]);
-               }
 
-       /* Call setters functions now */
-       for (i = 0; i < NELEMS(options); i++)
-               if (tmpvars[i] != NULL) {
-                       options[i].setter(ctx,
-                           ((char *) ctx) + options[i].ofs, tmpvars[i]);
-                       free(tmpvars[i]);
-               }
-       
-       /* If document_root is not set, set it to current directory */
-       if (ctx->document_root == NULL) {
-               (void) my_getcwd(root, sizeof(root));
-               ctx->document_root = my_strdup(root);
+       for (o = known_options; o->name != NULL; o++) {
+               shttpd_printf(arg,
+                   "<form method=post><tr><td>%s</td><td>%s</td>"
+                   "<input type=hidden name=o value='%s'>"
+                   "<td><input type=text name=v value='%s'></td>"
+                   "<td><input type=submit value=save></td></form></tr>",
+                   o->name, o->description, o->name,
+                   ctx->options[o->index] ? ctx->options[o->index] : "");
        }
 
-#ifdef _WIN32
-       {WSADATA data;  WSAStartup(MAKEWORD(2,2), &data);}
-#endif /* _WIN32 */
-
-       DBG(("init_ctx: initialized context %p", (void *) ctx));
+       shttpd_printf(arg, "%s", "</table></body></html>");
+       arg->flags |= SHTTPD_END_OF_OUTPUT;
 }
-
 /*
  * Show usage string and exit.
  */
 void
 usage(const char *prog)
 {
-       const struct opt        *opt;
+       const struct opt        *o;
 
        (void) fprintf(stderr,
            "SHTTPD version %s (c) Sergey Lyubka\n"
-           "usage: %s [OPTIONS] [config_file]\n"
-           "Note: config line keyword for every option is in the "
-           "round brackets\n", VERSION, prog);
+           "usage: %s [options] [config_file]\n", VERSION, prog);
 
 #if !defined(NO_AUTH)
-       (void) fprintf(stderr, "-A <htpasswd_file> <realm> <user> <passwd>\n");
+       fprintf(stderr, "  -A <htpasswd_file> <realm> <user> <passwd>\n");
 #endif /* NO_AUTH */
 
-       for (opt = options; opt->name != NULL; opt++)
-               (void) fprintf(stderr, "-%c <%s>\t\t%s (%s)\n",
-                   opt->sw, opt->arg, opt->desc, opt->name);
+       for (o = known_options; o->name != NULL; o++) {
+               (void) fprintf(stderr, "  -%s\t%s", o->name, o->description);
+               if (o->default_value != NULL)
+                       fprintf(stderr, " (default: %s)", o->default_value);
+               fputc('\n', stderr);
+       }
 
        exit(EXIT_FAILURE);
 }
 
-struct shttpd_ctx *
-init_from_argc_argv(const char *config_file, int argc, char *argv[])
+struct shttpd_ctx *shttpd_init(void)
 {
        struct shttpd_ctx       *ctx;
-       char                    *tmpvars[NELEMS(options)];
-       size_t                  i;
+       struct tm               *tm;
+       const struct opt        *o;
 
-       /* Initialize all temporary holders to NULL */
-       for (i = 0; i < NELEMS(tmpvars); i++)
-               tmpvars[i] = NULL;
+       if ((ctx = calloc(1, sizeof(*ctx))) == NULL)
+               elog(E_FATAL, NULL, "cannot allocate shttpd context");
 
-       if ((ctx = malloc(sizeof(*ctx))) != NULL)
-               initialize_context(ctx, config_file, argc, argv, tmpvars);
-       
-       return (ctx);
-}
+       /* Set default values */
+       for (o = known_options; o->name != NULL; o++) {
+               ctx->options[o->index] = o->default_value == NULL ?
+                   NULL : my_strdup(o->default_value);
+       }
 
-struct shttpd_ctx *
-shttpd_init(const char *config_file, ...)
-{
-       struct shttpd_ctx       *ctx;
-       va_list                 ap;
-       const char              *opt_name, *opt_value;
-       char                    *tmpvars[NELEMS(options)];
-       const struct opt        *opt;
-       size_t                  i;
-
-       /* Initialize all temporary holders to NULL */
-       for (i = 0; i < NELEMS(tmpvars); i++)
-               tmpvars[i] = NULL;
-
-       if ((ctx = malloc(sizeof(*ctx))) != NULL) {
-
-               va_start(ap, config_file);
-               while ((opt_name = va_arg(ap, const char *)) != NULL) {
-                       opt_value = va_arg(ap, const char *);
-                       
-                       if ((opt = find_option(0, opt_name)) == NULL)
-                               elog(E_FATAL, NULL, "shttpd_init: "
-                                   "unknown variable [%s]", opt_name);
-                       set_option(opt, opt_value, tmpvars);
-               }
-               va_end(ap);
+       current_time = ctx->start_time = time(NULL);
+       tm = localtime(&current_time);
+       tz_offset = 0;
+#if 0
+       tm->tm_gmtoff - 3600 * (tm->tm_isdst > 0 ? 1 : 0);
+#endif
 
-               initialize_context(ctx, config_file, 0, NULL, tmpvars);
-       }
+       InitializeCriticalSection(&ctx->mutex);
+
+       LL_INIT(&ctx->connections);
+       LL_INIT(&ctx->registered_uris);
+       LL_INIT(&ctx->error_handlers);
+       LL_INIT(&ctx->acl);
+       LL_INIT(&ctx->ssi_funcs);
+       LL_INIT(&ctx->listeners);
+
+#ifdef _WIN32
+       {WSADATA data;  WSAStartup(MAKEWORD(2,2), &data);}
+#endif /* _WIN32 */
 
        return (ctx);
 }