/*
 * $Id: fcgi_config.c,v 1.54 2009/09/28 12:33:14 robs Exp $
 */

#define CORE_PRIVATE
#include "fcgi.h"

#ifdef APACHE2

#include <limits.h>
#include "mpm_common.h"     /* ap_uname2id, ap_gname2id */

#ifdef WIN32
#include <direct.h>
#else
#include <unistd.h>
#include "unixd.h"
#endif

#endif

#ifdef WIN32
/* warning C4100: unreferenced formal parameter */
/* warning C4706: assignment within conditional expression */ 
#pragma warning( disable : 4100 4706 )
#endif

/*******************************************************************************
 * Get the next configuration directive argument, & return an in_addr and port.
 * The arg must be in the form "host:port" where host can be an IP or hostname.
 * The pool arg should be persistant storage.
 */
static const char *get_host_n_port(pool *p, const char **arg,
        const char **host, u_short *port)
{
    char *cvptr, *portStr;
    long tmp;

    *host = ap_getword_conf(p, arg);
    if (**host == '\0')
        return "\"\"";

    portStr = strchr(*host, ':');
    if (portStr == NULL)
        return "missing port specification";

    /* Split the host and port portions */
    *portStr++ = '\0';

    /* Convert port number */
    tmp = (u_short) strtol(portStr, &cvptr, 10);
    if (*cvptr != '\0' || tmp < 1 || tmp > USHRT_MAX)
        return ap_pstrcat(p, "bad port number \"", portStr, "\"", NULL);

    *port = (unsigned short) tmp;

    return NULL;
}

/*******************************************************************************
 * Get the next configuration directive argument, & return an u_short.
 * The pool arg should be temporary storage.
 */
static const char *get_u_short(pool *p, const char **arg,
        u_short *num, u_short min)
{
    char *ptr;
	long tmp;
    const char *txt = ap_getword_conf(p, arg);

    if (*txt == '\0') {
		return "\"\"";
	}

    tmp = strtol(txt, &ptr, 10);

    if (*ptr != '\0') {
        return ap_pstrcat(p, "\"", txt, "\" must be a positive integer", NULL);
	}
    
	if (tmp < min || tmp > USHRT_MAX) {
        return ap_psprintf(p, "\"%u\" must be >= %u and < %u", *num, min, USHRT_MAX);
	}

	*num = (u_short) tmp;

    return NULL;
}

static const char *get_int(pool *p, const char **arg, int *num, int min)
{
    char *cp;
    const char *val = ap_getword_conf(p, arg);

    if (*val == '\0')
    {
        return "\"\"";
    }

    *num = (int) strtol(val, &cp, 10);

    if (*cp != '\0')
    {
        return ap_pstrcat(p, "can't parse ", "\"", val, "\"", NULL);
    }
    else if (*num < min)
    {
        return ap_psprintf(p, "\"%d\" must be >= %d", *num, min);
    }
            
    return NULL;
}

/*******************************************************************************
 * Get the next configuration directive argument, & return an u_int.
 * The pool arg should be temporary storage.
 */
static const char *get_u_int(pool *p, const char **arg,
        u_int *num, u_int min)
{
    char *ptr;
    const char *val = ap_getword_conf(p, arg);

    if (*val == '\0')
        return "\"\"";
    *num = (u_int)strtol(val, &ptr, 10);

    if (*ptr != '\0')
        return ap_pstrcat(p, "\"", val, "\" must be a positive integer", NULL);
    else if (*num < min)
        return ap_psprintf(p, "\"%u\" must be >= %u", *num, min);
    return NULL;
}

/*******************************************************************************
 * Get the next configuration directive argument, & return a float.
 * The pool arg should be temporary storage.
 */
static const char *get_float(pool *p, const char **arg,
        float *num, float min, float max)
{
    char *ptr;
    const char *val = ap_getword_conf(p, arg);

    if (*val == '\0')
        return "\"\"";
    *num = (float) strtod(val, &ptr);

    if (*ptr != '\0')
        return ap_pstrcat(p, "\"", val, "\" is not a floating point number", NULL);
    if (*num < min || *num > max)
        return ap_psprintf(p, "\"%f\" is not between %f and %f", *num, min, max);
    return NULL;
}

const char *fcgi_config_set_env_var(pool *p, char **envp, unsigned int *envc, char * var)
{
    if (*envc >= MAX_INIT_ENV_VARS) {
        return "too many variables, must be <= MAX_INIT_ENV_VARS";
    }

    if (strchr(var, '=') == NULL) {
        *(envp + *envc) = ap_pstrcat(p, var, "=", getenv(var), NULL);
    }
    else {
        *(envp + *envc) = var;
    }

    (*envc)++;

    return NULL;
}

/*******************************************************************************
 * Get the next configuration directive argument, & add it to an env array.
 * The pool arg should be permanent storage.
 */
static const char *get_env_var(pool *p, const char **arg, char **envp, unsigned int *envc)
{
    char * const val = ap_getword_conf(p, arg);

    if (*val == '\0') {
        return "\"\"";
    }

    return fcgi_config_set_env_var(p, envp, envc, val);
}

static const char *get_pass_header(pool *p, const char **arg, array_header **array)
{
    const char **header;

    if (!*array) {
        *array = ap_make_array(p, 10, sizeof(char*));
    }

    header = (const char **)ap_push_array(*array);
    *header = ap_getword_conf(p, arg);

    return header ? NULL : "\"\"";
}

/*******************************************************************************
 * Return a "standard" message for common configuration errors.
 */
static const char *invalid_value(pool *p, const char *cmd, const char *id,
        const char *opt, const char *err)
{
    return ap_psprintf(p, "%s%s%s: invalid value for %s: %s",
                    cmd, id ? " " : "", id ? id : "",  opt, err);
}

/*******************************************************************************
 * Set/Reset the uid/gid that Apache and the PM will run as.  This is ap_user_id
 * and ap_group_id if we're started as root, and euid/egid otherwise.  Also try
 * to check that the config files don't set the User/Group after a FastCGI
 * directive is used that depends on it.
 */
/*@@@ To be complete, we should save a handle to the server each AppClass is
 * configured in and at init() check that the user/group is still what we
 * thought it was.  Also the other directives should only be allowed in the
 * parent Apache server.
 */
const char *fcgi_config_set_fcgi_uid_n_gid(int set)
{
    static int isSet = 0;

#ifndef WIN32

    uid_t uid = geteuid();
    gid_t gid = getegid();

    if (set == 0) {
        isSet = 0;
        fcgi_user_id = (uid_t)-1;
        fcgi_group_id = (gid_t)-1;
        return NULL;
    }

    if (uid == 0) {
        uid = ap_user_id;
    }

    if (gid == 0) {
        gid = ap_group_id;
    }

    if (isSet && (uid != fcgi_user_id || gid != fcgi_group_id)) {
        return "User/Group commands must preceed FastCGI server definitions";
    }

    isSet = 1;
    fcgi_user_id = uid;
    fcgi_group_id = gid;

#endif /* !WIN32 */

    return NULL;
}

apcb_t fcgi_config_reset_globals(void* dummy)
{
    fcgi_config_pool = NULL;
    fcgi_servers = NULL;
    fcgi_config_set_fcgi_uid_n_gid(0);
    fcgi_wrapper = NULL;
    fcgi_socket_dir = NULL;
    
    fcgi_dynamic_total_proc_count = 0;
    fcgi_dynamic_epoch = 0;
    fcgi_dynamic_last_analyzed = 0;

    dynamicMaxProcs = FCGI_DEFAULT_MAX_PROCS;
    dynamicMinProcs = FCGI_DEFAULT_MIN_PROCS;
    dynamicMaxClassProcs = FCGI_DEFAULT_MAX_CLASS_PROCS;
    dynamicKillInterval = FCGI_DEFAULT_KILL_INTERVAL;
    dynamicUpdateInterval = FCGI_DEFAULT_UPDATE_INTERVAL;
    dynamicGain = FCGI_DEFAULT_GAIN;
    dynamicThreshold1 = FCGI_DEFAULT_THRESHOLD_1;
    dynamicThresholdN = FCGI_DEFAULT_THRESHOLD_N;
    dynamicPleaseStartDelay = FCGI_DEFAULT_START_PROCESS_DELAY;
    dynamicAppConnectTimeout = FCGI_DEFAULT_APP_CONN_TIMEOUT;
    dynamicEnvp = &fcgi_empty_env;
    dynamicProcessSlack = FCGI_DEFAULT_PROCESS_SLACK;
    dynamicAutoRestart = FCGI_DEFAULT_RESTART_DYNAMIC;
    dynamicAutoUpdate = FCGI_DEFAULT_AUTOUPDATE;
    dynamicListenQueueDepth = FCGI_DEFAULT_LISTEN_Q;
    dynamicInitStartDelay = DEFAULT_INIT_START_DELAY;
    dynamicRestartDelay = FCGI_DEFAULT_RESTART_DELAY;
    dynamicMinServerLife = FCGI_DEFAULT_MIN_SERVER_LIFE;
    dynamic_pass_headers = NULL;
    dynamic_idle_timeout = FCGI_DEFAULT_IDLE_TIMEOUT;
	dynamicFlush = FCGI_FLUSH;

#ifndef WIN32
	/* Close any old pipe (HUP/USR1) */
	if (fcgi_pm_pipe[0] != -1) {
		close(fcgi_pm_pipe[0]);
		fcgi_pm_pipe[0] = -1;
	}
	if (fcgi_pm_pipe[1] != -1) {
		close(fcgi_pm_pipe[1]);
		fcgi_pm_pipe[1] = -1;
	}
#endif

    return APCB_OK;
}

/*******************************************************************************
 * Create a directory to hold Unix/Domain sockets.
 */
const char *fcgi_config_make_dir(pool *tp, char *path)
{
    struct stat finfo;
    const char *err = NULL;

    /* Is the directory spec'd correctly */
    if (*path != '/') {
        return "path is not absolute (it must start with a \"/\")";
    }
    else {
        int i = strlen(path) - 1;

        /* Strip trailing "/"s */
        while(i > 0 && path[i] == '/') path[i--] = '\0';
    }

    /* Does it exist? */
    if (stat(path, &finfo) != 0) {
        /* No, but maybe we can create it */
#ifdef WIN32
        if (mkdir(path) != 0) 
#else
        if (mkdir(path, S_IRWXU) != 0)
#endif
        {
            return ap_psprintf(tp,
                "doesn't exist and can't be created: %s",
                strerror(errno));
        }

#ifndef WIN32
        /* If we're root, we're gonna setuid/setgid so we need to chown */
        if (geteuid() == 0 && chown(path, ap_user_id, ap_group_id) != 0) {
            return ap_psprintf(tp,
                "can't chown() to the server (uid %ld, gid %ld): %s",
                (long)ap_user_id, (long)ap_group_id, strerror(errno));
        }
#endif
    }
    else {
        /* Yes, is it a directory? */
        if (!S_ISDIR(finfo.st_mode))
            return "isn't a directory!";

        /* Can we RWX in there? */
#ifdef WIN32
        err = fcgi_util_check_access(tp, NULL, &finfo, _S_IREAD | _S_IWRITE | _S_IEXEC, fcgi_user_id, fcgi_group_id);
#else
        err = fcgi_util_check_access(tp, NULL, &finfo, R_OK | W_OK | X_OK,
                          fcgi_user_id, fcgi_group_id);
#endif
        if (err != NULL) {
            return ap_psprintf(tp,
                "access for server (uid %ld, gid %ld) failed: %s",
                (long)fcgi_user_id, (long)fcgi_group_id, err);
        }
    }
    return NULL;
}

/*******************************************************************************
 * Create a "dynamic" subdirectory.  If the directory
 * already exists we don't mess with it unless 'wax' is set.
 */
#ifndef WIN32
const char *fcgi_config_make_dynamic_dir(pool *p, const int wax)
{
    const char *err;
    pool *tp;

    fcgi_dynamic_dir = ap_pstrcat(p, fcgi_socket_dir, "/dynamic", NULL);

    if ((err = fcgi_config_make_dir(p, fcgi_dynamic_dir)))
        return ap_psprintf(p, "can't create dynamic directory \"%s\": %s", fcgi_dynamic_dir, err);

    /* Don't step on a running server unless its OK. */
    if (!wax)
        return NULL;

#ifdef APACHE2
    {
        apr_dir_t * dir;
        apr_finfo_t finfo;

        if (apr_pool_create(&tp, p))
            return "apr_pool_create() failed";

        if (apr_dir_open(&dir, fcgi_dynamic_dir, tp))
            return "apr_dir_open() failed";

        /* delete the contents */

        while (apr_dir_read(&finfo, APR_FINFO_NAME, dir) == APR_SUCCESS)
        {
            if (strcmp(finfo.name, ".") == 0 || strcmp(finfo.name, "..") == 0)
                continue;

            apr_file_remove(finfo.name, tp);
        }
    }

#else /* !APACHE2 */
    {
        DIR *dp;
        struct dirent *dirp = NULL;

        tp = ap_make_sub_pool(p);

        dp = ap_popendir(tp, fcgi_dynamic_dir);
        if (dp == NULL) {
            ap_destroy_pool(tp);
            return ap_psprintf(p, "can't open dynamic directory \"%s\": %s",
                fcgi_dynamic_dir, strerror(errno));
        }

        /* delete the contents */

        while ((dirp = readdir(dp)) != NULL) 
        {
            if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0)
                continue;

            unlink(ap_pstrcat(tp, fcgi_dynamic_dir, "/", dirp->d_name, NULL));
        }
    }

#endif /* !APACHE2 */

    ap_destroy_pool(tp);

    return NULL;
}
#endif

/*******************************************************************************
 * Change the directory used for the Unix/Domain sockets from the default.
 * Create the directory and the "dynamic" subdirectory.
 */
const char *fcgi_config_set_socket_dir(cmd_parms *cmd, void *dummy, const char *arg)
{
    pool * const tp = cmd->temp_pool;
    const char * const name = cmd->cmd->name;
    const char *err;
    char * arg_nc;

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err)
    {
        return err;
    }

    if (fcgi_socket_dir) {
        return ap_psprintf(tp, "%s %s: already defined as \"%s\"",
                        name, arg, fcgi_socket_dir);
    }

    err = fcgi_config_set_fcgi_uid_n_gid(1);
    if (err != NULL)
        return ap_psprintf(tp, "%s %s: %s", name, arg, err);

    if (fcgi_servers != NULL) {
        return ap_psprintf(tp,
            "The %s command must preceed static FastCGI server definitions",
            name);
    }

    arg_nc = ap_pstrdup(cmd->pool, arg);

#ifndef WIN32

#ifdef APACHE2
    if (apr_filepath_merge(&arg_nc, "", arg, 0, cmd->pool))
        return ap_psprintf(tp, "%s %s: invalid filepath", name, arg);
#else
    arg_nc = ap_os_canonical_filename(cmd->pool, arg_nc);
#endif

    arg_nc = ap_server_root_relative(cmd->pool, arg_nc);

#else /* WIN32 */

	if (strncmp(arg_nc, "\\\\.\\pipe\\", 9) != 0)
		return ap_psprintf(tp, "%s %s is invalid format",name, arg_nc);

#endif

    fcgi_socket_dir = arg_nc;

#ifdef WIN32
    fcgi_dynamic_dir = ap_pstrcat(cmd->pool, fcgi_socket_dir, "dynamic", NULL);
#else
    err = fcgi_config_make_dir(tp, fcgi_socket_dir);
    if (err != NULL)
        return ap_psprintf(tp, "%s %s: %s", name, arg_nc, err);

    err = fcgi_config_make_dynamic_dir(cmd->pool, 0);
    if (err != NULL)
        return ap_psprintf(tp, "%s %s: %s", name, arg_nc, err);
#endif

    return NULL;
}

/*******************************************************************************
 * Enable, disable, or specify the path to a wrapper used to invoke all
 * FastCGI applications.
 */
const char *fcgi_config_set_wrapper(cmd_parms *cmd, void *dummy, const char *arg)
{
#ifdef WIN32
    return ap_psprintf(cmd->temp_pool, 
        "the %s directive is not supported on WIN", cmd->cmd->name);
#else

    const char *err = NULL;
    const char * const name = cmd->cmd->name;
    pool * const tp = cmd->temp_pool;
    char * wrapper = NULL;

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err)
    {
        return err;
    }

    if (fcgi_wrapper)
    {
        return ap_psprintf(tp, "%s was already set to \"%s\"",
                           name, fcgi_wrapper);
    }

    err = fcgi_config_set_fcgi_uid_n_gid(1);
    if (err != NULL)
        return ap_psprintf(tp, "%s %s: %s", name, arg, err);

    if (fcgi_servers != NULL) {
        return ap_psprintf(tp,
            "The %s command must preceed static FastCGI server definitions", name);
    }

    if (strcasecmp(arg, "Off") == 0) {
        fcgi_wrapper = NULL;
        return NULL;
    }

    if (strcasecmp(arg, "On") == 0) 
    {
        wrapper = SUEXEC_BIN;
    }
    else
    {
#ifdef APACHE2
        if (apr_filepath_merge(&wrapper, "", arg, 0, cmd->pool))
            return ap_psprintf(tp, "%s %s: invalid filepath", name, arg);
#else
        wrapper = ap_os_canonical_filename(cmd->pool, (char *) arg);
#endif

        wrapper = ap_server_root_relative(cmd->pool, wrapper);
    }

    err = fcgi_util_check_access(tp, wrapper, NULL, X_OK, fcgi_user_id, fcgi_group_id);
    if (err) 
    {
        return ap_psprintf(tp, "%s: \"%s\" execute access for server "
                           "(uid %ld, gid %ld) failed: %s", name, wrapper,
                           (long) fcgi_user_id, (long) fcgi_group_id, err);
    }

    fcgi_wrapper = wrapper;

    return NULL;
#endif /* !WIN32 */
}

/*******************************************************************************
 * Configure a static FastCGI server.
 */
const char *fcgi_config_new_static_server(cmd_parms *cmd, void *dummy, const char *arg)
{
    fcgi_server *s;
    pool *p = cmd->pool, *tp = cmd->temp_pool;
    const char *name = cmd->cmd->name;
    char *fs_path = ap_getword_conf(p, &arg);
    const char *option, *err;

    /* Allocate temp storage for the array of initial environment variables */
    char **envp = ap_pcalloc(tp, sizeof(char *) * (MAX_INIT_ENV_VARS + 3));
    unsigned int envc = 0;

#ifdef WIN32
    HANDLE mutex;
#endif

    err = ap_check_cmd_context(cmd, NOT_IN_LIMIT|NOT_IN_DIR_LOC_FILE);
    if (err)
    {
        return err;
    }

    if (*fs_path == '\0')
        return "AppClass requires a pathname!?";

    if ((err = fcgi_config_set_fcgi_uid_n_gid(1)) != NULL)
        return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);

#ifdef APACHE2
    if (apr_filepath_merge(&fs_path, "", fs_path, 0, p))
        return ap_psprintf(tp, "%s %s: invalid filepath", name, fs_path);
#else
    fs_path = ap_os_canonical_filename(p, fs_path);
#endif
    fs_path = ap_server_root_relative(p, fs_path);

    ap_getparents(fs_path);
    ap_no2slash(fs_path);

    /* See if we've already got one of these configured */
    s = fcgi_util_fs_get_by_id(fs_path, fcgi_util_get_server_uid(cmd->server),
                               fcgi_util_get_server_gid(cmd->server));
    if (s != NULL) {
        if (fcgi_wrapper) {
            return ap_psprintf(tp,
                "%s: redefinition of a previously defined FastCGI "
                "server \"%s\" with uid=%ld and gid=%ld",
                name, fs_path, (long) fcgi_util_get_server_uid(cmd->server),
                (long) fcgi_util_get_server_gid(cmd->server));
        }
        else {
            return ap_psprintf(tp,
                "%s: redefinition of a previously defined FastCGI server \"%s\"",
                name, fs_path);
        }
    }

    err = fcgi_util_fs_is_path_ok(tp, fs_path, NULL);
    if (err != NULL) {
        return ap_psprintf(tp, "%s: \"%s\" %s", name, fs_path, err);
    }

    s = fcgi_util_fs_new(p);
    s->fs_path = fs_path;
    s->directive = APP_CLASS_STANDARD;
    s->restartOnExit = TRUE;
    s->numProcesses = 1;

#ifdef WIN32

    /* TCP FastCGI applications require SystemRoot be present in the environment
     * Put it in both for consistency to the application */
    fcgi_config_set_env_var(p, envp, &envc, "SystemRoot");

    mutex = CreateMutex(NULL, FALSE, fs_path);
    
    if (mutex == NULL)
    {
        ap_log_error(FCGI_LOG_ALERT, fcgi_apache_main_server,
            "FastCGI: CreateMutex() failed");
        return "failed to create FastCGI application accept mutex";
    }
    
    SetHandleInformation(mutex, HANDLE_FLAG_INHERIT, TRUE);

    s->mutex_env_string = ap_psprintf(p, "_FCGI_MUTEX_=%ld", mutex);

#endif

    /*  Parse directive arguments */
    while (*arg) {
        option = ap_getword_conf(tp, &arg);

        if (strcasecmp(option, "-processes") == 0) {
            if ((err = get_u_int(tp, &arg, &s->numProcesses, 1)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-restart-delay") == 0) {
            if ((err = get_u_int(tp, &arg, &s->restartDelay, 0)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-init-start-delay") == 0) {
            if ((err = get_int(tp, &arg, &s->initStartDelay, 0)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-min-server-life") == 0) {
            if ((err = get_u_int(tp, &arg, &s->minServerLife, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-priority") == 0) {
            if ((err = get_u_int(tp, &arg, &s->processPriority, 0)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-listen-queue-depth") == 0) {
            if ((err = get_u_int(tp, &arg, &s->listenQueueDepth, 1)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-appConnTimeout") == 0) {
            if ((err = get_u_int(tp, &arg, &s->appConnectTimeout, 0)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-idle-timeout") == 0) {
            if ((err = get_u_int(tp, &arg, &s->idle_timeout, 1)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-port") == 0) {
            if ((err = get_u_short(tp, &arg, &s->port, 1)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-socket") == 0) {
            s->socket_path = ap_getword_conf(tp, &arg);
            if (*s->socket_path == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
        }
        else if (strcasecmp(option, "-initial-env") == 0) {
            if ((err = get_env_var(p, &arg, envp, &envc)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-pass-header") == 0) {
            if ((err = get_pass_header(p, &arg, &s->pass_headers)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-flush") == 0) {
            s->flush = 1;
        }
        else if (strcasecmp(option, "-nph") == 0) {
            s->nph = 1;
        }
        else if (strcasecmp(option, "-user") == 0) {
#ifdef WIN32
            return ap_psprintf(tp, 
                "%s %s: the -user option isn't supported on WIN", name, fs_path);
#else
            s->user = ap_getword_conf(tp, &arg);
            if (*s->user == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
#endif
        }
        else if (strcasecmp(option, "-group") == 0) {
#ifdef WIN32
            return ap_psprintf(tp, 
                "%s %s: the -group option isn't supported on WIN", name, fs_path);
#else
            s->group = ap_getword_conf(tp, &arg);
            if (*s->group == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
#endif
        }
        else {
            return ap_psprintf(tp, "%s %s: invalid option: %s", name, fs_path, option);
        }
    } /* while */

#ifndef WIN32
    if (fcgi_wrapper)
    {
        if (s->group == NULL)
        {
            s->group = ap_psprintf(tp, "#%ld", (long) fcgi_util_get_server_gid(cmd->server));
        }

        if (s->user == NULL)
        {
            s->user = ap_psprintf(p, "#%ld", (long) fcgi_util_get_server_uid(cmd->server)); 
        }

        s->uid = ap_uname2id(s->user);
        s->gid = ap_gname2id(s->group);
    }
    else if (s->user || s->group)
    {
        ap_log_error(FCGI_LOG_WARN, cmd->server, "FastCGI: there is no "
                     "fastcgi wrapper set, user/group options are ignored");
    }

    if ((err = fcgi_util_fs_set_uid_n_gid(p, s, s->uid, s->gid)))
    {
        return ap_psprintf(tp, 
            "%s %s: invalid user or group: %s", name, fs_path, err);
    }
#endif /* !WIN32 */

    if (s->socket_path != NULL && s->port != 0) {
        return ap_psprintf(tp,
                "%s %s: -port and -socket are mutually exclusive options",
                name, fs_path);
    }

    /* Move env array to a surviving pool */
    s->envp = (char **)ap_pcalloc(p, sizeof(char *) * (envc + 4));
    memcpy(s->envp, envp, sizeof(char *) * envc);

    /* Initialize process structs */
    s->procs = fcgi_util_fs_create_procs(p, s->numProcesses);

    /* Build the appropriate sockaddr structure */
    if (s->port != 0) {
        err = fcgi_util_socket_make_inet_addr(p, (struct sockaddr_in **)&s->socket_addr,
                                &s->socket_addr_len, NULL, s->port);
        if (err != NULL)
            return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);
#ifdef WIN32
        err = fcgi_util_socket_make_inet_addr(p, (struct sockaddr_in **)&s->dest_addr,
                                          &s->socket_addr_len, "localhost", s->port);
        if (err != NULL)
            return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);
#endif
    } else {
        if (s->socket_path == NULL)
             s->socket_path = fcgi_util_socket_hash_filename(tp, fs_path, s->user, s->group);

        if (fcgi_socket_dir == NULL)
        {
#ifdef WIN32
            fcgi_socket_dir = DEFAULT_SOCK_DIR;
#else
            fcgi_socket_dir = ap_server_root_relative(p, DEFAULT_SOCK_DIR);
#endif
        }

        s->socket_path = fcgi_util_socket_make_path_absolute(p, s->socket_path, 0);
#ifndef WIN32
        err = fcgi_util_socket_make_domain_addr(p, (struct sockaddr_un **)&s->socket_addr,
                                  &s->socket_addr_len, s->socket_path);
        if (err != NULL)
            return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);
#endif
    }

    /* Add it to the list of FastCGI servers */
    fcgi_util_fs_add(s);

    return NULL;
}

/*******************************************************************************
 * Configure a static FastCGI server that is started/managed elsewhere.
 */
const char *fcgi_config_new_external_server(cmd_parms *cmd, void *dummy, const char *arg)
{
    fcgi_server *s;
    pool * const p = cmd->pool, *tp = cmd->temp_pool;
    const char * const name = cmd->cmd->name;
    char *fs_path = ap_getword_conf(p, &arg);
    const char *option, *err;

    err = ap_check_cmd_context(cmd, NOT_IN_LIMIT|NOT_IN_DIR_LOC_FILE);
    if (err) {
        return err;
    }

    if (!*fs_path) {
        return ap_pstrcat(tp, name, " requires a path and either a -socket or -host option", NULL);
    }

#ifdef APACHE2
    if (apr_filepath_merge(&fs_path, "", fs_path, 0, p))
        return ap_psprintf(tp, "%s %s: invalid filepath", name, fs_path);
#else
    fs_path = ap_os_canonical_filename(p, fs_path);
#endif

    fs_path = ap_server_root_relative(p, fs_path);

    ap_getparents(fs_path);
    ap_no2slash(fs_path);

    /* See if we've already got one of these bettys configured */
    s = fcgi_util_fs_get_by_id(fs_path, fcgi_util_get_server_uid(cmd->server),
                               fcgi_util_get_server_gid(cmd->server));
    if (s != NULL) {
        if (fcgi_wrapper) {
            return ap_psprintf(tp,
                "%s: redefinition of a previously defined class \"%s\" "
                "with uid=%ld and gid=%ld",
                name, fs_path, (long) fcgi_util_get_server_uid(cmd->server),
                (long) fcgi_util_get_server_gid(cmd->server));
        }
        else 
        {
            return ap_psprintf(tp,
                "%s: redefinition of previously defined class \"%s\"", name, fs_path);
        }
    }

    s = fcgi_util_fs_new(p);
    s->fs_path = fs_path;
    s->directive = APP_CLASS_EXTERNAL;

    /*  Parse directive arguments */
    while (*arg != '\0') {
        option = ap_getword_conf(tp, &arg);

        if (strcasecmp(option, "-host") == 0) {
            if ((err = get_host_n_port(p, &arg, &s->host, &s->port)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-socket") == 0) {
            s->socket_path = ap_getword_conf(tp, &arg);
            if (*s->socket_path == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
        }
        else if (strcasecmp(option, "-appConnTimeout") == 0) {
            if ((err = get_u_int(tp, &arg, &s->appConnectTimeout, 0)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-idle-timeout") == 0) {
            if ((err = get_u_int(tp, &arg, &s->idle_timeout, 1)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-nph") == 0) {
            s->nph = 1;
        }
        else if (strcasecmp(option, "-pass-header") == 0) {
            if ((err = get_pass_header(p, &arg, &s->pass_headers)))
                return invalid_value(tp, name, fs_path, option, err);
        }
        else if (strcasecmp(option, "-flush") == 0) {
            s->flush = 1;
        }
        else if (strcasecmp(option, "-user") == 0) {
#ifdef WIN32
            return ap_psprintf(tp, 
                "%s %s: the -user option isn't supported on WIN", name, fs_path);
#else
            s->user = ap_getword_conf(tp, &arg);
            if (*s->user == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
#endif
        }
        else if (strcasecmp(option, "-group") == 0) {
#ifdef WIN32
            return ap_psprintf(tp, 
                "%s %s: the -group option isn't supported on WIN", name, fs_path);
#else
            s->group = ap_getword_conf(tp, &arg);
            if (*s->group == '\0')
                return invalid_value(tp, name, fs_path, option, "\"\"");
#endif
        }
        else {
            return ap_psprintf(tp, "%s %s: invalid option: %s", name, fs_path, option);
        }
    } /* while */


#ifndef WIN32
    if (fcgi_wrapper)
    {
        if (s->group == NULL)
        {
            s->group = ap_psprintf(tp, "#%ld", (long) fcgi_util_get_server_gid(cmd->server));
        }

        if (s->user == NULL)
        {
            s->user = ap_psprintf(p, "#%ld", (long) fcgi_util_get_server_uid(cmd->server));
        }

        s->uid = ap_uname2id(s->user);
        s->gid = ap_gname2id(s->group);
    }
    else if (s->user || s->group)
    {
        ap_log_error(FCGI_LOG_WARN, cmd->server, "FastCGI: there is no "
                     "fastcgi wrapper set, user/group options are ignored");
    }

    if ((err = fcgi_util_fs_set_uid_n_gid(p, s, s->uid, s->gid)))
    {
        return ap_psprintf(tp,
            "%s %s: invalid user or group: %s", name, fs_path, err);
    }
#endif /* !WIN32 */

    /* Require one of -socket or -host, but not both */
    if (s->socket_path != NULL && s->port != 0) {
        return ap_psprintf(tp,
            "%s %s: -host and -socket are mutually exclusive options",
            name, fs_path);
    }
    if (s->socket_path == NULL && s->port == 0) {
        return ap_psprintf(tp,
            "%s %s: -socket or -host option missing", name, fs_path);
    }

    /* Build the appropriate sockaddr structure */
    if (s->port != 0) {
        err = fcgi_util_socket_make_inet_addr(p, (struct sockaddr_in **)&s->socket_addr,
            &s->socket_addr_len, s->host, s->port);
        if (err != NULL)
            return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);
    } else {

        if (fcgi_socket_dir == NULL)
        {
#ifdef WIN32
            fcgi_socket_dir = DEFAULT_SOCK_DIR;
#else
            fcgi_socket_dir = ap_server_root_relative(p, DEFAULT_SOCK_DIR);
#endif
        }

        s->socket_path = fcgi_util_socket_make_path_absolute(p, s->socket_path, 0);
#ifndef WIN32
        err = fcgi_util_socket_make_domain_addr(p, (struct sockaddr_un **)&s->socket_addr,
                                  &s->socket_addr_len, s->socket_path);
        if (err != NULL)
            return ap_psprintf(tp, "%s %s: %s", name, fs_path, err);
#endif
    }

    /* Add it to the list of FastCGI servers */
    fcgi_util_fs_add(s);

    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * fcgi_config_set_config --
 *
 *      Implements the FastCGI FCGIConfig configuration directive.
 *      This command adds routines to control the execution of the
 *      dynamic FastCGI processes.
 *
 *
 *----------------------------------------------------------------------
 */
const char *fcgi_config_set_config(cmd_parms *cmd, void *dummy, const char *arg)
{
    pool * const p = cmd->pool;
    pool * const tp = cmd->temp_pool;
    const char *err, *option;
    const char * const name = cmd->cmd->name;

    /* Allocate temp storage for an initial environment */
    unsigned int envc = 0;
    char **envp = (char **)ap_pcalloc(tp, sizeof(char *) * (MAX_INIT_ENV_VARS + 3));

    err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
    if (err)
    {
        return err;
    }

    /* Parse the directive arguments */
    while (*arg) {
        option = ap_getword_conf(tp, &arg);

        if (strcasecmp(option, "-maxProcesses") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicMaxProcs, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-minProcesses") == 0) {
            if ((err = get_int(tp, &arg, &dynamicMinProcs, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-maxClassProcesses") == 0) {
            if ((err = get_int(tp, &arg, &dynamicMaxClassProcs, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-killInterval") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicKillInterval, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-updateInterval") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicUpdateInterval, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-gainValue") == 0) {
            if ((err = get_float(tp, &arg, &dynamicGain, 0.0, 1.0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if ((strcasecmp(option, "-singleThreshold") == 0)
		    || (strcasecmp(option, "-singleThreshhold") == 0)) 
        {
            if ((err = get_int(tp, &arg, &dynamicThreshold1, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if ((strcasecmp(option, "-multiThreshold") == 0)
		    || (strcasecmp(option, "-multiThreshhold") == 0)) 
        {
            if ((err = get_int(tp, &arg, &dynamicThresholdN, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-startDelay") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicPleaseStartDelay, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-initial-env") == 0) {
            if ((err = get_env_var(p, &arg, envp, &envc)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-pass-header") == 0) {
            if ((err = get_pass_header(p, &arg, &dynamic_pass_headers)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-appConnTimeout") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicAppConnectTimeout, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-idle-timeout") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamic_idle_timeout, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-listen-queue-depth") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicListenQueueDepth, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-min-server-life") == 0) {
            if ((err = get_int(tp, &arg, &dynamicMinServerLife, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-restart-delay") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicRestartDelay, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-init-start-delay") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicInitStartDelay, 0)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-processSlack") == 0) {
            if ((err = get_u_int(tp, &arg, &dynamicProcessSlack, 1)))
                return invalid_value(tp, name, NULL, option, err);
        }
        else if (strcasecmp(option, "-restart") == 0) {
            dynamicAutoRestart = 1;
        }
        else if (strcasecmp(option, "-autoUpdate") == 0) {
            dynamicAutoUpdate = 1;
		}
        else if (strcasecmp(option, "-flush") == 0) {
            dynamicFlush = TRUE;
        }
        else {
            return ap_psprintf(tp, "%s: invalid option: %s", name, option);
        }
    } /* while */

    if (dynamicProcessSlack >= dynamicMaxProcs + 1) {
	    /* the kill policy would work unexpectedly */
    	return ap_psprintf(tp, 
            "%s: processSlack (%u) must be less than maxProcesses (%u) + 1", 
        	name, dynamicProcessSlack, dynamicMaxProcs);
    }

    /* Move env array to a surviving pool, leave 2 extra slots for 
     * WIN32 _FCGI_MUTEX_ and _FCGI_SHUTDOWN_EVENT_ */
    dynamicEnvp = (char **)ap_pcalloc(p, sizeof(char *) * (envc + 4));
    memcpy(dynamicEnvp, envp, sizeof(char *) * envc);

    return NULL;
}

void *fcgi_config_create_dir_config(pool *p, char *dummy)
{
    fcgi_dir_config *dir_config = ap_pcalloc(p, sizeof(fcgi_dir_config));

    dir_config->authenticator_options = FCGI_AUTHORITATIVE;
    dir_config->authorizer_options = FCGI_AUTHORITATIVE;
    dir_config->access_checker_options = FCGI_AUTHORITATIVE;

    return dir_config;
}


const char *fcgi_config_new_auth_server(cmd_parms * cmd,
    void * dircfg, const char *fs_path, const char * compat)
{
    fcgi_dir_config * dir_config = (fcgi_dir_config *) dircfg;
    pool * const tp = cmd->temp_pool;
    char * auth_server;

#ifdef APACHE2
    if (apr_filepath_merge(&auth_server, "", fs_path, 0, cmd->pool))
        return ap_psprintf(tp, "%s %s: invalid filepath", cmd->cmd->name, fs_path);
#else
    auth_server = (char *) ap_os_canonical_filename(cmd->pool, fs_path);
#endif

    auth_server = ap_server_root_relative(cmd->pool, auth_server);

    /* Make sure its already configured or at least a candidate for dynamic */
    if (fcgi_util_fs_get_by_id(auth_server, fcgi_util_get_server_uid(cmd->server),
                               fcgi_util_get_server_gid(cmd->server)) == NULL) 
    {
        const char *err = fcgi_util_fs_is_path_ok(tp, auth_server, NULL);
        if (err)
            return ap_psprintf(tp, "%s: \"%s\" %s", cmd->cmd->name, auth_server, err);
    }

    if (compat && strcasecmp(compat, "-compat"))
        return ap_psprintf(cmd->temp_pool, "%s: unknown option: \"%s\"", cmd->cmd->name, compat);

    switch((int)cmd->info) {
        case FCGI_AUTH_TYPE_AUTHENTICATOR:
            dir_config->authenticator = auth_server;
            dir_config->authenticator_options |= (compat) ? FCGI_COMPAT : 0;
            break;
        case FCGI_AUTH_TYPE_AUTHORIZER:
            dir_config->authorizer = auth_server;
            dir_config->authorizer_options |= (compat) ? FCGI_COMPAT : 0;
            break;
        case FCGI_AUTH_TYPE_ACCESS_CHECKER:
            dir_config->access_checker = auth_server;
            dir_config->access_checker_options |= (compat) ? FCGI_COMPAT : 0;
            break;
    }

    return NULL;
}

const char *fcgi_config_set_authoritative_slot(cmd_parms * cmd,
    void * dir_config, int arg)
{
    int offset = (int)(long)cmd->info;

    if (arg)
        *((u_char *)dir_config + offset) |= FCGI_AUTHORITATIVE;
    else
        *((u_char *)dir_config + offset) &= ~FCGI_AUTHORITATIVE;

    return NULL;
}
