/*
 *  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: Next generation bi-directional protocol handler.           *
 * Author:      Henri Gomez <hgomez@apache.org>                            *
 ***************************************************************************/

#include "jk_global.h"
#include "jk_util.h"
#include "jk_map.h"
#include "jk_ajp_common.h"
#include "jk_ajp14.h"
#include "jk_md5.h"

/*
 * Compute the MD5 with ENTROPY / SECRET KEY
 */

void ajp14_compute_md5(jk_login_service_t *s, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);
    jk_md5((const unsigned char *)s->entropy,
           (const unsigned char *)s->secret_key, s->computed_key);

    if (JK_IS_DEBUG_LEVEL(l))
        jk_log(l, JK_LOG_DEBUG, "(%s/%s) -> (%s)",
               s->entropy, s->secret_key, s->computed_key);
    JK_TRACE_EXIT(l);
}


/*
 * Build the Login Init Command
 *
 * +-------------------------+---------------------------+---------------------------+
 * | LOGIN INIT CMD (1 byte) | NEGOTIATION DATA (32bits) | WEB SERVER INFO (CString) |
 * +-------------------------+---------------------------+---------------------------+
 *
 */

int ajp14_marshal_login_init_into_msgb(jk_msg_buf_t *msg,
                                       jk_login_service_t *s, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);
    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * LOGIN
     */
    if (jk_b_append_byte(msg, AJP14_LOGINIT_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * NEGOTIATION FLAGS
     */
    if (jk_b_append_long(msg, s->negotiation)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * WEB-SERVER NAME
     */
    if (jk_b_append_string(msg, s->web_server_name)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the web_server_name string");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Decode the Login Seed Command
 *
 * +-------------------------+---------------------------+
 * | LOGIN SEED CMD (1 byte) | MD5 of entropy (32 chars) |
 * +-------------------------+---------------------------+
 *
 */

int ajp14_unmarshal_login_seed(jk_msg_buf_t *msg,
                               jk_login_service_t *s, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);

    if (jk_b_get_bytes
        (msg, (unsigned char *)s->entropy, AJP14_ENTROPY_SEED_LEN) < 0) {
        jk_log(l, JK_LOG_ERROR,
               "can't get seed");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    s->entropy[AJP14_ENTROPY_SEED_LEN] = 0;     /* Just to have a CString */
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * Build the Login Computed Command
 *
 * +-------------------------+---------------------------------------+
 * | LOGIN COMP CMD (1 byte) | MD5 of RANDOM + SECRET KEY (32 chars) |
 * +-------------------------+---------------------------------------+
 *
 */

int ajp14_marshal_login_comp_into_msgb(jk_msg_buf_t *msg,
                                       jk_login_service_t *s, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);

    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * LOGIN
     */
    if (jk_b_append_byte(msg, AJP14_LOGCOMP_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * COMPUTED-SEED
     */
    if (jk_b_append_bytes
        (msg, (const unsigned char *)s->computed_key,
         AJP14_COMPUTED_KEY_LEN)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the COMPUTED MD5 bytes");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Decode the LogOk Command
 *
 * +--------------------+------------------------+-------------------------------+
 * | LOGOK CMD (1 byte) | NEGOCIED DATA (32bits) | SERVLET ENGINE INFO (CString) |
 * +--------------------+------------------------+-------------------------------+
 *
 */

int ajp14_unmarshal_log_ok(jk_msg_buf_t *msg,
                           jk_login_service_t *s, jk_logger_t *l)
{
    unsigned long nego;
    char *sname;

    JK_TRACE_ENTER(l);

    nego = jk_b_get_long(msg);

    if (nego == 0xFFFFFFFF) {
        jk_log(l, JK_LOG_ERROR,
               "can't get negociated data");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    sname = jk_b_get_string(msg);

    if (!sname) {
        jk_log(l, JK_LOG_ERROR,
               "can't get servlet engine name");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (s->servlet_engine_name) /* take care of removing previously allocated data */
        free(s->servlet_engine_name);

    s->servlet_engine_name = strdup(sname);

    if (!s->servlet_engine_name) {
        jk_log(l, JK_LOG_ERROR,
               "can't malloc servlet engine name");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Decode the Log Nok Command
 *
 * +---------------------+-----------------------+
 * | LOGNOK CMD (1 byte) | FAILURE CODE (32bits) |
 * +---------------------+-----------------------+
 *
 */

int ajp14_unmarshal_log_nok(jk_msg_buf_t *msg, jk_logger_t *l)
{
    unsigned long status;

    JK_TRACE_ENTER(l);

    status = jk_b_get_long(msg);

    if (status == 0xFFFFFFFF) {
        jk_log(l, JK_LOG_ERROR,
               "can't get failure code");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    jk_log(l, JK_LOG_INFO, "Can't Log with servlet engine - code %08lx",
           status);
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Build the Shutdown Cmd
 *
 * +-----------------------+---------------------------------------+
 * | SHUTDOWN CMD (1 byte) | MD5 of RANDOM + SECRET KEY (32 chars) |
 * +-----------------------+---------------------------------------+
 *
 */

int ajp14_marshal_shutdown_into_msgb(jk_msg_buf_t *msg,
                                     jk_login_service_t *s, jk_logger_t *l)
{

    JK_TRACE_ENTER(l);

    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * SHUTDOWN CMD
     */
    if (jk_b_append_byte(msg, AJP14_SHUTDOWN_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * COMPUTED-SEED
     */
    if (jk_b_append_bytes
        (msg, (const unsigned char *)s->computed_key,
         AJP14_COMPUTED_KEY_LEN)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the COMPUTED MD5 bytes");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * Decode the Shutdown Nok Command
 *
 * +----------------------+-----------------------+
 * | SHUTNOK CMD (1 byte) | FAILURE CODE (32bits) |
 * +----------------------+-----------------------+
 *
 */
int ajp14_unmarshal_shutdown_nok(jk_msg_buf_t *msg, jk_logger_t *l)
{
    unsigned long status;

    JK_TRACE_ENTER(l);
    status = jk_b_get_long(msg);

    if (status == 0xFFFFFFFF) {
        jk_log(l, JK_LOG_ERROR,
               "can't get failure code");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    jk_log(l, JK_LOG_INFO, "Can't shutdown servlet engine - code %08lx",
           status);
    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * Build the Unknown Packet
 *
 * +-----------------------------+---------------------------------+------------------------------+
 * | UNKNOWN PACKET CMD (1 byte) | UNHANDLED MESSAGE SIZE (16bits) | UNHANDLED MESSAGE (bytes...) |
 * +-----------------------------+---------------------------------+------------------------------+
 *
 */

int ajp14_marshal_unknown_packet_into_msgb(jk_msg_buf_t *msg,
                                           jk_msg_buf_t *unk, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);

    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * UNKNOWN PACKET CMD
     */
    if (jk_b_append_byte(msg, AJP14_UNKNOW_PACKET_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * UNHANDLED MESSAGE SIZE
     */
    if (jk_b_append_int(msg, (unsigned short)unk->len)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * UNHANDLED MESSAGE (Question : Did we have to send all the message or only part of)
     *                                       (           ie: only 1k max                                                                )
     */
    if (jk_b_append_bytes(msg, unk->buf, unk->len)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the UNHANDLED MESSAGE");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * Build the Context Query Cmd (autoconf)
 *
 * +--------------------------+---------------------------------+
 * | CONTEXT QRY CMD (1 byte) | VIRTUAL HOST NAME (CString (*)) |
 * +--------------------------+---------------------------------+
 *
 */

int ajp14_marshal_context_query_into_msgb(jk_msg_buf_t *msg,
                                          char *virtual, jk_logger_t *l)
{
    JK_TRACE_ENTER(l);

    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * CONTEXT QUERY CMD
     */
    if (jk_b_append_byte(msg, AJP14_CONTEXT_QRY_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * VIRTUAL HOST CSTRING
     */
    if (jk_b_append_string(msg, virtual)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the virtual host string");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Decode the Context Info Cmd (Autoconf)
 *
 * The Autoconf feature of AJP14, let us know which URL/URI could
 * be handled by the servlet-engine
 *
 * +---------------------------+---------------------------------+----------------------------+-------------------------------+-----------+
 * | CONTEXT INFO CMD (1 byte) | VIRTUAL HOST NAME (CString (*)) | CONTEXT NAME (CString (*)) | URL1 [\n] URL2 [\n] URL3 [\n] | NEXT CTX. |
 * +---------------------------+---------------------------------+----------------------------+-------------------------------+-----------+
 */

int ajp14_unmarshal_context_info(jk_msg_buf_t *msg,
                                 jk_context_t *c, jk_logger_t *l)
{
    char *vname;
    char *cname;
    char *uri;

    vname = jk_b_get_string(msg);

    JK_TRACE_ENTER(l);
    jk_log(l, JK_LOG_DEBUG,
           "get virtual %s for virtual %s",
           vname, c->virt);

    if (!vname) {
        jk_log(l, JK_LOG_ERROR,
               "can't get virtual hostname");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    /* Check if we get the correct virtual host */
    if (c->virt != NULL && vname != NULL && strcmp(c->virt, vname)) {
        /* set the virtual name, better to add to a virtual list ? */

        if (context_set_virtual(c, vname) == JK_FALSE) {
            jk_log(l, JK_LOG_ERROR,
                   "can't malloc virtual hostname");
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
    }

    for (;;) {

        cname = jk_b_get_string(msg);

        if (!cname) {
            jk_log(l, JK_LOG_ERROR,
                   "can't get context");
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        jk_log(l, JK_LOG_DEBUG,
               "get context %s for virtual %s",
               cname, vname);

        /* grab all contexts up to empty one which indicate end of contexts */
        if (!strlen(cname))
            break;

        /* create new context base (if needed) */

        if (context_add_base(c, cname) == JK_FALSE) {
            jk_log(l, JK_LOG_ERROR,
                   "can't add/set context %s",
                   cname);
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        for (;;) {

            uri = jk_b_get_string(msg);

            if (!uri) {
                jk_log(l, JK_LOG_ERROR,
                       "can't get URI");
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }

            if (!strlen(uri)) {
                jk_log(l, JK_LOG_DEBUG, "No more URI for context %s", cname);
                break;
            }

            jk_log(l, JK_LOG_INFO,
                   "Got URI (%s) for virtualhost %s and context %s", uri,
                   vname, cname);

            if (context_add_uri(c, cname, uri) == JK_FALSE) {
                jk_log(l, JK_LOG_ERROR,
                       "can't add/set uri (%s) for context %s",
                       uri, cname);
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }
        }
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Build the Context State Query Cmd
 *
 * We send the list of contexts where we want to know state, empty string end context list*
 * If cname is set, only ask about THIS context
 *
 * +----------------------------+----------------------------------+----------------------------+----+
 * | CONTEXT STATE CMD (1 byte) |  VIRTUAL HOST NAME (CString (*)) | CONTEXT NAME (CString (*)) | .. |
 * +----------------------------+----------------------------------+----------------------------+----+
 *
 */

int ajp14_marshal_context_state_into_msgb(jk_msg_buf_t *msg,
                                          jk_context_t *c,
                                          char *cname, jk_logger_t *l)
{
    jk_context_item_t *ci;
    int i;

    JK_TRACE_ENTER(l);

    /* To be on the safe side */
    jk_b_reset(msg);

    /*
     * CONTEXT STATE CMD
     */
    if (jk_b_append_byte(msg, AJP14_CONTEXT_STATE_CMD)) {
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }
    /*
     * VIRTUAL HOST CSTRING
     */
    if (jk_b_append_string(msg, c->virt)) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending the virtual host string");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    if (cname) {

        ci = context_find_base(c, cname);

        if (!ci) {
            jk_log(l, JK_LOG_ERROR,
                   "unknown context %s",
                   cname);
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        /*
         * CONTEXT CSTRING
         */

        if (jk_b_append_string(msg, cname)) {
            jk_log(l, JK_LOG_ERROR,
                   "failed appending the context string %s",
                   cname);
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }
    }
    else {                      /* Grab all contexts name */

        for (i = 0; i < c->size; i++) {

            /*
             * CONTEXT CSTRING
             */
            if (jk_b_append_string(msg, c->contexts[i]->cbase)) {
                jk_log(l, JK_LOG_ERROR,
                       "failed appending the context string %s",
                       c->contexts[i]->cbase);
                JK_TRACE_EXIT(l);
                return JK_FALSE;
            }
        }
    }

    /* End of context list, an empty string */

    if (jk_b_append_string(msg, "")) {
        jk_log(l, JK_LOG_ERROR,
               "failed appending end of contexts");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}


/*
 * Decode the Context State Reply Cmd
 *
 * We get update of contexts list, empty string end context list*
 *
 * +----------------------------------+---------------------------------+----------------------------+------------------+----+
 * | CONTEXT STATE REPLY CMD (1 byte) | VIRTUAL HOST NAME (CString (*)) | CONTEXT NAME (CString (*)) | UP/DOWN (1 byte) | .. |
 * +----------------------------------+---------------------------------+----------------------------+------------------+----+
 *
 */

int ajp14_unmarshal_context_state_reply(jk_msg_buf_t *msg,
                                        jk_context_t *c, jk_logger_t *l)
{
    char *vname;
    char *cname;
    jk_context_item_t *ci;

    JK_TRACE_ENTER(l);
    /* get virtual name */
    vname = jk_b_get_string(msg);

    if (!vname) {
        jk_log(l, JK_LOG_ERROR,
               "can't get virtual hostname");
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    /* Check if we speak about the correct virtual */
    if (strcmp(c->virt, vname)) {
        jk_log(l, JK_LOG_ERROR,
               "incorrect virtual %s instead of %s",
               vname, c->virt);
        JK_TRACE_EXIT(l);
        return JK_FALSE;
    }

    for (;;) {

        /* get context name */
        cname = jk_b_get_string(msg);

        if (!cname) {
            jk_log(l, JK_LOG_ERROR,
                   "can't get context");
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        if (!strlen(cname))
            break;

        ci = context_find_base(c, cname);

        if (!ci) {
            jk_log(l, JK_LOG_ERROR,
                   "unknow context %s for virtual %s",
                   cname, vname);
            JK_TRACE_EXIT(l);
            return JK_FALSE;
        }

        ci->status = jk_b_get_int(msg);

        if (JK_IS_DEBUG_LEVEL(l))
            jk_log(l, JK_LOG_DEBUG,
                   "updated context %s to state %d",
                   cname, ci->status);
    }

    JK_TRACE_EXIT(l);
    return JK_TRUE;
}

/*
 * Decode the Context Update Cmd
 *
 * +-----------------------------+---------------------------------+----------------------------+------------------+
 * | CONTEXT UPDATE CMD (1 byte) | VIRTUAL HOST NAME (CString (*)) | CONTEXT NAME (CString (*)) | UP/DOWN (1 byte) |
 * +-----------------------------+---------------------------------+----------------------------+------------------+
 *
 */

int ajp14_unmarshal_context_update_cmd(jk_msg_buf_t *msg,
                                       jk_context_t *c, jk_logger_t *l)
{
    int rc;
    JK_TRACE_ENTER(l);
    rc = ajp14_unmarshal_context_state_reply(msg, c, l);
    JK_TRACE_EXIT(l);
    return rc;
}
