/*
 * Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
 *
 * 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 <pwd.h>
#include <stdarg.h>
#include <unistd.h>
/* LSAPI_PRO_ONLY:BEGIN */
#include <sys/syscall.h>
/* LSAPI_PRO_ONLY:END */

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

#ifdef APACHE2_2
#define ap_log_rerror_ ap_log_rerror
#endif

// For getpwuid_r used few times here
#define PWBUF_SIZE 2048

const int *lsapilog_module_index;

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

static const char get_request_body_key[] = "mod_lsapi-get_client_block-status";

#if 0
// instead of ap_http_filter_errmsg_key defined in our (yet not applied) patch for Apache http_input_filter
static const char apache_http_filter_errmsg_key[] = "http-filter-errmsg";
#endif

static long get_request_body(char *buf, size_t bufLen, void *user_data )
{
    request_rec *r = (request_rec*)user_data;

    if(apr_table_get(r->notes, get_request_body_key)) {
        return 0;
    }

    //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body(%lu) is called", *bufLenPtr);
    long readlen = ap_get_client_block(r, buf, bufLen);
    if(readlen < 0) {
    // No use to lookup the error message until applying the corresponding http_input_filter patch
#if 0
        // Use the same technique for another filters, SSL, for example? Another key, another Where clause in the log.
        const char *errmsg = apr_table_get(r->notes, apache_http_filter_errmsg_key);
        lscapi_rlog(LOG_ERR, 0, r, "Request retrieving failed. Reason: %s, On: request body, Source: client, Where: %s",
                     errmsg ? errmsg : "unknown", 
                     errmsg ? "http filter" : "unknown" );
        if(!errmsg) {
            lscapi_rlog(LOG_ERR, 0, r, "In order to get more info about fail reason try to change LogLevel to Info in httpd.conf and restart Apache" );
        }
#else
        lscapi_rlog(LOG_ERR, 0, r, "Request retrieving failed. Reason: unknown, On: request body, Source: client, Where: unknown");
        lscapi_rlog(LOG_ERR, 0, r, "In order to get more info about fail reason try to change LogLevel to Info in httpd.conf and restart Apache" );
#endif
        apr_table_set(r->notes, get_request_body_key, "failed");
    }
    //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "get_request_body(%lu): get_client_block read %lu", *bufLenPtr, readlen);
    else if(readlen == 0) {
        apr_table_set(r->notes, get_request_body_key, "eof");
    }
    return readlen;
}


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
    int module_index = lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE;
#endif

    if (!s) {
        return;
    }

#ifndef APACHE2_2
    if (!APLOG_MODULE_IS_LEVEL(s, module_index, 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, module_index, level, status, s, "[host %s] %s",
                  s->server_hostname, buf);
#endif

/* LSAPI_PRO_ONLY:BEGIN */
#ifndef WITHOUT_SENTRY
    lscapi_sentry_log(level, fmt, buf);
#endif
/* LSAPI_PRO_ONLY:END */
    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
    int module_index = lsapilog_module_index ? *lsapilog_module_index : APLOG_NO_MODULE;
#endif

    if(!r)
        return;

#ifndef APACHE2_2
    if (!APLOG_R_MODULE_IS_LEVEL(r, module_index, 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, module_index, level, status, r, "[host %s] %s",
                   r->hostname, buf);
#endif

/* LSAPI_PRO_ONLY:BEGIN */
#ifndef WITHOUT_SENTRY
    lscapi_sentry_log(level, fmt, buf);
#endif
/* LSAPI_PRO_ONLY:END */
    va_end(args);
}

static int try_create_socket_dir(const char* nm) {
    struct stat dir_info;

    if(stat(nm, &dir_info) != 0) {

        if(errno == ENOENT) {
            // TODO: recursive creation
            if(mkdir(nm, 0755) != 0)
                return -1;
        } else {
            return -1;
        }

    } else { // if(stat(nm, &dir_info) != 0)

        if(!S_ISDIR(dir_info.st_mode)) {
            return -1;
        }

    } // else of if(stat(nm, &dir_info) != 0)

    uid_t uid;
    gid_t gid;

#if MODULE_MAGIC_NUMBER_MAJOR > 20081212
    uid = ap_unixd_config.user_id;
    gid = ap_unixd_config.group_id;
#else
    uid = unixd_config.user_id;
    gid = unixd_config.group_id;
#endif

   if(uid <= 0) { // use default user if unixd info is unreliable
      struct passwd *pw = getpwnam(SULSPHP_HTTPD_USER);
      if(pw) {
          uid = pw->pw_uid;
          gid = pw->pw_gid;
      }
   }
   return chown(nm, uid, gid);
}



static uint8_t global_salt[LSCAPI_SALT_SIZE];

int lscapi_util_init(server_rec *main_server, apr_pool_t *config_pool, char *errbuf, size_t errlen)
{
    int rc;
/* LSAPI_PRO_ONLY:BEGIN */
    int should_create_default_sock_path = 0;
/* LSAPI_PRO_ONLY:END */
#ifdef APACHE2_2
    apr_generate_random_bytes(global_salt, sizeof global_salt);
#else
    ap_random_insecure_bytes(global_salt, sizeof global_salt);
#endif

    for (server_rec *s = main_server; s != NULL; s = s->next)
    {
        lsapi_svr_conf_t *cfg = lsapi_get_svr_config(s);

        /* Default values of cfg options are still not applied at this moment */
        rc = try_create_socket_dir(cfg->socket_path ? cfg->socket_path : LSAPI_SOCK_PATH);
/* LSAPI_PRO_ONLY:BEGIN */

        /*
            TODO: Default socket path is created additionally to custom socket_path
            for /var/mod_lsapi/criu.enabled file
            It should be removed (or changed) after expected changing of location of this file
        */
        if (should_create_default_sock_path == 0 && cfg->socket_path
           && strcmp(cfg->socket_path, LSAPI_SOCK_PATH) != 0 )
        {
            should_create_default_sock_path = 1;
        }
/* LSAPI_PRO_ONLY:END */
    }
/* LSAPI_PRO_ONLY:BEGIN */
    if (should_create_default_sock_path != 0)
    {
       rc = try_create_socket_dir(LSAPI_SOCK_PATH);
    }

    rc = try_create_socket_dir(LSAPI_LOG_PATH);
/* LSAPI_PRO_ONLY:END */

    rc = 0; // ignore these errors

    return rc;
}


/*
apr_status_t lscapi_util_child_init(apr_pool_t *configpool, server_rec *s)
{
    //allocate hash uid/gid to socket name
    // register cleaner

    return APR_SUCCESS;
}
*/

/* in order to have random socket names */
#ifndef LSCAPI_WITH_RANDOM_SOCKET_NAMES
#define LSCAPI_WITH_RANDOM_SOCKET_NAMES
#endif


static apr_status_t check_script_file(request_rec *r, uint32_t uid, uint32_t gid,
                                      lsapi_dir_conf_t* cfg,
                                      int check_document_root, int check_owner, int paranoid,
                                      char *errbuf, size_t errlen)
{
    const char *script_filename = apr_table_get(r->subprocess_env, "SCRIPT_FILENAME");
    struct stat st_info;

    if(!script_filename) {
        snprintf(errbuf, errlen, "Could not determine script filename for request");
        // impossible case as SCRIPT_FILENAME was set in ap_add_common_vars - internal error
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    if(check_document_root)
    {
        const char *document_root;
#ifdef APACHE2_2
        document_root = ap_document_root(r);
#else
        document_root = ap_context_document_root(r);
#endif
        if (stat(document_root, &st_info) != 0) {
            snprintf(errbuf, errlen, "Could not stat document root (%s): errno %d", document_root, errno);
            return cfg->err_server_docroot;  // HTTP_INTERNAL_SERVER_ERROR
        }

        if(!(S_ISDIR(st_info.st_mode))) {
            snprintf(errbuf, errlen, "Document root (%s) is not directory", document_root);
            return cfg->err_server_docroot;  // HTTP_INTERNAL_SERVER_ERROR
        }

        if(st_info.st_uid != uid) {
            snprintf(errbuf, errlen, "document root (%s) is not owned by uid %u",
                    document_root, uid);
            return cfg->err_server_docroot;  // HTTP_INTERNAL_SERVER_ERROR
        }


    } //if(check_document_root)

    if (stat(script_filename, &st_info) != 0) {
    	snprintf(errbuf, errlen, "Could not stat script filename (%s): errno %d", script_filename, errno);
    	return HTTP_NOT_FOUND;
    }

    if(!(S_ISREG(st_info.st_mode))) {
    	snprintf(errbuf, errlen, "Script file (%s) is not regular", script_filename);
    	return HTTP_NOT_FOUND;
    }


    if(check_owner)
    {

        if(st_info.st_uid != uid) {
            snprintf(errbuf, errlen, "script file (%s) is not owned by uid %u",
                    script_filename, uid);
            return cfg->err_script_perms;  // HTTP_INTERNAL_SERVER_ERROR
        }

        if(paranoid) {
            if( (st_info.st_mode & S_IWOTH) ) {
                snprintf(errbuf, errlen, "Script file (%s) is writable by others", script_filename);
                return cfg->err_script_perms;  // HTTP_INTERNAL_SERVER_ERROR
            }
        }
    } //if(check_owner)

    return OK;
}

static void set_auth(request_rec *r) {
    apr_table_unset(r->subprocess_env, "PHP_AUTH_USER");
    apr_table_unset(r->subprocess_env, "PHP_AUTH_PW");
    if(r->headers_in) {
        const char *auth = NULL;
        auth = apr_table_get(r->headers_in, "Authorization");
        if(auth && auth[0] != 0 && strncmp(auth, "Basic ", 6) == 0) {
            char *user;
            char *pass;
            user = ap_pbase64decode(r->pool, auth + 6);
            if(user) {
                pass = strchr(user, ':');
                if(pass) {
                    *pass++ = '\0';
                    char* auth_user = apr_pstrdup(r->pool, user);
                    char* auth_pass = apr_pstrdup(r->pool, pass);

                    apr_table_setn(r->subprocess_env, "PHP_AUTH_USER", auth_user);
                    apr_table_setn(r->subprocess_env, "PHP_AUTH_PW", auth_pass);
                }
            }
        }
    }
}

static char *get_regexp_match (apr_pool_t *pool, ap_regex_t *rx,
                                char *buf, int match)
{
    int result;
    ap_regmatch_t *matches;
    char *bbuf;
    if (rx->re_nsub < match)
    {
        return NULL;
    }

    matches = (ap_regmatch_t *) apr_palloc (pool, (rx->re_nsub + 1) * sizeof (ap_regmatch_t));
    if (!matches)
    {
        return NULL;
    }
    if (!buf || !buf[0])
        return NULL;
    result = ap_regexec (rx, buf, rx->re_nsub + 1, matches, 0);
    if (!result)
    {
        int i;
        for (i = 0; i <= rx->re_nsub; i++)
        {
            if ((matches[i].rm_so != -1) && (i == match))
            {
                bbuf = apr_psprintf (pool, "%.*s", matches[i].rm_eo - matches[i].rm_so,
                                        buf + matches[i].rm_so);
                return bbuf;
            }
        }
    }
  return NULL;
}

static apr_status_t get_uid_gid(request_rec *r, lsapi_svr_conf_t *svrcfg, lsapi_dir_conf_t* cfg, uint32_t *uidptr, uint32_t *gidptr)
{
    // The most priority way to set uid/git is via LSAPIPath option
    if(cfg->path_regex) {
        char *user_name = NULL;
        //Parse path
        apr_finfo_t st;
        //Check if file exists
        if(apr_stat(&st, r->filename, APR_FINFO_NORM, r->pool)==APR_SUCCESS)
        {
            int need_prcd = 1;
            ap_regex_t rx;
            if (ap_regcomp (&rx, cfg->path_regex, AP_REG_EXTENDED))
            {
                need_prcd=0;
            }

            //Get user name from path
            if (need_prcd)
            {
                user_name = get_regexp_match(r->pool, &rx, r->filename, 1);
                ap_regfree (&rx);
            }
            int find_uid = 0;
            if(user_name){
                //Find user id
                find_uid = (int) ap_uname2id(user_name);
                if (find_uid > 0)
                {
                    struct passwd *pw;
                    struct passwd pwd;
                    char pwbuf[PWBUF_SIZE];

                    getpwuid_r(find_uid, &pwd, pwbuf, sizeof pwbuf, &pw);

                    if(pw)
                    {
                        *uidptr = pw->pw_uid;
                        *gidptr = pw->pw_gid;
                        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from path_regex", *uidptr, *gidptr);
                        return APR_SUCCESS;
                    }
                }
            }
        }
    } // if(cfg->path_regex)

    ap_unix_identity_t *ugid = ap_run_get_suexec_identity(r);
    // path_regex is not set or some error, to be continued
    if(cfg->lsapi_uid > 0) {
        *uidptr = cfg->lsapi_uid;
        *gidptr = cfg->lsapi_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from lsapi", *uidptr, *gidptr);
    } else if(ugid) {
        *uidptr = ugid->uid;
        *gidptr = ugid->gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suexec", *uidptr, *gidptr);
    } else if(cfg->suphp_uid > 0) {
        *uidptr = cfg->suphp_uid;
        *gidptr = cfg->suphp_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from suphp", *uidptr, *gidptr);
    } else if(cfg->ruid > 0) {
        *uidptr = cfg->ruid;
        *gidptr = cfg->rgid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from ruid", *uidptr, *gidptr);
    } else if(cfg->itk_uid > 0) {
        *uidptr = cfg->itk_uid;
        *gidptr = cfg->itk_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from itk", *uidptr, *gidptr);
    } else if(svrcfg->use_default_uid) {
        *uidptr = svrcfg->default_uid;
        *gidptr = svrcfg->default_gid;
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid %d/%d from default", *uidptr, *gidptr);
    } else {
        //ap_log_rerror(APLOG_MARK, LOG_DEBUG, 0, r, "ugid is absent");
        return APR_EGENERAL;
    }

    return APR_SUCCESS;
}


static const char* get_phprc(request_rec *r, lsapi_svr_conf_t *svrcfg, uint32_t real_uid)
{
    if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_NO)
    {
        return NULL;
    }

    if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_VAL)
    {
        return svrcfg->phprc;
    }

    if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_ENV)
    {
        return apr_table_get(r->subprocess_env, "PHPRC");
    }

    // if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_AUTO || svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_DOCROOT)

    const char *docroot;
    size_t docroot_len;
    struct stat phpini_stat;
    static const char phpini_name[] = "/php.ini";

    // Try to use document root
#ifdef APACHE2_2
    docroot = ap_document_root(r);
#else
    docroot = ap_context_document_root(r);
#endif
    docroot_len = strlen(docroot);

    {
        char phpini_in_docroot[docroot_len + sizeof(phpini_name)];
        memcpy(phpini_in_docroot, docroot, docroot_len);
        memcpy(phpini_in_docroot+docroot_len, phpini_name, sizeof(phpini_name));
        if(stat(phpini_in_docroot, &phpini_stat) < 0)
        {
            // in case of ENOENT php.ini just does not exist in document root, it is ok
            if(errno != ENOENT)
            {
                lscapi_rlog(LOG_WARNING, errno, r,
                        "Failed to stat php.ini in document root(%s) - proceed to searching php.ini in home dir", phpini_in_docroot);
            }
        } else if(!S_ISREG(phpini_stat.st_mode))
        {
            lscapi_rlog(LOG_WARNING, errno, r,
                        "php.ini in document root(%s) is not regular file - proceed to searching php.ini in home dir", phpini_in_docroot);
        }
        else
        {
            return docroot;
        }
    }

    if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_DOCROOT)
    {
        return NULL;
    }

    // if(svrcfg->phprc_source == LSAPI_PHPRC_SOURCE_AUTO)

    // php.ini is not found in document root, so try to use homedir
    struct passwd *pw;
    struct passwd pwd;
    char pwbuf[PWBUF_SIZE];
    int rc;

    rc = getpwuid_r(real_uid, &pwd, pwbuf, sizeof pwbuf, &pw);

    if(!pw)
    {
        lscapi_rlog(LOG_WARNING, rc, r,
                    "gepwuid(%d) failed - cannot find php.ini in home dir", real_uid);
        return NULL;
    }

    // we will use php.ini from home dir only if doc root is inside of home dir
    // pw->pw_dir against docroot
    size_t pwdir_len = strlen(pw->pw_dir);

    // no, it is not inside
    if(docroot_len < pwdir_len)
        return NULL;

    // prefix of docroot is not equal to home dir
    if(memcmp(docroot, pw->pw_dir, pwdir_len) != 0)
        return NULL;

    char phpini_in_homedir[pwdir_len + sizeof(phpini_name)];
    memcpy(phpini_in_homedir, pw->pw_dir, pwdir_len);
    memcpy(phpini_in_homedir+pwdir_len, phpini_name, sizeof(phpini_name));
    if(stat(phpini_in_homedir, &phpini_stat) < 0)
    {
        // in case of ENOENT php.ini just does not exist in home dir, it is ok
        if(errno != ENOENT)
        {
            lscapi_rlog(LOG_WARNING, errno, r,
                    "Failed to stat php.ini in home dir(%s) - will not use phprc", phpini_in_homedir);
        }
        return NULL;
    } else if(!S_ISREG(phpini_stat.st_mode))
    {
        lscapi_rlog(LOG_WARNING, errno, r,
                    "php.ini in document root(%s) is not regular file - will not use phprc", phpini_in_homedir);
        return NULL;
    }

    return apr_pstrdup(r->pool, pw->pw_dir);
}


/* LSAPI_PRO_ONLY:BEGIN */
const char lscapi_request_info_key[] = "lscapi-dump-request-info-key";

/*
#define DUMP_STRING(str) \
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "%s", (str) )

#define DUMP_REQUEST_ENTRY(key, val, val_format) \
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "    '%s': '%" val_format "',", (key), (val) )

#define DUMP_REQUEST_ENTRY2(key, val, val_format) \
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "        '%s': '%" val_format "',", (key), (val) )

#define DUMP_REQUEST_ENTRY2_2(key, val, val_format) \
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "        '%s': %" val_format ",", (key), (val) )

#define DUMP_REQUEST_ENTRY3(key, val, val_format) \
    ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "            '%s': '%" val_format "',", (key), (val) )
*/

#define DUMP_STRING(str) \
    fprintf( fptr, "%s\n", (str) )

#define DUMP_REQUEST_ENTRY(key, val, val_format) \
    fprintf( fptr, "    \"%s\": \"%" val_format "\",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2(key, val, val_format) \
    fprintf( fptr, "        \"%s\": \"%" val_format "\",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2_2(key, val, val_format) \
    fprintf( fptr, "        \"%s\": %" val_format ",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY3(key, val, val_format) \
    fprintf( fptr, "            \"%s\": \"%" val_format "\",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY_ASNUM(key, val, val_format) \
    fprintf( fptr, "    \"%s\": %" val_format ",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2_ASNUM(key, val, val_format) \
    fprintf( fptr, "        \"%s\": %" val_format ",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY3_ASNUM(key, val, val_format) \
    fprintf( fptr, "            \"%s\": %" val_format ",\n", (key), (val) )

#define DUMP_REQUEST_ENTRY_NODELIM(key, val, val_format) \
    fprintf( fptr, "    \"%s\": \"%" val_format "\"\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2_NODELIM(key, val, val_format) \
    fprintf( fptr, "        \"%s\": \"%" val_format "\"\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2_2_NODELIM(key, val, val_format) \
    fprintf( fptr, "        \"%s\": %" val_format "\n", (key), (val) )

#define DUMP_REQUEST_ENTRY3_NODELIM(key, val, val_format) \
    fprintf( fptr, "            \"%s\": \"%" val_format "\"\n", (key), (val) )

#define DUMP_REQUEST_ENTRY_ASNUM_NODELIM(key, val, val_format) \
    fprintf( fptr, "    \"%s\": %" val_format "\n", (key), (val) )

#define DUMP_REQUEST_ENTRY2_ASNUM_NODELIM(key, val, val_format) \
    fprintf( fptr, "        \"%s\": %" val_format "\n", (key), (val) )

#define DUMP_REQUEST_ENTRY3_ASNUM_NODELIM(key, val, val_format) \
    fprintf( fptr, "            \"%s\": %" val_format "\n", (key), (val) )


void lsapi_dump_request_info(request_rec *r, const dump_request_info_t *info)
{
    static __thread unsigned counter = 0;
    static unsigned const counter_max = 1000;
    char fname[1024];
    char buf[256];

    if (counter > counter_max)
    {
        counter = 0;
    }

    snprintf(fname, sizeof fname, "/var/log/mod_lsapi/req-%lu.%lu.%s-%010u.json",
              (unsigned long)getpid(),
              (unsigned long) syscall(SYS_gettid),
              info->domain, counter++);
    FILE *fptr = fopen(fname, "w");

    if (fptr == NULL)
    {
        return;
    }

    DUMP_STRING("{");
    DUMP_REQUEST_ENTRY("backend_path", info->backend_path, "s");
    DUMP_REQUEST_ENTRY_ASNUM("uid", info->real_uid, "u");
    DUMP_REQUEST_ENTRY_ASNUM("gid", info->real_gid, "u");
    DUMP_REQUEST_ENTRY_ASNUM("lveid", info->lve_uid, "u");
    DUMP_REQUEST_ENTRY("socket_path", info->socket_path, "s");
    if (info->phprc && info->phprc[0] != '\0')
    {
        DUMP_REQUEST_ENTRY("phprc", info->phprc, "s");
    }
    DUMP_REQUEST_ENTRY_ASNUM("debug_enabled", info->debug_enabled, "u");
    DUMP_REQUEST_ENTRY_ASNUM("accept_notify", info->accept_notify, "u");
    DUMP_REQUEST_ENTRY("domain", info->domain, "s");
#ifdef WITH_CRIU
    if (info->criu_socket_path && info->criu_socket_path[0] != '\0')
    {
        DUMP_REQUEST_ENTRY("criu_socket_path", info->criu_socket_path, "s");
    }
    if (info->criu_imgs_dir_path && info->criu_imgs_dir_path[0] != '\0')
    {
        DUMP_REQUEST_ENTRY("criu_imgs_dir_path", info->criu_imgs_dir_path, "s");
    }
#endif
    if (info->tmpdir && info->tmpdir[0] != '\0')
    {
        DUMP_REQUEST_ENTRY("tmpdir", info->tmpdir, "s");
    }

    DUMP_REQUEST_ENTRY_ASNUM("content_length", info->contentLength, "u");

    DUMP_STRING("    \"environment\": {");
    for (int i = 0; i < info->envNum; i++)
    {
        if (i < info->envNum-1)
        {
            DUMP_REQUEST_ENTRY2(info->envs[i].key, info->envs[i].val, "s");
        }
        else
        {
            DUMP_REQUEST_ENTRY2_NODELIM(info->envs[i].key, info->envs[i].val, "s");
        }
    }
    DUMP_STRING("    },");

    DUMP_STRING("    \"special_environment\": {");
    for (int i = 0; i < info->specialEnvNum; i++)
    {
        char buf[256];
        snprintf(buf, sizeof buf, "[ \"%s\", %d ]", info->specialEnvs[i].val, info->specialEnvs[i].perdir );
        if (i < info->specialEnvNum-1)
        {
            DUMP_REQUEST_ENTRY2_2(info->specialEnvs[i].key, buf, "s");
        }
        else
        {
            DUMP_REQUEST_ENTRY2_2_NODELIM(info->specialEnvs[i].key, buf, "s");
        }
    }
    DUMP_STRING("    },");

    DUMP_STRING("    \"request\": {");
    DUMP_REQUEST_ENTRY2("request", r->the_request, "s");
    // TODO: proxyreq
    // TODO: header_only
    DUMP_REQUEST_ENTRY2("proto", r->protocol, "s");
    DUMP_REQUEST_ENTRY2("host", r->hostname, "s");

    snprintf(buf, sizeof buf, "%ld.%06ld", (long)(r->request_time / 1000000), (long)(r->request_time % 1000000) );
    DUMP_REQUEST_ENTRY2_ASNUM("start_time", buf, "s");
    // TODO: range
    // TODO: chunked
    DUMP_REQUEST_ENTRY2("method", r->method, "s");
    DUMP_REQUEST_ENTRY2("content_type", r->content_type, "s");
    DUMP_REQUEST_ENTRY2("handler", r->handler, "s");
    if (r->content_encoding && r->content_encoding[0] != '\0')
    {
        DUMP_REQUEST_ENTRY2("content_encoding", r->content_encoding, "s");
    }
    // TODO: content_languages
    if (r->user && r->user[0] != '\0')
    {
        DUMP_REQUEST_ENTRY2("auth_user", r->user, "s");
    }
    if (r->ap_auth_type && r->ap_auth_type[0] != '\0')
    {
        DUMP_REQUEST_ENTRY2("auth_type", r->ap_auth_type, "s");
    }
    DUMP_REQUEST_ENTRY2("unparsed_uri", r->unparsed_uri, "s");
    DUMP_REQUEST_ENTRY2("uri", r->uri, "s");
    // TODO: filename and canonical_filename (or these are response field?)
    DUMP_REQUEST_ENTRY2("path_info", r->path_info, "s");
    if (r->args && r->args[0] != '\0')
    {
        DUMP_REQUEST_ENTRY2("query_args", r->args, "s");
    }
#ifdef APACHE2_2
    DUMP_REQUEST_ENTRY2("client_ip", r->connection->remote_ip, "s");
#else
    DUMP_REQUEST_ENTRY2("client_ip", r->connection->client_ip, "s");
#endif
    if (r->connection->remote_host && r->connection->remote_host[0] != '\0')
    {
        DUMP_REQUEST_ENTRY2("client_hostname", r->connection->remote_host, "s");
    }

    // TODO: trailers_in

    const apr_array_header_t *hdrs_arr;
    const apr_table_entry_t *hdrs;

    hdrs_arr = apr_table_elts(r->headers_in);
    hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
    DUMP_STRING("        \"headers\": {");
    for (int i = 0; i < hdrs_arr->nelts; ++i)
    {
        if (hdrs[i].key == NULL)
        {
            continue;
        }
        if (i < hdrs_arr->nelts-1)
        {
            DUMP_REQUEST_ENTRY3(hdrs[i].key, hdrs[i].val, "s");
        }
        else
        {
            DUMP_REQUEST_ENTRY3_NODELIM(hdrs[i].key, hdrs[i].val, "s");
        }
    }
    DUMP_STRING("        },");

    hdrs_arr = apr_table_elts(r->subprocess_env);
    hdrs = (const apr_table_entry_t *) hdrs_arr->elts;
    DUMP_STRING("        \"backend_environment\": {");
    for (int i = 0; i < hdrs_arr->nelts; ++i)
    {
        if (hdrs[i].key == NULL)
        {
            continue;
        }
        if (i < hdrs_arr->nelts-1)
        {
            DUMP_REQUEST_ENTRY3(hdrs[i].key, hdrs[i].val, "s");
        }
        else
        {
            DUMP_REQUEST_ENTRY3_NODELIM(hdrs[i].key, hdrs[i].val, "s");
        }
    }
    DUMP_STRING("        }");

    DUMP_STRING("    },");

    // TODO: r->connection info
    // TODO: r->server info

    DUMP_STRING("    \"response\": {");
    DUMP_REQUEST_ENTRY2_ASNUM("status_code", info->http_status_code, "d");
    DUMP_REQUEST_ENTRY2_NODELIM("status_line", r->status_line, "s");
    DUMP_STRING("    },");

    snprintf(buf, sizeof buf, "%ld.%06ld", info->create_connection_tv.tv_sec, info->create_connection_tv.tv_usec );
    DUMP_REQUEST_ENTRY_ASNUM("create_connection_time", buf, "s");

    snprintf(buf, sizeof buf, "%ld.%06ld", info->get_response_tv.tv_sec, info->get_response_tv.tv_usec );
    DUMP_REQUEST_ENTRY_ASNUM_NODELIM("get_response_time", buf, "s");

    DUMP_STRING("}");
    fclose(fptr);
}
/* LSAPI_PRO_ONLY:END */

// it was set by mod_hostinglimits
#define X_LVE_ID_HEADER "X_LVE_ID_HEADER"

lscapi_rec *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;
    char my_errbuf[256];
    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 real_uid = 0;
    uint32_t real_gid = 0;

    if(get_uid_gid(r, svrcfg, cfg, &real_uid, &real_gid) != APR_SUCCESS)
    {
        snprintf(errbuf, errlen, "Could not determine uid/gid for request");
        *statusPtr = cfg->err_server_uid; //HTTP_INTERNAL_SERVER_ERROR
        return NULL;
    }

    char *tmp_res = (char *) apr_table_get(r->notes, X_LVE_ID_HEADER);
    apr_int64_t tmp_uid = 0;
    if (tmp_res)
        tmp_uid = apr_atoi64(tmp_res);
    uint32_t lve_uid = tmp_uid > 0 ? tmp_uid : real_uid;

    ap_add_common_vars(r);
    ap_add_cgi_vars(r);
    apr_table_unset(r->subprocess_env, "GATEWAY_INTERFACE");

    /*
      MODLS-231
      https://httpoxy.org
      https://www.apache.org/security/asf-httpoxy-response.txt
    */
    apr_table_unset(r->subprocess_env, "HTTP_PROXY");

    /*
      These ones can be allowed by CGIPassAuth Apache directive. So let them be
    */
    /*
    apr_table_unset(r->subprocess_env, "HTTP_PROXY_AUTHORIZATION");
    apr_table_unset(r->subprocess_env, "HTTP_AUTHORIZATION");
    */

    set_auth(r);

    status = check_script_file(r, real_uid, real_gid, cfg, !svrcfg->skip_check_document_root,
                               svrcfg->check_target_perm, svrcfg->paranoid, errbuf, errlen);
    if(status != OK) {
        *statusPtr = status;
        return NULL;
    }

    lscapi_rec *lscapi = lscapi_create_ex(lsapi_alloc_from_pool,
                                          (lscapi_log_fn)lscapi_log_rerror, r,
                                          real_uid, real_gid, lve_uid,
                                          flagsPtr,
                                          my_errbuf, sizeof my_errbuf);
    if(!lscapi) {
        snprintf(errbuf, errlen, "Could not create lsapi connection: %s", my_errbuf);
        *statusPtr = cfg->err_lsapi_create; // HTTP_SERVICE_UNAVAILABLE
        return NULL;
    }

    lscapi_set_socket_path(lscapi, svrcfg->socket_path ? svrcfg->socket_path : LSAPI_SOCK_PATH);

    lscapi_set_phprc(lscapi, get_phprc(r, svrcfg, real_uid) );

    lscapi_set_debug_enabled(lscapi, svrcfg->debug_enabled);

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

    lscapi_set_user_spawn_backend(lscapi, (lscapi_spawn_backend_fn)lscapi_starter_send_spawn_cmd);

    lscapi_set_accept_notify(lscapi, svrcfg->accept_notify);

    lscapi_set_domain(lscapi, svrcfg->use_req_hostname ? r->hostname :
                                                            r->server->server_hostname);

    if(svrcfg->protocol_log_file != NULL)
        lscapi_set_protocol_log(lscapi, svrcfg->protocol_log_file);

/* LSAPI_PRO_ONLY:BEGIN */
#ifdef WITH_CRIU
    lscapi_set_criu_socket_path(lscapi, svrcfg->criu_socket_path);
    lscapi_set_criu_imgs_dir_path(lscapi, svrcfg->criu_imgs_dir_path ? svrcfg->criu_imgs_dir_path 
                                                                        : (svrcfg->socket_path ? svrcfg->socket_path : LSAPI_SOCK_PATH));
#endif
/* LSAPI_PRO_ONLY:END */

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

    lscapi_set_tmpdir(lscapi, svrcfg->tmpdir);

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

    // Ignore Content-Length in the case of GET method
    // For example in the case of redirecting POST request with ErrorDocument
    if(r->method_number == M_GET) {
        apr_table_setn(r->headers_in, "Content-Length", "0");
    }

    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_body_info(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, &specialEnvs);
    if(specialEnvNum > 0)
        lscapi_set_special_envs(lscapi, specialEnvs, specialEnvNum);

/* LSAPI_PRO_ONLY:BEGIN */
    if(cfg->dump_request_info)
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r,
                        "Before trying to gather dump_request_info, as cfg->dump_request_info is switched on");
        }
        dump_request_info_t *infoPtr = apr_palloc(r->pool, sizeof(dump_request_info_t));
        if(infoPtr)
        {
            memset(infoPtr, 0, sizeof(dump_request_info_t) );

            infoPtr->backend_path = backend_path;
            infoPtr->real_uid = real_uid;
            infoPtr->real_gid = real_gid;
            infoPtr->lve_uid = lve_uid;
            infoPtr->socket_path = svrcfg->socket_path ? svrcfg->socket_path : LSAPI_SOCK_PATH;
            infoPtr->phprc = get_phprc(r, svrcfg, real_uid);
            infoPtr->debug_enabled = svrcfg->debug_enabled;
            infoPtr->accept_notify = svrcfg->accept_notify;
            infoPtr->domain = svrcfg->use_req_hostname ? r->hostname : r->server->server_hostname;
#ifdef WITH_CRIU
            infoPtr->criu_socket_path = svrcfg->criu_socket_path;
            infoPtr->criu_imgs_dir_path = svrcfg->criu_imgs_dir_path ? svrcfg->criu_imgs_dir_path 
                                                            : (svrcfg->socket_path ? svrcfg->socket_path : LSAPI_SOCK_PATH);
#else
            infoPtr->criu_socket_path = "";
            infoPtr->criu_imgs_dir_path = "";
#endif
            infoPtr->tmpdir = svrcfg->tmpdir;
            infoPtr->contentLength = contentLength;
            // envs is allocated by lscapi_parse_server_vars using r->pool,
            // so has lifetime the same as the request itself and can be safely referenced here
            infoPtr->envs = envs;
            infoPtr->envNum = envNum;
            // specialEnvs is allocated by lscapi_parse_special_vars using r->pool,
            // so has lifetime the same as the request itself and can be safely referenced here
            infoPtr->specialEnvs = specialEnvs;
            infoPtr->specialEnvNum = specialEnvNum;
            gettimeofday(&(infoPtr->create_connection_tv), NULL);
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r, "before write dump_request_info into r->notes with key %s. Request time is %lu.%06lu",
                            lscapi_request_info_key,
                            (unsigned long)infoPtr->create_connection_tv.tv_sec,
                            (unsigned long)infoPtr->create_connection_tv.tv_usec );
            }

            apr_table_setn(r->notes, lscapi_request_info_key, (char*)infoPtr);
        }
        else
        {
            if (svrcfg->debug_enabled)
            {
                lscapi_rlog(APLOG_NOTICE, 0, r,
                        "not trying to gather dump_request_info, as apr_palloc(r->pool, %u) failed",
                        (unsigned)(sizeof(dump_request_info_t)));
            }
        }
    }
    else
    {
        if (svrcfg->debug_enabled)
        {
            lscapi_rlog(APLOG_NOTICE, 0, r,
                        "not trying to gather dump_request_info, as cfg->dump_request_info is switched off");
        }
    }

/* LSAPI_PRO_ONLY:END */
    return lscapi;
}
