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

/***************************************************************************
 * Description: AJP14 next generation Bi-directional protocol.             *
 * Author:      Henri Gomez <hgomez@apache.org>                            *
 ***************************************************************************/

#include "jk_context.h"
#include "jk_ajp14_worker.h"


/*
 * AJP14 Autoconf Phase
 *
 * CONTEXT QUERY / REPLY
 */

#define MAX_URI_SIZE    512

static int handle_discovery(ajp_endpoint_t * ae,
                            jk_worker_env_t *we,
                            jk_msg_buf_t *msg, jk_logger_t *l)
{
    int cmd;
    int i, j;
#if 0
    /* Not used for now */
    jk_login_service_t *jl = ae->worker->login;
#endif
    jk_context_item_t *ci;
    jk_context_t *c;
    char *buf;

#ifndef TESTME
    JK_TRACE_ENTER(l);

    ajp14_marshal_context_query_into_msgb(msg, we->virtual, l);

    jk_log(l, JK_LOG_DEBUG, "send query");

    if (ajp_connection_tcp_send_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG, "wait context reply");

    jk_b_reset(msg);

    if (ajp_connection_tcp_get_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    if ((cmd = jk_b_get_byte(msg)) != AJP14_CONTEXT_INFO_CMD) {
        jk_log(l, JK_LOG_ERROR,
               "awaited command %d, received %d",
               AJP14_CONTEXT_INFO_CMD, cmd);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (context_alloc(&c, we->virtual) != JK_TRUE) {
        jk_log(l, JK_LOG_ERROR,
               "can't allocate context room");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (ajp14_unmarshal_context_info(msg, c, l) != JK_TRUE) {
        jk_log(l, JK_LOG_ERROR,
               "can't get context reply");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    jk_log(l, JK_LOG_DEBUG, "received context");

    buf = malloc(MAX_URI_SIZE); /* Really a very long URI */

    if (!buf) {
        jk_log(l, JK_LOG_ERROR, "can't malloc buf");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    for (i = 0; i < c->size; i++) {
        ci = c->contexts[i];
        for (j = 0; j < ci->size; j++) {

#ifndef USE_SPRINTF
            snprintf(buf, MAX_URI_SIZE - 1, "/%s/%s", ci->cbase, ci->uris[j]);
#else
            sprintf(buf, "/%s/%s", ci->cbase, ci->uris[j]);
#endif

            jk_log(l, JK_LOG_INFO,
                   "worker %s will handle uri %s in context %s [%s]",
                   ae->worker->name, ci->uris[j], ci->cbase, buf);

            uri_worker_map_add(we->uri_to_worker, buf, ae->worker->name, SOURCE_TYPE_DISCOVER, l);
        }
    }

    free(buf);
    context_free(&c);

#else

    uri_worker_map_add(we->uri_to_worker, "/examples/servlet/*",
                       ae->worker->name, SOURCE_TYPE_DISCOVER, l);
    uri_worker_map_add(we->uri_to_worker, "/examples/*.jsp", ae->worker->name,
                       SOURCE_TYPE_DISCOVER, l);
    uri_worker_map_add(we->uri_to_worker, "/examples/*.gif", ae->worker->name,
                       SOURCE_TYPE_DISCOVER, l);

#endif

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * AJP14 Logon Phase
 *
 * INIT + REPLY / NEGO + REPLY
 */

static int handle_logon(ajp_endpoint_t * ae,
                        jk_msg_buf_t *msg, jk_logger_t *l)
{
    int cmd;

    jk_login_service_t *jl = ae->worker->login;
    JK_TRACE_ENTER(l);

    ajp14_marshal_login_init_into_msgb(msg, jl, l);

    jk_log(l, JK_LOG_DEBUG, "send init");

    if (ajp_connection_tcp_send_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG, "wait init reply");

    jk_b_reset(msg);

    if (ajp_connection_tcp_get_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    if ((cmd = jk_b_get_byte(msg)) != AJP14_LOGSEED_CMD) {
        jk_log(l, JK_LOG_ERROR,
               "awaited command %d, received %d",
               AJP14_LOGSEED_CMD, cmd);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (ajp14_unmarshal_login_seed(msg, jl, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_log(l, JK_LOG_DEBUG, "received entropy %s",
           jl->entropy);

    ajp14_compute_md5(jl, l);

    if (ajp14_marshal_login_comp_into_msgb(msg, jl, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    if (ajp_connection_tcp_send_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    jk_b_reset(msg);

    if (ajp_connection_tcp_get_message(ae, msg, l) != JK_TRUE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    switch (jk_b_get_byte(msg)) {

    case AJP14_LOGOK_CMD:
        if (ajp14_unmarshal_log_ok(msg, jl, l) == JK_TRUE) {
            jk_log(l, JK_LOG_DEBUG,
                   "Successfully connected to servlet-engine %s",
                   jl->servlet_engine_name);
            JK_TRACE_EXIT(l);
            return JK_TRUE;
        }
        break;

    case AJP14_LOGNOK_CMD:
        ajp14_unmarshal_log_nok(msg, l);
        break;
    }

    JK_TRACE_EXIT(l);
    return JK_FALSE;
}

static int logon(ajp_endpoint_t * ae, jk_logger_t *l)
{
    jk_pool_t *p = &ae->pool;
    jk_msg_buf_t *msg;
    int rc;

    JK_TRACE_ENTER(l);

    msg = jk_b_new(p);
    jk_b_set_buffer_size(msg, AJP13_DEF_PACKET_SIZE);

    if ((rc = handle_logon(ae, msg, l)) == JK_FALSE)
        ajp_close_endpoint(ae, l);

    JK_TRACE_EXIT(l);
    return rc;
}

static int discovery(ajp_endpoint_t * ae, jk_worker_env_t *we, jk_logger_t *l)
{
    jk_pool_t *p = &ae->pool;
    jk_msg_buf_t *msg;
    int rc;

    JK_TRACE_ENTER(l);

    msg = jk_b_new(p);
    jk_b_set_buffer_size(msg, AJP13_DEF_PACKET_SIZE);

    if ((rc = handle_discovery(ae, we, msg, l)) == JK_FALSE)
        ajp_close_endpoint(ae, l);

    JK_TRACE_EXIT(l);
    return rc;
}

/* -------------------- Method -------------------- */
static int JK_METHOD validate(jk_worker_t *pThis,
                              jk_map_t *props,
                              jk_worker_env_t *we, jk_logger_t *l)
{
    ajp_worker_t *aw;
    const char *secret_key;

    JK_TRACE_ENTER(l);
    if (ajp_validate(pThis, props, we, l, AJP14_PROTO) == JK_FALSE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    aw = pThis->worker_private;

    secret_key = jk_get_worker_secret_key(props, aw->name);

    if ((!secret_key) || (!strlen(secret_key))) {
        jk_log(l, JK_LOG_ERROR,
               "validate error, empty or missing secretkey");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    /* jk_log(l, JK_LOG_DEBUG, "Into ajp14:validate - secret_key=%s", secret_key); */
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

static int JK_METHOD get_endpoint(jk_worker_t *pThis,
                                  jk_endpoint_t **pend, jk_logger_t *l)
{
    int rc;
    JK_TRACE_ENTER(l);
    rc = ajp_get_endpoint(pThis, pend, l, AJP14_PROTO);
    JK_TRACE_EXIT(l);
    return rc;
}

static int JK_METHOD init(jk_worker_t *pThis,
                          jk_map_t *props,
                          jk_worker_env_t *we, jk_logger_t *l)
{
    ajp_worker_t *aw;
    ajp_endpoint_t *ae;
    jk_endpoint_t *je;
    int rc;

    JK_TRACE_EXIT(l);

    if (ajp_init(pThis, props, we, l, AJP14_PROTO) == JK_FALSE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    aw = pThis->worker_private;

    /* Set Secret Key (used at logon time) */
    aw->login->secret_key = jk_get_worker_secret_key(props, aw->name);

    if (aw->login->secret_key == NULL) {
        jk_log(l, JK_LOG_ERROR, "can't malloc secret_key");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    /* Set WebServerName (used at logon time) */
    aw->login->web_server_name = strdup(we->server_name);

    if (aw->login->web_server_name == NULL) {
        jk_log(l, JK_LOG_ERROR, "can't malloc web_server_name");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (get_endpoint(pThis, &je, l) == JK_FALSE) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    ae = je->endpoint_private;

    if (ajp_connect_to_endpoint(ae, l) == JK_TRUE) {

        /* connection stage passed - try to get context info
         * this is the long awaited autoconf feature :)
         */
        rc = discovery(ae, we, l);
        ajp_close_endpoint(ae, l);
        JK_TRACE_EXIT(l);
        return rc;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


static int JK_METHOD destroy(jk_worker_t **pThis, jk_logger_t *l)
{
    int rc;
    ajp_worker_t *aw = (*pThis)->worker_private;

    JK_TRACE_ENTER(l);

    if (aw->login) {
        free(aw->login);
        aw->login = NULL;
    }

    rc = ajp_destroy(pThis, l, AJP14_PROTO);
    JK_TRACE_EXIT(l);
    return rc;
}

int JK_METHOD ajp14_worker_factory(jk_worker_t **w,
                                   const char *name, jk_logger_t *l)
{
    ajp_worker_t *aw;

    JK_TRACE_ENTER(l);
    if (ajp_worker_factory(w, name, l) == JK_FALSE)
        return 0;

    aw = (*w)->worker_private;
    aw->proto = AJP14_PROTO;

    aw->login = (jk_login_service_t *)malloc(sizeof(jk_login_service_t));

    if (aw->login == NULL) {
        jk_log(l, JK_LOG_ERROR,
               "malloc failed for login area");
        JK_TRACE_EXIT(l);
        return 0;
    }

    memset(aw->login, 0, sizeof(jk_login_service_t));

    aw->login->negotiation =
        (AJP14_CONTEXT_INFO_NEG | AJP14_PROTO_SUPPORT_AJP14_NEG);
    aw->login->web_server_name = NULL;  /* must be set in init */

    aw->worker.validate = validate;
    aw->worker.init = init;
    aw->worker.get_endpoint = get_endpoint;
    aw->worker.destroy = destroy;

    aw->logon = logon;          /* LogOn Handler for AJP14 */

    JK_TRACE_EXIT(l);
    return JK_AJP14_WORKER_TYPE;
}
