#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <string.h>
#include <unistd.h>
#include <sys/un.h> /* for sockaddr_un */
#include <sys/stat.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/sem.h>
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_strings.h>
#include <apr_lib.h>
#include <apr_file_io.h>
#include <apr_signal.h>

#include <stdlib.h>

#include <lve/lve-ctl.h>
#include "sucgid.h"
#include <time.h>


#define DEFAULT_CGID_LISTENBACKLOG 100
#ifndef APACHE_USER
#define APACHE_USER "apache"
#endif

#define CGI_REQ    1
#define SSI_REQ    2
#define GETPID_REQ 3 /* get the pid of script created for prior request */
#define KILLCHILD_REQ 4 /* kill child script process */

#ifndef SHELL_PATH
#define SHELL_PATH "/bin/sh"
#endif

/* TODO: check suexec.c in order to close potential security issues */
/* TODO: should be >= 500 */

#ifndef MIN_GID_UID
#define MIN_GID_UID 10
#endif

#ifndef AP_SAFE_PATH
#define AP_SAFE_PATH "PATH=/usr/local/bin:/usr/bin:/bin"
#endif

static const char *const safe_env_lst[] =
{
    /* variable name starts with */
    "HTTP_",
    "SSL_",

    /* variable name is */
    "AUTH_TYPE=",
    "CONTENT_LENGTH=",
    "CONTENT_TYPE=",
    "DATE_GMT=",
    "DATE_LOCAL=",
    "DOCUMENT_NAME=",
    "DOCUMENT_PATH_INFO=",
    "DOCUMENT_ROOT=",
    "DOCUMENT_URI=",
    "GATEWAY_INTERFACE=",
    "HTTPS=",
    "LAST_MODIFIED=",
    "PATH_INFO=",
    "PATH_TRANSLATED=",
    "PHPRC=",
    "QUERY_STRING=",
    "QUERY_STRING_UNESCAPED=",
    "REMOTE_ADDR=",
    "REMOTE_HOST=",
    "REMOTE_IDENT=",
    "REMOTE_PORT=",
    "REMOTE_USER=",
    "REDIRECT_HANDLER=",
    "REDIRECT_HTTP_AUTHORIZATION=",
    "REDIRECT_QUERY_STRING=",
    "REDIRECT_REMOTE_USER=",
    "REDIRECT_STATUS=",
    "REDIRECT_URL=",
    "REQUEST_METHOD=",
    "REQUEST_URI=",
    "SCRIPT_FILENAME=",
    "SCRIPT_NAME=",
    "SCRIPT_URI=",
    "SCRIPT_URL=",
    "SERVER_ADMIN=",
    "SERVER_NAME=",
    "SERVER_ADDR=",
    "SERVER_PORT=",
    "SERVER_PROTOCOL=",
    "SERVER_SIGNATURE=",
    "SERVER_SOFTWARE=",
    "UNIQUE_ID=",
    "USER_NAME=",
    "TZ=",
    NULL
};

static struct liblve *lve = 0; /* not available by default */

static int daemon_should_exit = 0;

static int child_count_semid = 0;

static void daemon_signal_handler(int sig)
{
	if (sig == SIGHUP) {
		++daemon_should_exit;
	}
}

static void log_error(const char *fmt, ...) {
	char log_buffer[1024],current_data_string[128];
  	time_t rawtime;
  	struct tm * timeinfo;

	va_list args;
	va_start(args, fmt);
	
	time(&rawtime);
  	timeinfo = localtime(&rawtime);

  	strftime(current_data_string,128,"%c",timeinfo);
	sprintf(log_buffer,"[%s] [error] %s\n",current_data_string,fmt);

	vfprintf(stderr,log_buffer,args);
	va_end(args);
}

/* deal with incomplete reads and signals
 * assume you really have to read buf_size bytes
 */
static apr_status_t sock_read(int fd, void *vbuf, size_t buf_size)
{
    char *buf = vbuf;
    int rc;
    size_t bytes_read = 0;

    do {
        do {
            rc = read(fd, buf + bytes_read, buf_size - bytes_read);
        } while (rc < 0 && errno == EINTR);
        switch(rc) {
        case -1:
            return errno;
        case 0: /* unexpected */
            return ECONNRESET;
        default:
            bytes_read += rc;
        }
    } while (bytes_read < buf_size);

    return APR_SUCCESS;
}

static apr_status_t get_req(int fd, apr_pool_t *pool, char **argv0, char ***env,
                            sucgid_req_t *req,char **filename,char **uri, 
			    char ***argv, char** err_log)
{
    int i;
    char **environ;
    char **arumnets;

    apr_status_t stat;

    /* read the request header */
    stat = sock_read(fd, req, sizeof(*req));
    if (stat != APR_SUCCESS) {
        return stat;
    }

    if (req->req_type == GETPID_REQ || req->req_type == KILLCHILD_REQ) {
        /* no more data sent for this request */
        return APR_SUCCESS;
    }

    /* Read the filename, argv0, uri, and args */
    *filename = apr_pcalloc(pool, req->filename_len + 1);
    *argv0 = apr_pcalloc(pool, req->argv0_len + 1);
    *uri = apr_pcalloc(pool, req->uri_len + 1);

    if ((stat = sock_read(fd, *filename, req->filename_len)) != APR_SUCCESS ||
        (stat = sock_read(fd, *argv0, req->argv0_len)) != APR_SUCCESS ||
        (stat = sock_read(fd, *uri, req->uri_len)) != APR_SUCCESS) {
        return stat;
    }
    if(req->err_log_len > 0) {
      *err_log = apr_pcalloc(pool, req->err_log_len + 1);
      if ((stat = sock_read(fd, *err_log, req->err_log_len)) != APR_SUCCESS) {
        return stat;
      }
    }
    else *err_log = NULL;

    /* read the environment variables */
    environ = apr_pcalloc(pool, (req->env_count + 2 + 1) *sizeof(char *));
    int env_idx = 0;
    environ[env_idx++] = AP_SAFE_PATH;
    
    for (i = 0; i < req->env_count; i++) {
        apr_size_t curlen;

        if ((stat = sock_read(fd, &curlen, sizeof(curlen))) != APR_SUCCESS) {
            return stat;
        }
        char *curr_env = apr_pcalloc(pool, curlen + 1);
        if ((stat = sock_read(fd, curr_env, curlen)) != APR_SUCCESS) {
            return stat;
        }
        int idx;
        for (idx = 0; safe_env_lst[idx]; idx++) {
            if (!strncmp(curr_env, safe_env_lst[idx],
                         strlen(safe_env_lst[idx]))) {
                environ[env_idx++] = curr_env;
                break;
            }
        }
    }
    *env = environ;
    
    
    arumnets = apr_pcalloc(pool, (req->args_count + 2) *sizeof(char *));
    for (i = 0; i < req->args_count; i++) {
        apr_size_t curlen;

        if ((stat = sock_read(fd, &curlen, sizeof(curlen))) != APR_SUCCESS) {
            return stat;
        }
        arumnets[i] = apr_pcalloc(pool, curlen + 1);
        if ((stat = sock_read(fd, arumnets[i], curlen)) != APR_SUCCESS) {
            return stat;
        }
    }
    *argv = arumnets;

    return APR_SUCCESS;
}

static int set_limits(struct rlimit *limit_cpu, struct rlimit *limit_mem, struct rlimit *limit_nproc) {
  #ifdef RLIMIT_CPU
  if (limit_cpu->rlim_max > 0 && setrlimit(RLIMIT_CPU, limit_cpu) != 0) {
    return errno;
  }
  #endif

#if defined(RLIMIT_AS) || defined(RLIMIT_DATA) || defined(RLIMIT_VMEM)
  if (limit_mem->rlim_max > 0) {
#if defined(RLIMIT_AS)
    if(setrlimit(RLIMIT_AS, limit_mem) != 0) {
#elif defined(RLIMIT_DATA)
    if ((setrlimit(RLIMIT_DATA, limit_mem) != 0) {
#elif defined(RLIMIT_VMEM)
    if ((setrlimit(RLIMIT_VMEM, limit_mem) != 0) {
#endif
      return errno;
    }
  }
#endif

#ifdef RLIMIT_NPROC
  if (limit_nproc->rlim_max > 0 && setrlimit(RLIMIT_NPROC, limit_nproc) != 0) {
    return errno;
  }
#endif
  return 0;

}


static apr_status_t dead_yet(pid_t pid, apr_interval_time_t max_wait)
{
    apr_interval_time_t interval = 10000; /* 10 ms */
    apr_interval_time_t total = 0;

    do {
#ifdef _AIX
        /* On AIX, for processes like mod_cgid's script children where
         * SIGCHLD is ignored, kill(pid,0) returns success for up to
         * one second after the script child exits, based on when a
         * daemon runs to clean up unnecessary process table entries.
         * getpgid() can report the proper info (-1/ESRCH) immediately.
         */
        if (getpgid(pid) < 0) {
#else
        if (kill(pid, 0) < 0) {
#endif
            return APR_SUCCESS;
        }
        apr_sleep(interval);
        total = total + interval;
        if (interval < 500000) {
            interval *= 2;
        }
    } while (total < max_wait);
    return APR_EGENERAL;
}

static apr_status_t cleanup_nonchild_process(pid_t pid)
{
       pid_t killer_pid;
	if((killer_pid = fork()) == 0) {
		kill(pid, SIGTERM); 
		if (dead_yet(pid, apr_time_from_sec(3)) == APR_SUCCESS) {
			_exit(APR_SUCCESS);
		}
		log_error("CGI process %" APR_PID_T_FMT " didn't exit, sending SIGKILL", pid);
		kill(pid, SIGKILL);
		if (dead_yet(pid, apr_time_from_sec(3)) == APR_SUCCESS) {
			_exit(APR_SUCCESS);
		}
		log_error("CGI process %" APR_PID_T_FMT " didn't exit, sending SIGKILL again", pid);
		kill(pid, SIGKILL);

		_exit(APR_EGENERAL);
	}
	else if (killer_pid < 0) {
		log_error("cleanup_nonchild_process: couldn't create child process");
		return errno;
	}
	else {
		return 0;
	}
}

 static int set_context(char *curdir, struct passwd* urec, uint32_t lve_id, int ignorelve,
			sucgid_req_t *req) {
  if(urec->pw_uid < MIN_GID_UID || urec->pw_gid < MIN_GID_UID) {
    log_error("Error: gid %d and/or uid %d is less than minimum plausible", urec->pw_gid, urec->pw_uid);
    return -1;
  }
  
  apr_signal(SIGCHLD, SIG_DFL);
  
  if(lve && req->cpu != -1) {        
    uint32_t p_cookie;
    struct liblve_settings settings = {
	    req->cpu,
	    req->ncpu, // number of cores
	    req->io, // io priority on CL5 kernel, IO limit in KB/s on CL6
	    req->maxentryproc,
	    0, //virtual memory
	    100, //cpu weight
	    req->limit_mem.rlim_max / 1024 / 4, //physical memory limit
	    req->maxentryproc,  // number of processes
	    };
    enum liblve_enter_flags flags = 0;
    
    log_error("sucgi sucgid_daemon: MEMORY LIMIT LVEID %d rlim_cur %lu, rlim_max %lu settings (int)%d, (long)%ld", lve_id, req->limit_mem.rlim_cur, req->limit_mem.rlim_max, settings.ls_memory_phy, (long int)settings.ls_memory_phy);
    if(req->maxentryproc <= 0)
	    flags |= LVE_NO_MAXENTER;
    int rc = lve_setup_enter(lve, lve_id, &settings, &p_cookie, flags);
    if(rc){
	log_error("Error on lve_setup_enter %d LVE id %d", rc, lve_id);
    	return rc;
    }
 
    char buf[8192];
    int jail_rv = lve_jail(getpwuid(urec->pw_uid), buf);
    if(jail_rv == -1){
        log_error("Error on lve_jail %s LVE id %d", buf, lve_id);
    }
  } 
  
  gid_t gid_list[1] = { urec->pw_gid };
  if (setgid(urec->pw_gid) || setgroups(1, gid_list)) {
    log_error("Error '%s' occurred for gid %d", strerror(errno), urec->pw_gid);
    return -1;
  }
  
  if (setuid(urec->pw_uid)) {
    log_error("Error '%s' occurred for uid %d", strerror(errno), urec->pw_uid);
    return -1;
  }
  
  if (!curdir || !*curdir || chdir(curdir) == -1) {
    log_error("Error '%s' occurred on trying to change directory to %s", strerror(errno), !curdir ? "null" : curdir);
    return -1;
  }
  
  
  return 0;
}

static int flush_fd = 0;
static pthread_t flush_thread;
typedef struct {
	char* out_fname;
	int pipe_fd;
} flush_t ;

void flush_free(flush_t *data)
{
    close(data->pipe_fd);
    if(data->out_fname)
    	free(data->out_fname);
    free(data);
}

void* flush_work(void* unused)
{
    char buff[1024];
    struct epoll_event ev;

    for(;;)
    {
    	int epoll_rv = epoll_wait(flush_fd, &ev, 1, -1);
    	if(epoll_rv == 0)
    		continue;
    	if(epoll_rv == -1 && errno == EINTR)
    		continue;
    	if(epoll_rv == -1)
    		break;
    	flush_t *data = ev.data.ptr;
    	int readed = read(data->pipe_fd, buff, sizeof(buff));
    	if(readed >= sizeof(buff)){
    		buff[sizeof(buff)-1] = '\n';
    	}
        if(readed > 0){
            if(data->out_fname){
				int fd = open(data->out_fname, O_WRONLY | O_CREAT | O_APPEND, 0644);
				write(fd, buff, readed);
				close(fd);
            }else{
            	write(STDERR_FILENO, buff, readed);
            }
        }
        flush_free(data);
    }

    log_error("epoll_wait retured -1: %s", strerror(errno));
    close(flush_fd);
    flush_fd = 0;

    return NULL;
}

void flush_schedule(char* fname, int fd)
{
    if(!flush_fd){
        flush_fd = epoll_create(1);
        if(flush_fd < 0){
        	log_error("epoll_create failed: %s", strerror(errno));
        	return;
        }
        pthread_create(&flush_thread, NULL, flush_work, NULL);
    }

    flush_t *data = malloc(sizeof(flush_t));
    memset(data, 0, sizeof(*data));
	if(fname && *fname == '/')
        data->out_fname = strdup(fname);
    data->pipe_fd = fd;
    struct epoll_event ev = {EPOLLHUP|EPOLLONESHOT, {data} };
    if(epoll_ctl(flush_fd, EPOLL_CTL_ADD, fd, &ev) == -1)
    	flush_free(data);
}


static int run_cgi(int sd, int sd2, char *argv0, char **env, char **argv, char *curdir, 
		   struct passwd* urec, pid_t* pid, uint32_t lve_id, int ignorelve, 
		   sucgid_req_t *req, char* err_log) {
	
	int cherr[2];
	if( pipe(cherr) == -1){
		log_error("Can't create stderr pipe: %s", strerror(errno));
		return errno;
	}
	fcntl(cherr[0], F_SETFD, O_NONBLOCK);
	fcntl(cherr[1], F_SETFD, O_NONBLOCK);

	char * empty_envp[] = {NULL};
	if (!env) { /* Specs require an empty array instead of NULL;
	             * Purify will trigger a failure, even if many
	             * implementations don't.
	             */
		env = empty_envp;
	}
	
	if((*pid = fork()) == 0) {
		int status;
		dup2(sd2, STDIN_FILENO);
		dup2(sd2, STDOUT_FILENO);
		dup2(cherr[1], STDERR_FILENO);
		close(cherr[1]);
		close(sd);
		close(sd2);
		if(child_count_semid){
			struct sembuf op = {
				.sem_num = 0,
				.sem_op = -1,
				.sem_flg = SEM_UNDO
			};
			semop(child_count_semid, &op, 1);
		}
		int ret_code;
		ret_code=set_context(curdir,urec, lve_id, ignorelve, req);
		if((ignorelve&&((ret_code==-E2BIG)||(ret_code==-EALREADY)))||((!ignorelve)&&(ret_code))) {
			printf("X-LVE-Error-Status: Error on LVE enter %d LVE id %d\r\n\r\nError on LVE enter LVE id %d", ret_code,lve_id,lve_id);
			fflush(stdout);
			exit(-1);
		}
		if (set_limits(&req->limit_cpu, &req->limit_mem, &req->limit_nproc)!=0) {
		    /* do nothing? */
		}
		execve(argv0, (char * const *)argv, (char * const *)env);
		exit(-1);
	}
	else if (*pid < 0) {
		log_error("couldn't create child process");
		return errno;
	}
	else {
		close(sd2);
		close(cherr[1]);
		flush_schedule(err_log, cherr[0]);
		return 0;
	}
}


static int run_ssi(int sd, int sd2, char *argv0, char **env, char **argv, char *curdir, 
		   struct passwd* urec, pid_t* pid, uint32_t lve_id,int ignorelve,
		   apr_pool_t *ptrans, 
		   sucgid_req_t *req) {
	int newfd = dup(sd2);
	if(newfd == -1) {
		log_error("Can't set descr props 1");
		return errno;
	}
	int fd_flags = fcntl(newfd, F_GETFD, 0);
	fd_flags &= ~(FD_CLOEXEC);
	if (fcntl(newfd, F_SETFD, fd_flags) == -1) {
		log_error("Can't set descr props 1");
		return errno;
	}
			
	close(sd2);
			
	if((*pid = fork()) == 0) {
		int status;
		dup2(newfd, STDOUT_FILENO);
		close(newfd);
		close(sd);
		
		int onearg_len = 0;
		const char *newargs[4];
	
		newargs[0] = SHELL_PATH;
		newargs[1] = "-c";
				
		int i = 0;
		while (argv[i]) {
			onearg_len += strlen(argv[i]);
			++onearg_len; /* for space delimiter */
			++i;
		}
	
		switch(i) {
		case 0:
			/* bad parameters; we're doomed */
			break;
		case 1:
			/* no args, or caller already built a single string from
			* progname and args
			*/
			newargs[2] = argv[0];
			break;
		default:
		{
			char *ch, *onearg;

			ch = onearg = apr_palloc(ptrans, onearg_len);
			i = 0;
			while (argv[i]) {
				size_t len = strlen(argv[i]);
	
				memcpy(ch, argv[i], len);
				ch += len;
				*ch = ' ';
				++ch;
				++i;
			}
			--ch; /* back up to trailing blank */
			*ch = '\0';
			newargs[2] = onearg;
		}
		} /* switch end */
		newargs[3] = NULL;
		int ret_code;
		ret_code=set_context(curdir,urec, lve_id, ignorelve, req);
		if((ignorelve&&((ret_code==-E2BIG)||(ret_code==-EALREADY)))||((!ignorelve)&&(ret_code))) {
			printf("X-LVE-Error-Status: Error on LVE enter %d LVE id %d\r\n\r\nError on LVE enter LVE id %d", ret_code,lve_id,lve_id);
			fflush(stdout);
			exit(-1);
		}
		
		if (set_limits(&req->limit_cpu, &req->limit_mem, &req->limit_nproc)!=0) {
		    /* do nothing? */
		}
		
		execve(SHELL_PATH, (char * const *) newargs, (char * const *)env);	            
		exit(-1);
	}
	else if (*pid < 0) {
		log_error("couldn't create child process");
		close(newfd);
		return errno;
	}
	else {
		close(newfd);
		return 0;
	}
}



int main(int argc, char *argv[])
{
    /* init apr library */
    apr_status_t rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        log_error("Can't init apr library");
        return 1;
    }
	
    if(argc < 4) {
      log_error("Not enought arguments");
      return 2;
    }

    // TODO: either close any file descriptor from [3..30]
    // or create fake file descriptor (e.g. /dev/urandom) and dup it to [3..30]
    
    pid_t parent_pid = atoi(argv[1]);
    const char * sockname = argv[2];	
    if(atoi(argv[3])){
        lve = init_lve(malloc, free);
	if(!lve)
            log_error("init_lve failed");
    }
    if(argc >= 5)
	    child_count_semid = atoi(argv[4]);
    int rc;

    /* global pool and pool cleaned up on each iteration */
    apr_pool_t *gpool, *ptrans;    
    apr_pool_create(&gpool, NULL);
    apr_pool_create(&ptrans, gpool);
	
    struct sockaddr_un unix_addr;
    int sd, sd2;
    mode_t omask;
    apr_socklen_t len;
    /* server_rec *main_server = data; */
    
    /* connection_id => script pid map */
    apr_hash_t *script_hash = apr_hash_make(gpool);

    /* ignore SIGCHLD & catch SIGHUP  */
    apr_signal(SIGCHLD, SIG_IGN);
    apr_signal(SIGHUP, daemon_signal_handler);

    if ((sd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
      log_error("Could not create unix domain socket");
      return errno;
    }
	
    memset(&unix_addr, 0, sizeof(unix_addr));
    unix_addr.sun_family = AF_UNIX;
    strncpy(unix_addr.sun_path, sockname, sizeof(unix_addr.sun_path));
 
    omask = umask(0077); /* so that only Apache can use socket */
    rc = bind(sd, (struct sockaddr *)&unix_addr, sizeof(unix_addr));
    umask(omask); /* can't fail, so can't clobber errno */
    if (rc < 0) {
      log_error("Couldn't bind unix domain socket %s",sockname);
      return errno;
    }
  
    /* Not all flavors of unix use the current umask for AF_UNIX perms */
    rv = apr_file_perms_set(sockname, APR_FPROT_UREAD|APR_FPROT_UWRITE|APR_FPROT_UEXECUTE);
    if (rv != APR_SUCCESS) {
      log_error("Couldn't set permissions on unix domain socket %s",sockname);
      return rv;
    }
    
    if (listen(sd, DEFAULT_CGID_LISTENBACKLOG) < 0) {
      log_error("Couldn't listen on unix domain socket");
      return errno;
    }
 
    /* allow apache user connect to this daemon */
    struct passwd* urec = getpwnam(APACHE_USER);
    if(!urec) {
      log_error("Couldn't find user %s",APACHE_USER);
      return 1;
    }
   
    if (chown(sockname, urec->pw_uid, -1) < 0) {
      log_error("Couldn't change owner of unix domain socket %s",sockname);
      return errno;
    }
  
    len = sizeof(unix_addr);
    
    while (!daemon_should_exit) {
		
      char *argv0;
      char **env;
      char **argv;
      char *filename; 
      char *uri;
      char *err_log = NULL;  
      
      sucgid_req_t cgid_req;
      int stat = 0;
      
      apr_pool_clear(ptrans);
      
      
      sd2 = accept(sd, (struct sockaddr *)&unix_addr, &len);
      if (sd2 < 0) {
	if (errno == ENETDOWN) {
	  /* The network has been shut down, no need to continue. Die gracefully */
	  ++daemon_should_exit;
	}
	if (errno != EINTR) {
	  log_error("Error accepting on sucgid socket");
	}
	continue;
      }

      
      stat = get_req(sd2, ptrans, &argv0, &env, &cgid_req, &filename, &uri,&argv, &err_log);
      if (stat != 0) {
	log_error("Error reading request on sucgid socket");
	close(sd2);
	continue;
      }

      
      if (cgid_req.ppid != parent_pid) {
	log_error("CGI request received from wrong server instance; "
		  "see ScriptSock directive");
	close(sd2);
	continue;
      }

      
      if (cgid_req.req_type == KILLCHILD_REQ || cgid_req.req_type == GETPID_REQ) {
	pid_t pid = 0;
	pid = (pid_t)((long)apr_hash_get(script_hash, &cgid_req.conn_id, sizeof(cgid_req.conn_id)));
	if (write(sd2, &pid, sizeof(pid)) != sizeof(pid)) {
	  log_error("Error writing pid %d to handler", pid);
	}
	close(sd2);
       if(cgid_req.req_type == KILLCHILD_REQ && pid) {
	  cleanup_nonchild_process(pid);
	}
	continue;
      }

      
      char *last_slash = strrchr(filename, '/');
      if(!last_slash) { 
	/* child will exit */
	*filename = '\0'; 
      }
      else { 
	*last_slash = '\0'; 
      }
		
      pid_t pid;
      struct passwd rec_ugid;
      rec_ugid.pw_uid = cgid_req.uid==empty_ugid?urec->pw_uid:cgid_req.uid;
      rec_ugid.pw_gid = cgid_req.gid==empty_ugid?urec->pw_gid:cgid_req.gid;
      uint32_t lve_id = cgid_req.lve_id > 0?cgid_req.lve_id:rec_ugid.pw_uid;
      int ignorelve = cgid_req.ignore_lve;

      if(cgid_req.req_type == CGI_REQ) {
	int status = run_cgi(sd, sd2, argv0, env, argv, filename, &rec_ugid, &pid,
			     lve_id,ignorelve, &cgid_req, err_log);
	/* On error we have to exit since it was not possible to allocate resources
	 *  N.B. run_cgi does not clean up allocated resources on error
	 */
	if(status != 0) return status;
      } else {
	int status = run_ssi(sd, sd2, argv0, env, argv, filename, &rec_ugid, &pid, 
			     lve_id,ignorelve,
			     ptrans, &cgid_req);
	/* On error we have to exit since it was not possible to allocate resources
	 *  N.B. run_cgi does not clean up allocated resources on error
	 */
	if(status != 0) return status;
      }
      /* update conn_id => pid map */
      void *key;
      if (apr_hash_get(script_hash, &cgid_req.conn_id, sizeof(cgid_req.conn_id))) {
	key = &cgid_req.conn_id;
      }
      else {
	key = apr_pcalloc(gpool, sizeof(cgid_req.conn_id));
	memcpy(key, &cgid_req.conn_id, sizeof(cgid_req.conn_id));
      }
      apr_hash_set(script_hash, key, sizeof(cgid_req.conn_id),(void *)((long)pid));
    }
    if(lve)
      destroy_lve(lve);
    return -1; /* should be <= 0 to distinguish from startup errors */
}
