/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * Copyright 2014 Cloud Linux Zug GmbH
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 *
 * The part the support library for lsapi & proxy_lsapi Apache modules
 * author Alexander Demeshko <ademeshko@cloudlinux.com>
 *
 */
 
#include <apr.h>
#include <ap_config.h>
#include "sulsphp.h"

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <time.h>
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#if APR_HAVE_FCNTL_H
#include <fcntl.h>
#endif

#include <pwd.h>
#include <grp.h>

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <signal.h>

#include "lve.h"
#include "sha1.h"

#ifdef SULSPHP_MAXPATH
#undef SULSPHP_MAXPATH
#endif // SULSPHP_MAXPATH

#if defined(PATH_MAX)
#define SULSPHP_MAXPATH PATH_MAX
#elif defined(MAXPATHLEN)
#define SULSPHP_MAXPATH MAXPATHLEN
#else
#define SULSPHP_MAXPATH 8192
#endif

#ifdef SULSPHP_ENVBUF
#undef SULSPHP_ENVBUF
#endif // SULSPHP_ENVBUF
#define SULSPHP_ENVBUF 256

#ifdef SULSPHP_UMASK
#undef SULSPHP_UMASK
#endif // SULSPHP_UMASK
#define SULSPHP_UMASK 022

#ifdef SULSPHP_SECRET_SIZE
#undef SULSPHP_SECRET_SIZE
#endif // SULSPHP_SECRET_SIZE
#define SULSPHP_SECRET_SIZE 16

#ifdef SULSPHP_SALT_SIZE
#undef SULSPHP_SALT_SIZE
#endif // SULSPHP_SALT_SIZE
#define SULSPHP_SALT_SIZE 16

extern char **environ;
static FILE *log = NULL;

static const char *const safe_env_lst[] =
{
    /* variable name starts with */
    "LSAPI_",
    "PHP_LSAPI_",
    NULL
};

static void log_err(const char *fmt,...) 
    __attribute__((format(printf,1,2)));
static void log_no_err(const char *fmt,...)  
    __attribute__((format(printf,1,2)));
static void err_output(int is_error, const char *fmt, va_list ap) 
    __attribute__((format(printf,2,0)));

static void err_output(int is_error, const char *fmt, va_list ap)
{
#ifdef SULSPHP_LOG_EXEC
    time_t timevar;
    struct tm *lt;

    if (!log) {
#if defined(_LARGEFILE64_SOURCE) && HAVE_FOPEN64
        if ((log = fopen64(SULSPHP_LOG_EXEC, "a")) == NULL) {
#else
        if ((log = fopen(SULSPHP_LOG_EXEC, "a")) == NULL) {
#endif
            fprintf(stderr, SULSPHP_PROGRAM_NAME " failure: could not open log file\n");
            perror("fopen");
            dl_clean_all_lve();
            exit(1);
        }
    }

    if (is_error) {
        fprintf(stderr, SULSPHP_PROGRAM_NAME " policy violation: see its log for more "
                        "details\n");
    }

    time(&timevar);
    lt = localtime(&timevar);

    fprintf(log, "[%d-%.2d-%.2d %.2d:%.2d:%.2d]: ",
            lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday,
            lt->tm_hour, lt->tm_min, lt->tm_sec);

    vfprintf(log, fmt, ap);

    fflush(log);
#endif /* SULSPHP_LOG_EXEC */
    return;
}

static void log_err(const char *fmt,...)
{
#ifdef SULSPHP_LOG_EXEC
    va_list ap;

    va_start(ap, fmt);
    err_output(1, fmt, ap); /* 1 == is_error */
    va_end(ap);
#endif /* SULSPHP_LOG_EXEC */
    return;
}

static void log_no_err(const char *fmt,...)
{
#ifdef SULSPHP_LOG_EXEC
    va_list ap;

    va_start(ap, fmt);
    err_output(0, fmt, ap); /* 0 == !is_error */
    va_end(ap);
#endif /* SULSPHP_LOG_EXEC */
    return;
}

#define SULSPHP_SIGN_SIZE (SULSPHP_SALT_SIZE + SHA1_DIGEST_SIZE)

static int verify_sign(const char *encoded_sign, 
                       const unsigned char secret[SULSPHP_SECRET_SIZE],
                       const unsigned char *signed_data, size_t data_len) {
    unsigned char sign[SULSPHP_SIGN_SIZE];
    unsigned char digest[SHA1_DIGEST_SIZE];
    
    /* Wrong length of encoded sign */
    if(strlen(encoded_sign) != 2 * SULSPHP_SIGN_SIZE) {
        return -1;
    }
    
    /* Decode the sign */
    for(int i = 0; i < SULSPHP_SIGN_SIZE; i++) {
        switch(encoded_sign[2*i]) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            sign[i] = (encoded_sign[2*i] - '0') * 16;
            break;
            
        case 'a':
        case 'A':
            sign[i] = 0xa0;
            break;

        case 'b':
        case 'B':
            sign[i] = 0xb0;
            break;

        case 'c':
        case 'C':
            sign[i] = 0xc0;
            break;

        case 'd':
        case 'D':
            sign[i] = 0xd0;
            break;

        case 'e':
        case 'E':
            sign[i] = 0xe0;
            break;

        case 'f':
        case 'F':
            sign[i] = 0xf0;
            break;
            
        default:
            return -1;
        } // switch(encoded_sign[2*i])

        switch(encoded_sign[2*i+1]) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            sign[i] += encoded_sign[2*i+1] - '0';
            break;
            
        case 'a':
        case 'A':
            sign[i] += 0x0a;
            break;

        case 'b':
        case 'B':
            sign[i] += 0x0b;
            break;

        case 'c':
        case 'C':
            sign[i] += 0x0c;
            break;

        case 'd':
        case 'D':
            sign[i] += 0x0d;
            break;

        case 'e':
        case 'E':
            sign[i] += 0x0e;
            break;

        case 'f':
        case 'F':
            sign[i] += 0x0f;
            break;
            
        default:
            return -1;
        } // switch(encoded_sign[2*i+1])

    } // for(int i = 0; i < SULSPHP_SIGN_SIZE; i++)
    
    SHA1_CTX sha1_context;
    SHA1_Init(&sha1_context);
    SHA1_Update(&sha1_context, sign, SULSPHP_SALT_SIZE);
    SHA1_Update(&sha1_context, secret, SULSPHP_SECRET_SIZE);
    SHA1_Update(&sha1_context, signed_data, data_len);
    SHA1_Final(&sha1_context, digest);
    if(memcmp(sign + SULSPHP_SALT_SIZE, digest, SHA1_DIGEST_SIZE) ) {
        return -1;
    }

    return 0;
}

static void read_secret(unsigned char secret[SULSPHP_SECRET_SIZE])
{
    struct stat dir_info;      /* directory info holder     */
    struct stat secret_info;   /* secret file info holder   */
    const char *dir_name;
    const char *secret_name;
    
    /*
     * Stat /etc and verify it is a directory, or error out.
     */
    dir_name = "/etc";
    if (((lstat(dir_name, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
        log_err("cannot stat directory: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(301);
    }
    
    if ((dir_info.st_uid != 0) || (dir_info.st_gid != 0)) {
        log_err("directory owned not by root: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(302);
    }
    
    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
        log_err("directory is writable by others: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(303);
    }

    /*
     * Stat /etc/sysconfig and verify it is a directory, or error out.
     */
    dir_name = "/etc/sysconfig";
    if (((lstat(dir_name, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
        log_err("cannot stat directory: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(304);
    }
    
    if ((dir_info.st_uid != 0) || (dir_info.st_gid != 0)) {
        log_err("directory owned not by root: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(305);
    }
    
    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
        log_err("directory is writable by others: (%s)\n", dir_name);
        dl_clean_all_lve();
        exit(306);
    }

    /*
     * Stat /etc/sysconfig/modlsapi.secret and verify it is a regular file, or error out.
     */
    secret_name = "/etc/sysconfig/modlsapi.secret";
    if (((lstat(secret_name, &secret_info)) != 0) || !(S_ISREG(secret_info.st_mode))) {
        log_err("cannot stat file: (%s)\n", secret_name);
        dl_clean_all_lve();
        exit(307);
    }

    if ((secret_info.st_uid != 0) || (secret_info.st_gid != 0)) {
        log_err("file owned not by root: (%s)\n", secret_name);
        dl_clean_all_lve();
        exit(308);
    }
    
    if ((secret_info.st_size != SULSPHP_SECRET_SIZE)) {
        log_err("wrong file size: (%s)\n", secret_name);
        dl_clean_all_lve();
        exit(309);
    }
    
    if ((secret_info.st_mode & S_IRWXO) 
        || (secret_info.st_mode & S_IRWXG)
        || (secret_info.st_mode & S_IWUSR)
        || (secret_info.st_mode & S_IXUSR) ) {
        log_err("wrong file permissions: (%s)\n", secret_name);
        dl_clean_all_lve();
        exit(310);
    }

    int fd = open(secret_name, O_RDONLY);
    if(fd == -1) {
        log_err("failed to open file: (%s)\n", secret_name);
        dl_clean_all_lve();
        exit(311);
    }
    if(read(fd, secret, SULSPHP_SECRET_SIZE) < SULSPHP_SECRET_SIZE)
    {
        log_err("failed to read from file: (%s)\n", secret_name);
        close(fd);
        dl_clean_all_lve();
        exit(312);
    }
    close(fd);
}

static int env_num;

static void clean_env(char **phprc_ptr, char **phprc_hash_ptr, char **phpug_hash_ptr)
{
    char pathbuf[512];
    char **cleanenv;
    char **ep;
    int cidx = 0;
    int idx;

    /* While cleaning the environment, the environment should be clean.
     * (e.g. malloc() may get the name of a file for writing debugging info.
     * Bad news if MALLOC_DEBUG_FILE is set to /etc/passwd.  Sprintf() may be
     * susceptible to bad locale settings....)
     * (from PR 2790)
     */
    char **envp = environ;
    char *empty_ptr = NULL;

    char *phprc = NULL;
    char *phprc_hash = NULL; 
    char *phpug_hash = NULL;


    environ = &empty_ptr; /* VERY safe environment */

    if ((cleanenv = (char **) calloc(SULSPHP_ENVBUF, sizeof(char *))) == NULL) {
        log_err("failed to malloc memory for environment\n");
        dl_clean_all_lve();
        exit(120);
    }

    sprintf(pathbuf, "PATH=%s", SULSPHP_SAFE_PATH);
    cleanenv[cidx] = strdup(pathbuf);
    if (cleanenv[cidx] == NULL) {
        log_err("failed to malloc memory for environment\n");
        dl_clean_all_lve();
        exit(124);
    }
    cidx++;

    for (ep = envp; *ep && cidx < SULSPHP_ENVBUF-1; ep++) {
        /*
         * Save PHPRC for later use.
         */
        if(!phprc && !strncmp(*ep, "PHPRC=", 6) ) {
            phprc = *ep;
            continue;
        }
        
        /*
         * Save PHPRC_HASH for later use.
         */
        if(!phprc_hash && !strncmp(*ep, "PHPRC_HASH=", 11) ) {
            phprc_hash = *ep;
            continue;
        }
        
        /*
         * Save PHPUG_HASH for later use.
         */
        if(!phpug_hash && !strncmp(*ep, "PHPUG_HASH=", 11) ) {
            phpug_hash = *ep;
            continue;
        }
        
        for (idx = 0; safe_env_lst[idx]; idx++) {
            if (!strncmp(*ep, safe_env_lst[idx],
                         strlen(safe_env_lst[idx]))) {
                cleanenv[cidx] = *ep;
                cidx++;
                break;
            }
        }
    }
    
    cleanenv[cidx] = NULL;

    environ = cleanenv;
    env_num = cidx;
    
    *phprc_ptr = phprc ? phprc+6 : NULL;                  /* skip PHPRC= prefix */
    *phprc_hash_ptr = phprc_hash ? phprc_hash+11 : NULL;  /* skip PHPRC_HASH= prefix */
    *phpug_hash_ptr = phpug_hash ? phpug_hash+11 : NULL;  /* skip PHPUG_HASH= prefix */
}

/*
 * We assume that the current environment will be created earlier with clean_env.
 * So allocated number of slots is equal to SULSPHP_ENVBUF, 
 * env_num contains current number of env vars,
 * and current environment does not contain PHPRC var.
 */
static void add_phprc_to_env(const char *phprc) {
    if(env_num < SULSPHP_ENVBUF - 1) {
        size_t len = strlen(phprc);
        char *ptr = malloc(6 + len + 1);
        if(!ptr) {
            log_err("failed to malloc memory for environment\n");
            dl_clean_all_lve();
            exit(401);
        }
        memcpy(ptr, "PHPRC=", 6);
        memcpy(ptr+6, phprc, len+1);
        environ[env_num++] = ptr;
        environ[env_num] = NULL;
    }
}

static void set_signals(void) 
{
    sigset_t sig_set;
    struct sigaction act;
    
    sigemptyset(&sig_set);
    sigprocmask(SIG_SETMASK, &sig_set, NULL);
    
    act.sa_flags = 0;
    act.sa_handler = SIG_DFL;
    sigaction(SIGCHLD, &act, NULL);
}

#define SULSPHP_PARENT_NAME_EVENT SULSPHP_PARENT_NAME ".event"
#define SULSPHP_PARENT_NAME_WORKER SULSPHP_PARENT_NAME ".worker"

static void check_parent(void) 
{
    char buf[SULSPHP_MAXPATH];
    int fd;
    int rc;
    
    snprintf(buf, sizeof buf, "/proc/%d/cmdline", getppid());
    fd = open(buf, O_RDONLY);
    if(fd < 0) {
        log_err("Could not get parent name\n");
        dl_clean_all_lve();
        exit(501);
    }
    
    rc = read(fd, buf, sizeof buf);
    close(fd);
    
    if(rc <= 0) {
        log_err("Could not get parent name\n");
        dl_clean_all_lve();
        exit(502);
    }

    char *last_slash = strrchr(buf, '/');
    char *parent_name = last_slash ? last_slash+1 : buf;
    
    if(strcmp(parent_name, SULSPHP_PARENT_NAME)
        && strcmp(parent_name, SULSPHP_PARENT_NAME_EVENT)
        && strcmp(parent_name, SULSPHP_PARENT_NAME_WORKER) )
    {
        log_err("Wrong parent (%s) instead of (%s)(%s)(%s)\n", 
                parent_name, SULSPHP_PARENT_NAME, 
                SULSPHP_PARENT_NAME_EVENT, SULSPHP_PARENT_NAME_WORKER);
        dl_clean_all_lve();
        exit(503);
    }
}

static void close_sockets(void)
{
    static const char fd_dir_nm[] = "/proc/self/fd";
    int stdin_sock;
    int stdout_sock;
    int stderr_sock;
    int log_sock;
    int fd_sock;
    
    DIR *fd_dir = opendir(fd_dir_nm);
    if(!fd_dir) {
        log_err("Could not open %s directory\n", fd_dir_nm);
        dl_clean_all_lve();
        exit(504);
    }

    stdin_sock = stdin ? fileno(stdin) : -1;
    stdout_sock = stdout ? fileno(stdout) : -1;
    stderr_sock = stderr ? fileno(stderr) : -1;
    log_sock = log ? fileno(log) : -1;
    fd_sock = dirfd(fd_dir);
    
    while(1) {
        struct dirent *dentry = readdir(fd_dir);
        if(!dentry) {
            break;
        }
        
        int cur_sock = 0;
        for(char *nm_ptr = dentry->d_name; *nm_ptr; nm_ptr++) {
            if(!isdigit(*nm_ptr)) {
                cur_sock = -1;
                break;
            }
            cur_sock = cur_sock * 10 + ( (*nm_ptr) - '0' );
        }
        
        /* non-digits in file name */
        if(cur_sock < 0) {
            continue;
        }
        
        if(cur_sock == stdin_sock 
           || cur_sock == stdout_sock 
           || cur_sock == stderr_sock
           || cur_sock == log_sock
           || cur_sock == fd_sock)     continue;
        
        close(cur_sock);
    }
    
    closedir(fd_dir);
}


int main(int argc, char *argv[])
{
    int userdir = 0;        /* ~userdir flag             */
    int selfstarter_mode = 0; /* selfstarter mode        */
    uid_t real_uid;         /* real user information     */
    uid_t target_uid;       /* target user information   */
    gid_t target_gid;       /* target group placeholder  */
    char *target_socket;    /* target socket to open     */
    char *target_uname;     /* target user name          */
    char *target_gname;     /* target group name         */
    char *target_homedir;   /* target home directory     */
    char *actual_uname;     /* actual user name          */
    char *actual_gname;     /* actual group name         */
    char *cmd;              /* command to be executed    */
    char cwd[SULSPHP_MAXPATH];   /* current working directory */
    char dwd[SULSPHP_MAXPATH];   /* docroot working directory */
    unsigned char secret[SULSPHP_SECRET_SIZE];
    struct passwd *pw;      /* password entry holder     */
    struct group *gr;       /* group entry holder        */
    struct stat dir_info;   /* directory info holder     */
    struct stat prg_info;   /* program info holder       */
    char *phprc;
    char *phprc_hash; 
    char *phpug_hash;
    
    /* inherited sigmask can be very confused */
    set_signals();

    /*
     * Start with a "clean" environment
     */
    clean_env(&phprc, &phprc_hash, &phpug_hash);


    /*
     * Check existence/validity of the UID of the user
     * running this program.  Error out if invalid.
     */
    real_uid = getuid();
    if ((pw = getpwuid(real_uid)) == NULL) {
        log_err("crit: invalid uid: (%"APR_PID_T_FMT")\n", real_uid);
        dl_clean_all_lve();
        exit(102);
    }
    /*
     * See if this is a 'how were you compiled' request, and
     * comply if so.
     */
    if ((argc > 1)
        && (! strcmp(argv[1], "-V"))
        && ((real_uid == 0)
#ifdef _OSD_POSIX
        /* User name comparisons are case insensitive on BS2000/OSD */
            || (! strcasecmp(SULSPHP_HTTPD_USER, pw->pw_name)))
#else  /* _OSD_POSIX */
            || (! strcmp(SULSPHP_HTTPD_USER, pw->pw_name)))
#endif /* _OSD_POSIX */
        ) {
#ifdef SULSPHP_ALLOWED_CMD
        fprintf(stderr, " -D SULSPHP_ALLOWED_CMD=\"%s\"\n", SULSPHP_ALLOWED_CMD);
#endif
#ifdef SULSPHP_ALLOWED_DIR
        fprintf(stderr, " -D SULSPHP_ALLOWED_DIR=\"%s\"\n", SULSPHP_ALLOWED_DIR);
#endif
#ifdef SULSPHP_DOC_ROOT
        fprintf(stderr, " -D SULSPHP_DOC_ROOT=\"%s\"\n", SULSPHP_DOC_ROOT);
#endif
#ifdef SULSPHP_GID_MIN
        fprintf(stderr, " -D SULSPHP_GID_MIN=%d\n", SULSPHP_GID_MIN);
#endif
#ifdef SULSPHP_HTTPD_USER
        fprintf(stderr, " -D SULSPHP_HTTPD_USER=\"%s\"\n", SULSPHP_HTTPD_USER);
#endif
#ifdef SULSPHP_LOG_EXEC
        fprintf(stderr, " -D SULSPHP_LOG_EXEC=\"%s\"\n", SULSPHP_LOG_EXEC);
#endif
#ifdef SULSPHP_PARENT_NAME
        fprintf(stderr, " -D SULSPHP_PARENT_NAME=\"%s\"\n", SULSPHP_PARENT_NAME);
#endif
#ifdef SULSPHP_SAFE_DIR
        fprintf(stderr, " -D SULSPHP_SAFE_DIR=\"%s\"\n", SULSPHP_SAFE_DIR);
#endif
#ifdef SULSPHP_SAFE_PATH
        fprintf(stderr, " -D SULSPHP_SAFE_PATH=\"%s\"\n", SULSPHP_SAFE_PATH);
#endif
#ifdef SULSPHP_UID_MIN
        fprintf(stderr, " -D SULSPHP_UID_MIN=%d\n", SULSPHP_UID_MIN);
#endif
#ifdef SULSPHP_USERDIR_SUFFIX
        fprintf(stderr, " -D SULSPHP_USERDIR_SUFFIX=\"%s\"\n", SULSPHP_USERDIR_SUFFIX);
#endif
        dl_clean_all_lve();
        exit(0);
    }
    
    if(dl_init_lve() < 0) {
        log_err("LVI initialization error\n");
        dl_clean_all_lve();
        exit(151);
    }
    
   check_parent();

    /*
     * If there are a proper number of arguments, set
     * all of them to variables.  Otherwise, error out.
     */
    if (argc != 5) {
        log_err("Wrong argument number\n");
        dl_clean_all_lve();
        exit(101);
    }
    target_uname = argv[1];
    target_gname = argv[2];
    cmd = argv[3];
    target_socket = argv[4];

    /*
     * Check to see if the user running this program
     * is the user allowed to do so as defined in
     * sulsphp.h.  If not the allowed user, error out.
     */
#ifdef _OSD_POSIX
    /* User name comparisons are case insensitive on BS2000/OSD */
    if (strcasecmp(SULSPHP_HTTPD_USER, pw->pw_name)) {
        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SULSPHP_HTTPD_USER);
        dl_clean_all_lve();
        exit(103);
    }
#else  /*_OSD_POSIX*/
    if (strcmp(SULSPHP_HTTPD_USER, pw->pw_name)) {
        log_err("user mismatch (%s instead of %s)\n", pw->pw_name, SULSPHP_HTTPD_USER);
        dl_clean_all_lve();
        exit(103);
    }
#endif /*_OSD_POSIX*/

#if 0    // Disable checking for leading slash in path 
    /*
     * Check for a leading '/' (absolute path) in the command to be executed,
     * or attempts to back up out of the current directory,
     * to protect against attacks.  If any are
     * found, error out.  Naughty naughty crackers.
     */
    if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
        || (strstr(cmd, "/../") != NULL)) {
        log_err("invalid command (%s)\n", cmd);
        dl_clean_all_lve();
        exit(104);
    }
#endif

    /*
     * Check for an allowable command.
     */
    size_t allowed_cmd_len = strlen(SULSPHP_ALLOWED_CMD);
    size_t cmd_len = strlen(cmd);
    if( (cmd_len < (allowed_cmd_len+1)) 
       || (cmd[cmd_len - allowed_cmd_len - 1] != '/') 
       ||  strncmp(cmd + cmd_len - allowed_cmd_len, SULSPHP_ALLOWED_CMD, allowed_cmd_len) )
    {
        log_err("invalid command (%s)\n", cmd);
        dl_clean_all_lve();
        exit(105);
    }

    /*
     * Check for an allowable (or safe) directory.
     */
    size_t safe_dir_len = strlen(SULSPHP_SAFE_DIR);
    size_t allowed_dir_len = strlen(SULSPHP_ALLOWED_DIR);
    if( ( (cmd_len != allowed_dir_len + allowed_cmd_len + 1)
            || strncmp(cmd, SULSPHP_ALLOWED_DIR, allowed_dir_len) 
            || (cmd[allowed_dir_len] != '/') )
     && ( (cmd_len < safe_dir_len + allowed_cmd_len + 1)
            || strncmp(cmd, SULSPHP_SAFE_DIR, safe_dir_len) 
            || (cmd[safe_dir_len] != '/') ) )
    {
        log_err("invalid command (%s)\n", cmd);
        dl_clean_all_lve();
        exit(106);
    }
        
    /*
     * Check for attempts to back up in directory tree,
     */
    if (strstr(cmd, "/../")) 
    {
        log_err("invalid command (%s)\n", cmd);
        dl_clean_all_lve();
        exit(107);
    }

    /*
     * Check to see if this is a ~userdir request.  If
     * so, set the flag, and remove the '~' from the
     * target username.
     */
    if (!strncmp("~", target_uname, 1)) {
        target_uname++;
        userdir = 1;
    }
 
    /*
     * Check to see if this is a request for self starter mode.
     * If so, set the flag, and remove the '~' from the
     * target groupname.
     */
    if (!strncmp("~", target_gname, 1)) {
        target_gname++;
        selfstarter_mode = 1;
    }
    
    /*
     * Error out if the target username is invalid.
     */
    if (strspn(target_uname, "1234567890") != strlen(target_uname)) {
        if ((pw = getpwnam(target_uname)) == NULL) {
            log_err("invalid target user name: (%s)\n", target_uname);
            dl_clean_all_lve();
            exit(108);
        }
    }
    else {
        if ((pw = getpwuid(atoi(target_uname))) == NULL) {
            log_err("invalid target user id: (%s)\n", target_uname);
            dl_clean_all_lve();
            exit(109);
        }
    }

    if(selfstarter_mode) {
        if(dl_enter_lve(pw->pw_uid) < 0) {
            log_err("Entering LVE error\n");
            dl_clean_all_lve();
            exit(152);
        }
    }
    
    /*
     * Error out if the target group name is invalid.
     */
    if (strspn(target_gname, "1234567890") != strlen(target_gname)) {
        if ((gr = getgrnam(target_gname)) == NULL) {
            log_err("invalid target group name: (%s)\n", target_gname);
            dl_clean_all_lve();
            exit(110);
        }
    }
    else {
        if ((gr = getgrgid(atoi(target_gname))) == NULL) {
            log_err("invalid target group id: (%s)\n", target_gname);
            dl_clean_all_lve();
            exit(111);
        }
    }
    target_gid = gr->gr_gid;
    if ((actual_gname = strdup(gr->gr_name)) == NULL) {
        log_err("failed to alloc memory\n");
        dl_clean_all_lve();
        exit(112);
    }

    /*
     * Save these for later since initgroups will hose the struct
     */
    target_uid = pw->pw_uid;
    actual_uname = strdup(pw->pw_name);
    target_homedir = strdup(pw->pw_dir);
    if (actual_uname == NULL || target_homedir == NULL) {
        log_err("failed to alloc memory\n");
        dl_clean_all_lve();
        exit(114);
    }

    /*
     * Read a secret from the secret file.
     */
    read_secret(secret);

    /*
     * uid and gid should be signed in any case
     */
    if(!phpug_hash) {
        log_err("not signed uid/gid\n");
        dl_clean_all_lve();
        exit(115);
    }

    pid_t uidgid[2];
    uidgid[0] = target_uid;
    uidgid[1] = target_gid;
    if(verify_sign(phpug_hash, secret, (unsigned char*)uidgid, sizeof uidgid) ) {
        log_err("uid/gid sign error\n");
        dl_clean_all_lve();
        exit(116);
    }
    
    /*
     * If PHPRC and its sign were found in the initial environment.
     */
    if(phprc && phprc_hash) { 
        if(!verify_sign(phprc_hash, secret, (unsigned char*)phprc, strlen(phprc)) ) {
            add_phprc_to_env(phprc);
        }
    } 
       

    /*
     * Log the transaction here to be sure we have an open log
     * before we setuid().
     */
    log_no_err("uid: (%s/%s) gid: (%s/%s) cmd: %s; socket: %s\n",
               target_uname, actual_uname,
               target_gname, actual_gname,
               cmd, target_socket );

    /*
     * Error out if attempt is made to execute as root or as
     * a UID less than SULSPHP_UID_MIN.  Tsk tsk.
     */
    if ((target_uid == 0) || (target_uid < SULSPHP_UID_MIN)) {
        log_err("cannot run as forbidden uid (%"APR_PID_T_FMT"/%s)\n", target_uid, cmd);
        dl_clean_all_lve();
        exit(115);
    }

    /*
     * Error out if attempt is made to execute as root group
     * or as a GID less than SULSPHP_GID_MIN.  Tsk tsk.
     */
    if ((target_gid == 0) || (target_gid < SULSPHP_GID_MIN)) {
        log_err("cannot run as forbidden gid (%"APR_PID_T_FMT"/%s)\n", target_gid, cmd);
        dl_clean_all_lve();
        exit(116);
    }
    
    /*
     *  Switch to real user to open socket. 
     */
     if (setresuid(-1, real_uid, 0) != 0) {
        log_err("failed to setuid (%"APR_PID_T_FMT": %s)\n", real_uid, cmd);
        dl_clean_all_lve();
        exit(117);
     }
     
     /*
      * Close all sockets except stdin, stdout, stderr
     */
     close_sockets();
     
     /*
      *  Create a listen socket.
      */
     int sock = create_listen_socket(target_socket, 10);
     if(sock < 0) {
        log_err("failed to create socket(%s): %s\n", target_socket, strerror(errno));
        dl_clean_all_lve();
        exit(118);
     }
     dup2(sock, 0);
     close(sock);


    /*
     *  Switch back to superuser.
     */
     if (setresuid(-1, 0, 0) != 0) {
        log_err("failed to setuid (0: %s)\n", cmd);
        dl_clean_all_lve();
        exit(118);
     }
     
    /* Entering into jail */
    if(dl_jail_lve(pw) < 0) {
        log_err("Entering LVE error\n");
        dl_clean_all_lve();
        exit(153);
    }
    
    /*
     * Change UID/GID here so that the following tests work over NFS.
     *
     * Initialize the group access list for the target user,
     * and setgid() to the target group. If unsuccessful, error out.
     */
    if (((setgid(target_gid)) != 0) || (initgroups(actual_uname, target_gid) != 0)) {
        log_err("failed to setgid (%"APR_PID_T_FMT": %s)\n", target_gid, cmd);
        dl_clean_all_lve();
        exit(120);
    }

    /*
     * setuid() to the target user.  Error out on fail.
     */
    if ((setuid(target_uid)) != 0) {
        log_err("failed to setuid (%"APR_PID_T_FMT": %s)\n", target_uid, cmd);
        dl_clean_all_lve();
        exit(121);
    }

    /*
     * Get the current working directory, as well as the proper
     * document root (dependant upon whether or not it is a
     * ~userdir request).  Error out if we cannot get either one,
     * or if the current working directory is not in the docroot.
     * Use chdir()s and getcwd()s to avoid problems with symlinked
     * directories.  Yuck.
     */
    if (getcwd(cwd, SULSPHP_MAXPATH) == NULL) {
        log_err("cannot get current working directory\n");
        dl_clean_all_lve();
        exit(122);
    }

    if (userdir) {
        if (((chdir(target_homedir)) != 0) ||
            ((chdir(SULSPHP_USERDIR_SUFFIX)) != 0) ||
            ((getcwd(dwd, SULSPHP_MAXPATH)) == NULL) ||
            ((chdir(cwd)) != 0)) {
            log_err("cannot get docroot information (%s)\n", target_homedir);
            dl_clean_all_lve();
            exit(123);
        }
    }
    else {
        if (((chdir(SULSPHP_DOC_ROOT)) != 0) ||
            ((getcwd(dwd, SULSPHP_MAXPATH)) == NULL) ||
            ((chdir(cwd)) != 0)) {
            
            log_err("cannot get docroot information (%s)\n", SULSPHP_DOC_ROOT);
            dl_clean_all_lve();
            exit(124);
        }
    }

/*
    if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
        log_err("command not in docroot (%s/%s)\n", cwd, cmd);
        dl_clean_all_lve();
        exit(125);
    }
*/

    /*
     * Stat the cwd and verify it is a directory, or error out.
     */
    if (((lstat(cwd, &dir_info)) != 0) || !(S_ISDIR(dir_info.st_mode))) {
        log_err("cannot stat directory: (%s)\n", cwd);
        dl_clean_all_lve();
        exit(126);
    }

    /*
     * Error out if cwd is writable by others.
     */
    if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP)) {
        log_err("directory is writable by others: (%s)\n", cwd);
        dl_clean_all_lve();
        exit(127);
    }

    /*
     * Error out if we cannot stat the program.
     */
    if (stat(cmd, &prg_info) != 0) {
        log_err("cannot stat program: (%s)\n", cmd);
        dl_clean_all_lve();
        exit(128);
    }

    /*
     * Error out if the program is writable by others.
     */
    if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode & S_IWGRP)) {
        log_err("file is writable by others: (%s/%s)\n", cwd, cmd);
        dl_clean_all_lve();
        exit(129);
    }

    /*
     * Error out if the file is setuid or setgid.
     */
    if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode & S_ISGID)) {
        log_err("file is either setuid or setgid: (%s/%s)\n", cwd, cmd);
        dl_clean_all_lve();
        exit(130);
    }

#if 0   // Disable checking the difference of target name/group
    /*
     * Error out if the target name/group is different from
     * the name/group of the cwd or the program.
     */
    if ((target_uid != dir_info.st_uid) ||
        (target_gid != dir_info.st_gid) ||
        (target_uid != prg_info.st_uid) ||
        (target_gid != prg_info.st_gid)) {
        log_err("target uid/gid (%"APR_PID_T_FMT"/%"APR_PID_T_FMT") mismatch "
                "with directory (%"APR_PID_T_FMT"/%"APR_PID_T_FMT") or program (%"APR_PID_T_FMT"/%"APR_PID_T_FMT")\n",
                target_uid, target_gid,
                dir_info.st_uid, dir_info.st_gid,
                prg_info.st_uid, prg_info.st_gid);
        dl_clean_all_lve();
        exit(131);
    }
#endif 
    /*
     * Error out if the program is not executable for the user.
     * Otherwise, she won't find any error in the logs except for
     * "[error] Premature end of script headers: ..."
     */
    if (!(prg_info.st_mode & S_IXUSR)) {
        log_err("file has no execute permission: (%s/%s)\n", cwd, cmd);
        dl_clean_all_lve();
        exit(132);
    }
    
    for(char *ptr = cmd+1; *ptr; ptr++) {
        if(*ptr == '/') {
            *ptr = '\0';
            if( (stat(cmd, &dir_info) != 0) || !(S_ISDIR(dir_info.st_mode))
               || (dir_info.st_mode & S_IWOTH) || (dir_info.st_mode & S_IWGRP) ) {
                
                log_err("path to file is writable for others (%s)\n", cmd);
                dl_clean_all_lve();
                exit(132);
            }
            *ptr = '/';
        }
    }

#ifdef SULSPHP_UMASK
    /*
     * umask() uses inverse logic; bits are CLEAR for allowed access.
     */
    umask(SULSPHP_UMASK);
#endif /* SULSPHP_UMASK */

    /* Be sure to close the log file so the CGI can't mess with it. */
    if (log != NULL) {
#if APR_HAVE_FCNTL_H
        /*
         * ask fcntl(2) to set the FD_CLOEXEC flag on the log file,
         * so it'll be automagically closed if the exec() call succeeds.
         */
        fflush(log);
        setbuf(log, NULL);
        if ((fcntl(fileno(log), F_SETFD, FD_CLOEXEC) == -1)) {
            log_err("error: can't set close-on-exec flag");
            dl_clean_all_lve();
            exit(133);
        }
#else
        /*
         * In this case, exec() errors won't be logged because we have already
         * dropped privileges and won't be able to reopen the log file.
         */
        fclose(log);
        log = NULL;
#endif
    }
    
    if(selfstarter_mode) {
            
        pid_t pid = fork();
        if(pid < 0) {
            log_err("(%d)%s: fork failed\n", errno, strerror(errno));
            dl_clean_all_lve();
            exit(201);
        }
        
        if(pid > 0) {
            dl_exit_lve();
            dl_destroy_lve();
            exit(0);
        }

        execl(cmd, cmd, NULL);
        log_err("execl(%s) failed: %d(%s)\n", cmd, errno, strerror(errno));
        /* It very seems that log is already closed, so write also into stderr */
        fprintf(stderr, SULSPHP_PROGRAM_NAME ": execl(%s) failed: %d(%s)\n", cmd, errno, strerror(errno));
        dl_clean_all_lve();
        exit(255);
        
    
    } else { // if(selfstarter_mode)

        execl(cmd, cmd, NULL);
        log_err("(%d)%s: exec failed (%s)\n", errno, strerror(errno), cmd);
        dl_clean_all_lve();
        exit(255);
    
    
    } //else of if(selfstarter_mode)

}
