/*
 * 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 <lscapi_util.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <unistd.h>

#include <http_request.h>
#include <util_script.h>

#ifdef APACHE2_2
#define ap_log_rerror_ ap_log_rerror
#endif

const int *lsapilog_module_index;

#ifndef SULSPHP_BIN
#define SULSPHP_BIN HTTPD_ROOT "/bin/sulsphp"
#endif

static void* alloc_by_request(size_t sz, void *user_data) 
{
    return apr_palloc(((request_rec*)user_data)->pool, sz);
}

// Passing client input via input filters instead of ap_get_client_block. We need that? It seems that not
#if 0
static int get_request_body(char *buf, size_t* bufLenPtr, void *user_data ) 
{
    // UGLY, using some context to store brigade? Store this context somewhere in request_rec ?
    static apr_bucket_brigade *ib;
    //static int eosWasRead;

    int rc;
    request_rec *r = (request_rec*)user_data;
    
    if(!ib) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: creating brigade");
        ib = apr_brigade_create(r->pool, r->connection->bucket_alloc);
        if(!ib) {
            ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: apr_brigade_create failed");
            return -1;
        }
        //eosWasRead = 0;
    }
    
    //if(eosWasRead) {
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: ret 0 due to eosWasRead");
        //*bufLenPtr = 0;
        //apr_brigade_destroy(ib);
        //ib = NULL;
        //eosWasRead = 0;
        //return 0;
    //}
    
    rc = ap_get_brigade(r->input_filters, ib,
                        AP_MODE_READBYTES, APR_BLOCK_READ,
                        *bufLenPtr);
    if(rc != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: ap_get_brigade failed");
        apr_brigade_destroy(ib);
        ib = NULL;
        //eosWasRead = 0;
        return -1;
    }
    
    //eosWasRead = APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(ib));
            
    apr_size_t bufLen = *bufLenPtr;
    rc = apr_brigade_flatten(ib, buf, &bufLen);

    apr_brigade_cleanup(ib);

    if(rc != APR_SUCCESS) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: ap_brigade_flatten failed");
        apr_brigade_destroy(ib);
        ib = NULL;
        //eosWasRead = 0;
        return -1;
    }
    
    *bufLenPtr = bufLen;
    
    if(!bufLen) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: ret 0 due to bufLen=0");
        apr_brigade_destroy(ib);
        ib = NULL;
        //eosWasRead = 0;
    } else {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body: NORMAL ret 0 due to bufLen=%lu", bufLen);
    }
    
    return 0;
}
#endif

static int get_request_body(char *buf, size_t* bufLenPtr, void *user_data ) 
{
    long readlen;
    request_rec *r = (request_rec*)user_data;
    
    //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body(%lu) is called", *bufLenPtr);
    readlen = ap_get_client_block(r, buf, *bufLenPtr);
    if(readlen < 0) {
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body(%lu): get_client_block failed", *bufLenPtr);
        return -1;
    }
    *bufLenPtr = readlen;
    //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body(%lu): get_client_block read %lu", *bufLenPtr, readlen);
    return 0;
}


void lscapi_log_error(const char *file, int line, int level, apr_status_t status,
                        const server_rec *s, const char *fmt, ...)
{
    char buf[8192];
    va_list args;

#ifndef APACHE2_2
    if (!APLOG_MODULE_IS_LEVEL(s, 
                                 (lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE), 
                                 level)) 
    {
        return;
    }
#endif
    va_start(args, fmt);
    vsnprintf(buf, sizeof buf, fmt, args);

#ifdef APACHE2_2
    ap_log_error(file, line,
                    level, status, s, "[host %s] %s", s->server_hostname, buf);
#else
    ap_log_error_(file, line, (lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE),
                    level, status, s, "[host %s] %s", s->server_hostname, buf);
#endif // APACHE2_2
    va_end(args);
}

void lscapi_log_rerror(const char *file, int line, int level,
                       apr_status_t status,
                        const request_rec *r, const char *fmt, ...)

{
    char buf[8192];
    va_list args;

#ifndef APACHE2_2
    if (!APLOG_R_MODULE_IS_LEVEL(r, 
                                 (lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE), 
                                 level)) 
    {
        return;
    }
#endif
    va_start(args, fmt);
    vsnprintf(buf, sizeof buf, fmt, args);

#ifdef APACHE2_2
    ap_log_rerror(file, line,
                    level, status, r, "[host %s] %s", r->hostname, buf);
#else
    ap_log_rerror_(file, line, (lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE),
                    level, status, r, "[host %s] %s", r->hostname, buf);
#endif // APACHE2_2
    va_end(args);
}


void lscapi_close_connection(lsapi_conn_t *conn)
{
    lscapi_release_lsphp_conn(conn->backend);
    lscapi_destroy(conn->lscapi);
}

static apr_status_t check_script_file(const char *script_filename, uint32_t uid, uint32_t gid,
                                      int check_owner,
                                      char *errbuf, size_t errlen) 
{
    if(!script_filename) {
        snprintf(errbuf, errlen, "Could not determine script filename for request");
        return HTTP_SERVICE_UNAVAILABLE;
    }

    struct stat script_info;
    if (stat(script_filename, &script_info) != 0) {
        snprintf(errbuf, errlen, "Could not stat script filename (%s): errno %d", script_filename, errno);
        return HTTP_NOT_FOUND;
    }
    
    if(!(S_ISREG(script_info.st_mode))) {
        snprintf(errbuf, errlen, "Script file (%s) is not regular", script_filename);
        return HTTP_NOT_FOUND;
    }
    
    if(!check_owner) {
        return OK;
    }
    
    if(script_info.st_uid != uid) {
        snprintf(errbuf, errlen, "Own id for script file (%s) is %d; should be %d", 
                 script_filename, script_info.st_uid, uid);
        return HTTP_SERVICE_UNAVAILABLE;
    }
    
    if( (script_info.st_mode & S_IWOTH) ) {
        snprintf(errbuf, errlen, "Script file (%s) is writable by others", script_filename);
        return HTTP_SERVICE_UNAVAILABLE;
    }

    return OK;
}



lsapi_conn_t *lscapi_create_connection(request_rec *r, const char *backend_path,
                                       unsigned *flagsPtr, apr_status_t *statusPtr, 
                                       char *errbuf, size_t errlen,
                                       const module *m)
{
    lsapilog_module_index = &(m->module_index);

    apr_status_t status;
    int rc;
    char my_errbuf[128];
    ap_unix_identity_t *ugid = ap_run_get_suexec_identity(r);

    lsapi_dir_conf_t* cfg = (lsapi_dir_conf_t*) ap_get_module_config(r->per_dir_config, m);

    lsapi_svr_conf_t *svrcfg = ap_get_module_config(r->server->module_config, m);

    uint32_t lveuid;
    uint32_t lvegid;
    int userdir = 0;

#ifdef LSCAPI_DEBUG_UID_GID
    ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "uid nums: lsapi %d; suphp: %d; r: %d; itk: %d",
                  cfg->lsapi_uid_num, cfg->suphp_uid_num, cfg->ruid_num, cfg->itk_uid_num);
    for(int i = 0; i < cfg->lsapi_uid_num; i++) {
        ap_log_rerror(APLOG_MARK, LOG_ERR, 0, r, "lsapi uids %d/%d: %d/%d",
                      i, cfg->lsapi_uid_num, cfg->lsapi_uids[i], cfg->lsapi_gids[i] );
        
    }
    for(int i = 0; i < cfg->suphp_uid_num; i++) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "suphp uids %d/%d: %d/%d",
                      i, cfg->suphp_uid_num, cfg->suphp_uids[i], cfg->suphp_gids[i] );
        
    }
    for(int i = 0; i < cfg->ruid_num; i++) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "r uids %d/%d: %d/%d",
                      i, cfg->ruid_num, cfg->ruids[i], cfg->rgids[i] );
        
    }
    for(int i = 0; i < cfg->itk_uid_num; i++) {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "lsapi uids %d/%d: %d/%d",
                      i, cfg->itk_uid_num, cfg->itk_uids[i], cfg->itk_gids[i] );
        
    }
    
    if(cfg->lsapi_uid_num > 0) {
        lveuid = cfg->lsapi_uids[cfg->lsapi_uid_num-1];
        lvegid = cfg->lsapi_gids[cfg->lsapi_uid_num-1];
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from lsapi", lveuid, lvegid);
    } else if(ugid) {
        lveuid = ugid->uid;
        lvegid = ugid->gid;
        userdir = ugid->userdir;
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suexec", lveuid, lvegid);
    } else if(cfg->suphp_uid_num > 0) {
        lveuid = cfg->suphp_uids[cfg->suphp_uid_num-1];
        lvegid = cfg->suphp_gids[cfg->suphp_uid_num-1];
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suphp", lveuid, lvegid);
    } else if(cfg->ruid_num > 0) {
        lveuid = cfg->ruids[cfg->ruid_num-1];
        lvegid = cfg->rgids[cfg->ruid_num-1];
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from ruid", lveuid, lvegid);
    } else if(cfg->itk_uid_num > 0) {
        lveuid = cfg->itk_uids[cfg->itk_uid_num-1];
        lvegid = cfg->itk_gids[cfg->itk_uid_num-1];
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from itk", lveuid, lvegid);
    } else if(svrcfg->use_default_uid) {
        lveuid = svrcfg->default_uid;
        lvegid = svrcfg->default_gid;
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from default", lveuid, lvegid);
    } else {
        ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid is absent");
        snprintf(errbuf, errlen, "Could not determine uid/gid for request");
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
    }
#else
    if(cfg->lsapi_uid > 0) {
        lveuid = cfg->lsapi_uid;
        lvegid = cfg->lsapi_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from lsapi", lveuid, lvegid);
    } else if(ugid) {
        lveuid = ugid->uid;
        lvegid = ugid->gid;
        userdir = ugid->userdir;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suexec", lveuid, lvegid);
    } else if(cfg->suphp_uid > 0) {
        lveuid = cfg->suphp_uid;
        lvegid = cfg->suphp_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suphp", lveuid, lvegid);
    } else if(cfg->ruid > 0) {
        lveuid = cfg->ruid;
        lvegid = cfg->rgid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from ruid", lveuid, lvegid);
    } else if(cfg->itk_uid > 0) {
        lveuid = cfg->itk_uid;
        lvegid = cfg->itk_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from itk", lveuid, lvegid);
    } else if(svrcfg->use_default_uid) {
        lveuid = svrcfg->default_uid;
        lvegid = svrcfg->default_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from default", lveuid, lvegid);
    } else {
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid is absent");
        snprintf(errbuf, errlen, "Could not determine uid/gid for request");
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
    }
    if(cfg->lsapi_uid > 0) {
        lveuid = cfg->lsapi_uid;
        lvegid = cfg->lsapi_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from lsapi", lveuid, lvegid);
    } else if(ugid) {
        lveuid = ugid->uid;
        lvegid = ugid->gid;
        userdir = ugid->userdir;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suexec", lveuid, lvegid);
    } else if(cfg->suphp_uid > 0) {
        lveuid = cfg->suphp_uid;
        lvegid = cfg->suphp_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suphp", lveuid, lvegid);
    } else if(cfg->ruid > 0) {
        lveuid = cfg->ruid;
        lvegid = cfg->rgid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from ruid", lveuid, lvegid);
    } else if(cfg->itk_uid > 0) {
        lveuid = cfg->itk_uid;
        lvegid = cfg->itk_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from itk", lveuid, lvegid);
    } else if(svrcfg->use_default_uid) {
        lveuid = svrcfg->default_uid;
        lvegid = svrcfg->default_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from default", lveuid, lvegid);
    } else {
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid is absent");
        snprintf(errbuf, errlen, "Could not determine uid/gid for request");
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
    }
#endif // LSCAPI_DEBUG_UID_GID

    ap_add_common_vars(r);
    ap_add_cgi_vars(r);
    apr_table_unset(r->subprocess_env, "GATEWAY_INTERFACE");
    
    status = check_script_file(apr_table_get(r->subprocess_env, "SCRIPT_FILENAME"), 
                                lveuid, lvegid, svrcfg->check_target_perm,
                                errbuf, errlen);
    if(status != OK) {
        *statusPtr = status;
        return NULL;
    }
    
#ifdef WITH_LSAPI_LVE_ENABLED
    if(svrcfg->lve_enabled)
        *flagsPtr |= LSCAPI_CREATE_WITH_LVE; 
#endif
    
    lscapi_rec *lscapi = lscapi_create(alloc_by_request, 
                                       (lscapi_log_fn)lscapi_log_rerror, r, 
                                       lveuid, lvegid,
                                       flagsPtr, 
                                       my_errbuf, sizeof my_errbuf);
    if(!lscapi) {
        snprintf(errbuf, errlen, "Could not create lsapi connection: %s", my_errbuf);
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
    }

    lscapi_set_userdir(lscapi, userdir);

    lscapi_set_socket_path(lscapi, svrcfg->socket_path);

    lscapi_set_phprc(lscapi, svrcfg->phprc);

    lscapi_set_debug_enabled(lscapi, svrcfg->debug_enabled);

    lscapi_set_backend_path(lscapi, SULSPHP_BIN, r->handler, backend_path);

    lscapi_set_backend_coredump(lscapi, svrcfg->backend_coredump);

    if(svrcfg->selfstarter) {
        lscapi_set_user_spawn_backend(lscapi, (lscapi_spawn_backend_fn)lscapi_starter_send_spawn_cmd);
    }

#ifdef APACHE2_2
    lscapi_set_random(lscapi, (lscapi_random_fn)apr_generate_random_bytes);
#else
    lscapi_set_random(lscapi, ap_random_insecure_bytes);
#endif

    lsapi_http_header_index_t *hdrIndexPtr;
    lsapi_header_offset_t *hdrOffsets;
    size_t hdrOffsetsNum;
    char *httpHeader;
    size_t httpHeaderLen;
    size_t contentLength;

    lscapi_recreate_http_header(r, &hdrIndexPtr, &hdrOffsets, 
                                &hdrOffsetsNum, &contentLength,
                                &httpHeader, &httpHeaderLen);
    
    // Ignore Content-Length in the case of Expect: 100-continue ?
/*
    if(contentLength) {
        const char *expect = apr_table_get(r->headers_in, "Expect");
        if(expect && !strcmp(expect, "100-continue")) {
            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "Content-Length(%lu) is unreliable due to Expect:100-continue header", contentLength);
            contentLength = 0;
        }
    }
*/

    // set length of remaining request body and callback to read it
    lscapi_set_user_get_body(lscapi, contentLength, get_request_body, 
                             (lscapi_should_get_block_fn)ap_should_client_block);

    lscapi_set_header_info(lscapi, hdrIndexPtr, hdrOffsets, hdrOffsetsNum,
                            httpHeader, httpHeaderLen);
    lscapi_var_t *envs;
    int envNum = lscapi_parse_server_vars(r, &envs);
    lscapi_set_envs(lscapi, envs, envNum);

    lscapi_var_t *specialEnvs;
    int specialEnvNum = lscapi_parse_special_vars(r, cfg->phpParams, &specialEnvs);
    lscapi_set_special_envs(lscapi, specialEnvs, specialEnvNum);

    lsphp_conn_t *phpConn = lscapi_acquire_lsphp_conn(lscapi, my_errbuf, sizeof my_errbuf );
    if(!phpConn) {
        snprintf(errbuf, errlen, "Could not create lsphp connection: %s", my_errbuf);
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        lscapi_destroy(lscapi);
        return NULL;
    }

    // will be ready to get deal with request body
    status = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);
    if(status != OK) {
        lscapi_release_lsphp_conn(phpConn);
        lscapi_destroy(lscapi);
        snprintf(errbuf, errlen, "ap_client_block failed: %d", status);
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
    }

    lsapi_conn_t *conn = apr_palloc(r->pool, sizeof *conn);
    conn->r = r;
    conn->m = m;
    conn->lscapi = lscapi;
    conn->backend = phpConn;
    
    rc = lscapi_determine_conn_lsphp(phpConn, &(svrcfg->backend_info), my_errbuf, sizeof my_errbuf);
     if(rc != 0) {
        snprintf(errbuf, errlen, "Could not determine lsphp connection: %s", my_errbuf);
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        lscapi_release_lsphp_conn(phpConn);
        lscapi_destroy(lscapi);
        return NULL;
     }

     rc = lscapi_connect_lsphp(phpConn, my_errbuf, sizeof my_errbuf);
     if(rc != 0) {
        snprintf(errbuf, errlen, "Could not connect to lsphp backend: %s", my_errbuf);
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        lscapi_release_lsphp_conn(phpConn);
        lscapi_destroy(lscapi);
        return NULL;
     }

    return conn;
}
