/*
 * 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 <stdarg.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);
}

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

void lscapi_log_rerror(const char *file, int line, int level,
                        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, 0, r, "[host %s] %s", r->hostname, buf);
#else
    ap_log_rerror_(file, line, (lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE),
                    level, 0, r, "[host %s] %s", r->hostname, buf);
#endif // APACHE2_2
    va_end(args);
}


//#pragma GCC visibility push(default)

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

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;

    if(ugid) {
        lveuid = ugid->uid;
        lvegid = ugid->gid;
        userdir = ugid->userdir;
    } else if(svrcfg->suphp_uid) {
        lveuid = svrcfg->suphp_uid;
        lvegid = svrcfg->suphp_gid;
    } else if(svrcfg->ruid) {
        lveuid = svrcfg->ruid;
        lvegid = svrcfg->rgid;
    } else if(svrcfg->itk_uid) {
        lveuid = svrcfg->itk_uid;
        lvegid = svrcfg->itk_gid;
    } else {
#ifdef WITH_DEFAULT_UID_GID
        lveuid = svrcfg->default_uid;
        lvegid = svrcfg->default_gid;
#else
        /* Do not use apache user/group at all */
        snprintf(errbuf, errlen, "Could not determine uid/gid for request");
        *statusPtr = HTTP_SERVICE_UNAVAILABLE;
        return NULL;
#endif
    }

#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_debug_enabled(lscapi, svrcfg->debug_enabled);

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

#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;
    int contentLength;

    lscapi_recreate_http_header(r, &hdrIndexPtr, &hdrOffsets, 
                                &hdrOffsetsNum, &contentLength,
                                &httpHeader, &httpHeaderLen);
    
    //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "content length found: %d; remaining is %lu", contentLength, r->remaining);

    // set length of remaining request body and callback to read it
    lscapi_set_user_get_body(lscapi, 
                             contentLength > 0 ? contentLength : r->remaining, 
                             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_ERROR);
    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;
}

//#pragma GCC visibility pop
