/*
 * Drizzle Client & Protocol Library
 *
 * Copyright (C) 2008 Eric Day (eday@oddments.org)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *
 *     * The names of its contributors may not be used to endorse or
 * promote products derived from this software without specific prior
 * written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

/**
 * @file
 * @brief Handshake Definitions
 */

#include "common.h"

/*
 * Client Definitions
 */

drizzle_return_t drizzle_handshake_server_read(drizzle_con_st *con)
{
  if (drizzle_state_none(con))
  {
    drizzle_state_push(con, drizzle_state_handshake_server_read);
    drizzle_state_push(con, drizzle_state_packet_read);
  }

  return drizzle_state_loop(con);
}

drizzle_return_t drizzle_handshake_client_write(drizzle_con_st *con)
{
  if (drizzle_state_none(con))
  {
    drizzle_state_push(con, drizzle_state_write);
    drizzle_state_push(con, drizzle_state_handshake_client_write);
  }

  return drizzle_state_loop(con);
}

/*
 * Server Definitions
 */

drizzle_return_t drizzle_handshake_server_write(drizzle_con_st *con)
{
  if (drizzle_state_none(con))
  {
    drizzle_state_push(con, drizzle_state_write);
    drizzle_state_push(con, drizzle_state_handshake_server_write);
  }

  return drizzle_state_loop(con);
}

drizzle_return_t drizzle_handshake_client_read(drizzle_con_st *con)
{
  if (drizzle_state_none(con))
  {
    drizzle_state_push(con, drizzle_state_handshake_client_read);
    drizzle_state_push(con, drizzle_state_packet_read);
  }

  return drizzle_state_loop(con);
}

/*
 * State Definitions
 */

drizzle_return_t drizzle_state_handshake_server_read(drizzle_con_st *con)
{
  uint8_t *ptr;
  int extra_length;
  unsigned char* packet_end;

  drizzle_log_debug(con->drizzle, "drizzle_state_handshake_server_read");

  /* Assume the entire handshake packet will fit in the buffer. */
  if (con->buffer_size < con->packet_size)
  {
    drizzle_state_push(con, drizzle_state_read);
    return DRIZZLE_RETURN_OK;
  }

  if (con->packet_size < 46)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "bad packet size:>=46:%zu", con->packet_size);
    return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
  }

  packet_end= con->buffer_ptr + con->packet_size;
  con->protocol_version= con->buffer_ptr[0];
  con->buffer_ptr++;

  if (con->protocol_version != 10)
  {
    /* This is a special case where the server determines that authentication
       will be impossible and denies any attempt right away. */
    if (con->protocol_version == 255)
    {
      drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                        "%.*s", (int32_t)con->packet_size - 3,
                        con->buffer_ptr + 2);
      return DRIZZLE_RETURN_AUTH_FAILED;
    }

    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "protocol version not supported:%d",
                      con->protocol_version);
    return DRIZZLE_RETURN_PROTOCOL_NOT_SUPPORTED;
  }

  /* Look for null-terminated server version string. */
  ptr= memchr(con->buffer_ptr, 0, con->buffer_size - 1);
  if (ptr == NULL)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "server version string not found");
    return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
  }

  if (con->packet_size < (46 + (size_t)(ptr - con->buffer_ptr)))
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "bad packet size:%zu:%zu",
                      (46 + (size_t)(ptr - con->buffer_ptr)), con->packet_size);
    return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
  }

  strncpy(con->server_version, (char *)con->buffer_ptr,
          DRIZZLE_MAX_SERVER_VERSION_SIZE);
  con->server_version[DRIZZLE_MAX_SERVER_VERSION_SIZE - 1]= 0;
  con->buffer_ptr+= ((ptr - con->buffer_ptr) + 1);

  con->thread_id= (uint32_t)drizzle_get_byte4(con->buffer_ptr);
  con->buffer_ptr+= 4;

  con->scramble= con->scramble_buffer;
  memcpy(con->scramble, con->buffer_ptr, 8);
  /* Skip scramble and filler. */
  con->buffer_ptr+= 9;

  /* Even though drizzle_capabilities is more than 2 bytes, the protocol only
     allows for 2. This means some capabilities are not possible during this
     handshake step. The options beyond 2 bytes are for client response only. */
  con->capabilities= (drizzle_capabilities_t)drizzle_get_byte2(con->buffer_ptr);
  con->buffer_ptr+= 2;

  if (con->options & DRIZZLE_CON_MYSQL &&
      !(con->capabilities & DRIZZLE_CAPABILITIES_PROTOCOL_41))
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "protocol version not supported, must be MySQL 4.1+");
    return DRIZZLE_RETURN_PROTOCOL_NOT_SUPPORTED;
  }

  con->charset= con->buffer_ptr[0];
  con->buffer_ptr+= 1;

  con->status= drizzle_get_byte2(con->buffer_ptr);
  /* Skip status and filler. */
  con->buffer_ptr+= 15;

  memcpy(con->scramble + 8, con->buffer_ptr, 12);
  con->buffer_ptr+= 13;

  /* MySQL 5.5 adds "mysql_native_password" after the server greeting. */
  extra_length= packet_end - con->buffer_ptr;
  assert(extra_length >= 0);
  if (extra_length > DRIZZLE_MAX_SERVER_EXTRA_SIZE - 1)
    extra_length= DRIZZLE_MAX_SERVER_EXTRA_SIZE - 1;
  memcpy(con->server_extra, (char *)con->buffer_ptr, extra_length);
  con->server_extra[extra_length]= 0;

  con->buffer_size-= con->packet_size;
  if (con->buffer_size != 0)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_read",
                      "unexpected data after packet:%zu", con->buffer_size);
    return DRIZZLE_RETURN_UNEXPECTED_DATA;
  }

  con->buffer_ptr= con->buffer;

  drizzle_state_pop(con);

  if (!(con->options & DRIZZLE_CON_RAW_PACKET))
  {
    drizzle_state_push(con, drizzle_state_handshake_result_read);
    drizzle_state_push(con, drizzle_state_packet_read);
    drizzle_state_push(con, drizzle_state_write);
    drizzle_state_push(con, drizzle_state_handshake_client_write);
  }

  return DRIZZLE_RETURN_OK;
}

drizzle_return_t drizzle_state_handshake_server_write(drizzle_con_st *con)
{
  uint8_t *ptr;

  drizzle_log_debug(con->drizzle, "drizzle_state_handshake_server_write");

  /* Calculate max packet size. */
  con->packet_size= 1   /* Protocol version */
                  + strlen(con->server_version) + 1
                  + 4   /* Thread ID */
                  + 8   /* Scramble */
                  + 1   /* NULL */
                  + 2   /* Capabilities */
                  + 1   /* Language */
                  + 2   /* Status */
                  + 13  /* Unused */
                  + 12  /* Scramble */
                  + 1;  /* NULL */

  /* Assume the entire handshake packet will fit in the buffer. */
  if ((con->packet_size + 4) > DRIZZLE_MAX_BUFFER_SIZE)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_write",
                      "buffer too small:%zu", con->packet_size + 4);
    return DRIZZLE_RETURN_INTERNAL_ERROR;
  }

  ptr= con->buffer_ptr;

  /* Store packet size and packet number first. */
  drizzle_set_byte3(ptr, con->packet_size);
  ptr[3]= 0;
  con->packet_number= 1;
  ptr+= 4;

  ptr[0]= con->protocol_version;
  ptr++;

  memcpy(ptr, con->server_version, strlen(con->server_version));
  ptr+= strlen(con->server_version);

  ptr[0]= 0;
  ptr++;

  drizzle_set_byte4(ptr, con->thread_id);
  ptr+= 4;

  if (con->scramble == NULL)
    memset(ptr, 0, 8);
  else
    memcpy(ptr, con->scramble, 8);
  ptr+= 8;

  ptr[0]= 0;
  ptr++;

  if (con->options & DRIZZLE_CON_MYSQL)
    con->capabilities|= DRIZZLE_CAPABILITIES_PROTOCOL_41;

  /* We can only send two bytes worth, this is a protocol limitation. */
  drizzle_set_byte2(ptr, con->capabilities);
  ptr+= 2;

  ptr[0]= con->charset;
  ptr++;

  drizzle_set_byte2(ptr, con->status);
  ptr+= 2;

  memset(ptr, 0, 13);
  ptr+= 13;

  if (con->scramble == NULL)
    memset(ptr, 0, 12);
  else
    memcpy(ptr, con->scramble + 8, 12);
  ptr+= 12;

  ptr[0]= 0;
  ptr++;

  con->buffer_size+= (4 + con->packet_size);

  /* Make sure we packed it correctly. */
  if ((size_t)(ptr - con->buffer_ptr) != (4 + con->packet_size))
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_server_write",
                      "error packing server handshake:%zu:%zu",
                      (size_t)(ptr - con->buffer_ptr), 4 + con->packet_size);
    return DRIZZLE_RETURN_INTERNAL_ERROR;
  }

  drizzle_state_pop(con);
  return DRIZZLE_RETURN_OK;
}

drizzle_return_t drizzle_state_handshake_client_read(drizzle_con_st *con)
{
  size_t real_size;
  uint8_t *ptr;
  uint8_t scramble_size;

  drizzle_log_debug(con->drizzle, "drizzle_state_handshake_client_read");

  /* Assume the entire handshake packet will fit in the buffer. */
  if (con->buffer_size < con->packet_size)
  {
    drizzle_state_push(con, drizzle_state_read);
    return DRIZZLE_RETURN_OK;
  }

  /* This is the minimum packet size. */
  if (con->packet_size < 34)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                      "bad packet size:>=34:%zu", con->packet_size);
    return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
  }

  real_size= 34;

  con->capabilities= drizzle_get_byte4(con->buffer_ptr);
  con->buffer_ptr+= 4;

  if (con->options & DRIZZLE_CON_MYSQL &&
      !(con->capabilities & DRIZZLE_CAPABILITIES_PROTOCOL_41))
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                      "protocol version not supported, must be MySQL 4.1+");
    return DRIZZLE_RETURN_PROTOCOL_NOT_SUPPORTED;
  }

  con->max_packet_size= (uint32_t)drizzle_get_byte4(con->buffer_ptr);
  con->buffer_ptr+= 4;

  con->charset= con->buffer_ptr[0];
  con->buffer_ptr+= 1;

  /* Skip unused. */
  con->buffer_ptr+= 23;

  /* Look for null-terminated user string. */
  ptr= memchr(con->buffer_ptr, 0, con->buffer_size - 32);
  if (ptr == NULL)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                      "user string not found");
    return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
  }

  if (con->buffer_ptr == ptr)
  {
    con->user[0]= 0;
    con->buffer_ptr++;
  }
  else
  {
    real_size+= (size_t)(ptr - con->buffer_ptr);
    if (con->packet_size < real_size)
    {
      drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                        "bad packet size:>=%zu:%zu", real_size,
                        con->packet_size);
      return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
    }

    strncpy(con->user, (char *)con->buffer_ptr, DRIZZLE_MAX_USER_SIZE);
    con->user[DRIZZLE_MAX_USER_SIZE - 1]= 0;
    con->buffer_ptr+= ((ptr - con->buffer_ptr) + 1);
  }

  scramble_size= con->buffer_ptr[0];
  con->buffer_ptr+= 1;

  if (scramble_size == 0)
    con->scramble= NULL;
  else
  {
    if (scramble_size != DRIZZLE_MAX_SCRAMBLE_SIZE)
    {
      drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                        "wrong scramble size");
      return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
    }

    real_size+= scramble_size;
    con->scramble= con->scramble_buffer;
    memcpy(con->scramble, con->buffer_ptr, DRIZZLE_MAX_SCRAMBLE_SIZE);

    con->buffer_ptr+= DRIZZLE_MAX_SCRAMBLE_SIZE;
  }

  /* Look for null-terminated db string. */
  if ((34 + strlen(con->user) + scramble_size) == con->packet_size)
    con->db[0]= 0;
  else
  {
    ptr= memchr(con->buffer_ptr, 0, con->buffer_size -
                                    (34 + strlen(con->user) + scramble_size));
    if (ptr == NULL)
    {
      drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                        "db string not found");
      return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
    }

    real_size+= ((size_t)(ptr - con->buffer_ptr) + 1);
    if (con->packet_size != real_size)
    {
      drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                        "bad packet size:%zu:%zu", real_size, con->packet_size);
      return DRIZZLE_RETURN_BAD_HANDSHAKE_PACKET;
    }

    if (con->buffer_ptr == ptr)
    {
      con->db[0]= 0;
      con->buffer_ptr++;
    }
    else
    {
      strncpy(con->db, (char *)con->buffer_ptr, DRIZZLE_MAX_DB_SIZE);
      con->db[DRIZZLE_MAX_DB_SIZE - 1]= 0;
      con->buffer_ptr+= ((ptr - con->buffer_ptr) + 1);
    }
  }

  con->buffer_size-= con->packet_size;
  if (con->buffer_size != 0)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_read",
                      "unexpected data after packet:%zu", con->buffer_size);
    return DRIZZLE_RETURN_UNEXPECTED_DATA;
  }

  con->buffer_ptr= con->buffer;

  drizzle_state_pop(con);
  return DRIZZLE_RETURN_OK;
}

drizzle_return_t drizzle_state_handshake_client_write(drizzle_con_st *con)
{
  uint8_t *ptr;
  drizzle_capabilities_t capabilities;
  drizzle_return_t ret;

  drizzle_log_debug(con->drizzle, "drizzle_state_handshake_client_write");

  /* Calculate max packet size. */
  con->packet_size= 4   /* Capabilities */
                  + 4   /* Max packet size */
                  + 1   /* Charset */
                  + 23  /* Unused */
                  + strlen(con->user) + 1
                  + 1   /* Scramble size */
                  + DRIZZLE_MAX_SCRAMBLE_SIZE
                  + strlen(con->db) + 1;

  /* Assume the entire handshake packet will fit in the buffer. */
  if ((con->packet_size + 4) > DRIZZLE_MAX_BUFFER_SIZE)
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_write",
                      "buffer too small:%zu", con->packet_size + 4);
    return DRIZZLE_RETURN_INTERNAL_ERROR;
  }

  ptr= con->buffer_ptr;

  /* Store packet size at the end since it may change. */
  ptr[3]= con->packet_number;
  con->packet_number++;
  ptr+= 4;

  if (con->options & DRIZZLE_CON_MYSQL)
    con->capabilities|= DRIZZLE_CAPABILITIES_PROTOCOL_41;

  capabilities= con->capabilities & DRIZZLE_CAPABILITIES_CLIENT;
  if (!(con->options & DRIZZLE_CON_FOUND_ROWS))
    capabilities&= ~DRIZZLE_CAPABILITIES_FOUND_ROWS;

  if (con->options & DRIZZLE_CON_ADMIN)
    capabilities|= DRIZZLE_CAPABILITIES_ADMIN;

  if (con->options & DRIZZLE_CON_INTERACTIVE)
  {
    capabilities|= DRIZZLE_CAPABILITIES_INTERACTIVE;
  }

  capabilities&= ~(DRIZZLE_CAPABILITIES_COMPRESS | DRIZZLE_CAPABILITIES_SSL);
  if (con->db[0] == 0)
    capabilities&= ~DRIZZLE_CAPABILITIES_CONNECT_WITH_DB;

  drizzle_set_byte4(ptr, capabilities);
  ptr+= 4;

  drizzle_set_byte4(ptr, con->max_packet_size);
  ptr+= 4;

  ptr[0]= con->charset;
  ptr++;

  memset(ptr, 0, 23);
  ptr+= 23;

  ptr= drizzle_pack_auth(con, ptr, &ret);
  if (ret != DRIZZLE_RETURN_OK)
    return ret;

  con->buffer_size+= (4 + con->packet_size);

  /* Make sure we packed it correctly. */
  if ((size_t)(ptr - con->buffer_ptr) != (4 + con->packet_size))
  {
    drizzle_set_error(con->drizzle, "drizzle_state_handshake_client_write",
                      "error packing client handshake:%zu:%zu",
                      (size_t)(ptr - con->buffer_ptr), 4 + con->packet_size);
    return DRIZZLE_RETURN_INTERNAL_ERROR;
  }

  /* Store packet size now. */
  drizzle_set_byte3(con->buffer_ptr, con->packet_size);

  drizzle_state_pop(con);
  return DRIZZLE_RETURN_OK;
}

drizzle_return_t drizzle_state_handshake_result_read(drizzle_con_st *con)
{
  drizzle_return_t ret;
  drizzle_result_st result;

  drizzle_log_debug(con->drizzle, "drizzle_state_handshake_result_read");

  if (drizzle_result_create(con, &result) == NULL)
    return DRIZZLE_RETURN_MEMORY;

  con->result= &result;

  ret= drizzle_state_result_read(con);
  if (drizzle_state_none(con))
  {
    if (ret == DRIZZLE_RETURN_OK)
    {
      if (drizzle_result_eof(&result))
      {
        drizzle_set_error(con->drizzle, "drizzle_state_handshake_result_read",
                         "old insecure authentication mechanism not supported");
        ret= DRIZZLE_RETURN_AUTH_FAILED;
      }
      else
        con->options|= DRIZZLE_CON_READY;
    }
  }

  drizzle_result_free(&result);

  if (ret == DRIZZLE_RETURN_ERROR_CODE)
    return DRIZZLE_RETURN_HANDSHAKE_FAILED;

  return ret;
}
