
/* mysql 3.2x.x to 5.0.x support - by mcbethh (at) u-n-f (dot) com */

#include <math.h>
#include "hydra-mod.h"

void hash_password(unsigned long *result, const char *password);
char *scramble(char *to, const char *message, const char *password);

extern int internal__hydra_recv(int socket, char *buf, int length);
extern int hydra_data_ready_timed(int socket, long sec, long usec);

extern char *HYDRA_EXIT;
char *buf;
char salt[9];

/* modified hydra_receive_line, I've striped code which changed every 0x00 to 0x20 */
char *
hydra_mysql_receive_line(int socket)
{
  char buf[300], *buff;
  int i = 0, j = 0;

  buff = malloc(sizeof(buf));
  if (buff == NULL)
    return NULL;
  memset(buff, 0, sizeof(buf));

  i = hydra_data_ready_timed(socket, (long) waittime, 0);
  if (i > 0) {
    if ((i = internal__hydra_recv(socket, buff, sizeof(buf))) < 0) {
      free(buff);
      return NULL;
    }
  }
  if (i <= 0) {
    if (debug)
      printf("DEBUG_RECV_BEGIN|%s|END\n", buff);
    free(buff);
    return NULL;
  }

  j = 1;
  while (hydra_data_ready(socket) > 0 && j > 0) {
    j = internal__hydra_recv(socket, buf, sizeof(buf));
    buff = realloc(buff, i + j);
    if (buff == NULL)
      return NULL;
    memcpy(buff + i, &buf, j);
    i = i + j;
  }

  if (debug)
    printf("DEBUG_RECV_BEGIN|%s|END\n", buff);
  return buff;
}

/* check if valid mysql protocol, mysql version and read salt */
char
hydra_mysql_init(int sock)
{
  char *server_version, *pos;
  unsigned char protocol;

  buf = hydra_mysql_receive_line(sock);
  if (buf == NULL)
    return 1;

  protocol = buf[4];
  if (protocol == 0xff) {
    pos = &buf[6];
//    *(strchr(pos, '.')) = '\0';
    fprintf(stderr, "[Error] %s\n", pos);
    return 2;
  }
  if (protocol < 10) {
    fprintf(stderr, "[Error] Only mysql 3.23.x and 4.0.x is supported\n");
    return 2;
  }
  if (protocol > 10) {
    fprintf(stderr, "[Warning] This is protocol version %d, only v10 is supported, not sure if it will work\n", protocol);
  }
  server_version = &buf[5];
  pos = buf + strlen(server_version) + 10;
  memcpy(salt, pos, 9);

  if (!strstr(server_version, "3.") && !strstr(server_version, "4.") && strstr(server_version, "5.")) {
    fprintf(stderr, "[Error] Not an MySQL protocol or unsupported version\n");
    return 2;
  }

  return 0;
}

/* prepare response to server greeting */
char *
hydra_mysql_prepare_auth(char *login, char *pass)
{
  unsigned char *response;
  unsigned long login_len = strlen(login) > 32 ? 32 : strlen(login);
  unsigned long response_len = 4 /* header */  +
    2 /* client flags */  +
    3 /* max packet len */  +
    login_len + 1 + 8 /* scrambled password len */ ;

  response = (unsigned char *) malloc(response_len + 4);
  if (response == NULL) {
    fprintf(stderr, "error allocating memory\n");
    return NULL;
  }
  memset(response, 0, response_len + 4);

  *((unsigned long *) response) = response_len - 4;
  response[3] = 0x01;           /* packet number */
  response[4] = 0x85;
  response[5] = 0x24;           /* client flags */
  response[6] = response[7] = response[8] = 0x00;       /* max packet */
  memcpy(&response[9], login, login_len);       /* login */
  response[9 + login_len] = '\0';       /* null terminate login */
  scramble((char *) &response[9 + login_len + 1], salt, pass);

  return (char *) response;
}

/* returns 0 if authentication succeed */
/* and 1 if failed                     */
char
hydra_mysql_parse_response(unsigned char *response)
{
  unsigned long response_len = *((unsigned long *) response) & 0xffffff;

  if (response_len < 4)
    return 0;

  if (response[4] == 0xff)
    return 1;

  return 0;
}

char
hydra_mysql_send_com_quit(int sock)
{
  char com_quit_packet[5] = { 0x01, 0x00, 0x00, 0x00, 0x01 };

  hydra_send(sock, com_quit_packet, 5, 0);
  return 0;
}

int
start_mysql(int sock, unsigned long int ip, int port, unsigned char options, char *miscptr, FILE * fp)
{
  char *response = NULL, *login = NULL, *pass = NULL;
  unsigned long response_len;
  char res = 0;

  login = hydra_get_next_login();
  pass = hydra_get_next_password();

  /* read server greeting */
  res = hydra_mysql_init(sock);
  if (res == 2) {
    hydra_child_exit(2);
    exit(-1);
  }
  if (res == 1)
    return 1;

  /* prepare client authentication packet */
  response = hydra_mysql_prepare_auth(login, pass);
  if (response == NULL)
    return 3;
  response_len = *((unsigned long *) response) & 0xffffff;

  /* send client auth packet                                             */
  /* dunny why, mysql IO code had problem reading my response.           */
  /* When I send response_len bytes, it always read response_len-4 bytes */
  /* I fixed it just by sending 4 characters more. It is maybe not good  */
  /* coding style, but working :)                                        */
  if (hydra_send(sock, response, response_len + 4, 0) < 0) {
    return 1;
  }
  free(response);

  /* read authentication response */
  if ((response = hydra_mysql_receive_line(sock)) == NULL)
    return 1;
  res = hydra_mysql_parse_response((unsigned char *) response);

  if (!res) {
    hydra_mysql_send_com_quit(sock);
    sock = hydra_disconnect(sock);
    hydra_report_found_host(port, ip, "mysql", fp);
    hydra_completed_pair_found();
    free(response);
    if (memcmp(hydra_get_next_pair(), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0)
      return 3;
    return 1;
  }

  free(response);
  hydra_completed_pair();
  if (memcmp(hydra_get_next_pair(), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0)
    return 3;

  /* each try requires new connection */
  return 1;
}

void
service_mysql(unsigned long int ip, int sp, unsigned char options, char *miscptr, FILE * fp, int port)
{
  int run = 1, next_run, sock = -1;
  int myport = PORT_MYSQL;

  hydra_register_socket(sp);
  if (memcmp(hydra_get_next_pair(), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0)
    return;
  while (1) {
    switch (run) {
    case 1:                    /* connect and service init function */
      if (sock >= 0) {
        hydra_mysql_send_com_quit(sock);
        sock = hydra_disconnect(sock);
      }
//      usleep(300000);
      if ((options & OPTION_SSL) == 0) {
        if (port != 0)
          myport = port;
        sock = hydra_connect_tcp(ip, myport);
        port = myport;
      }
      if (sock < 0) {
        fprintf(stderr, "Error: Child with pid %d terminating, can not connect\n", (int) getpid());
        hydra_child_exit(1);
      }
      next_run = 2;
      break;
    case 2:                    /* run the cracking function */
      next_run = start_mysql(sock, ip, port, options, miscptr, fp);
      break;
    case 3:                    /* clean exit */
      if (sock >= 0) {
        hydra_mysql_send_com_quit(sock);
        sock = hydra_disconnect(sock);
      }
      hydra_child_exit(0);
      return;
    default:
      fprintf(stderr, "Caught unknown return code, exiting!\n");
      hydra_child_exit(0);
      exit(-1);
    }
    run = next_run;
  }
}

/************************************************************************/

/* code belowe is copied from mysql 3.23.57 source code (www.mysql.com) */

/* and slightly modified (removed not needed parts of code, changed     */

/* data types)                                                          */

/************************************************************************/
struct rand_struct {
  unsigned long seed1, seed2, max_value;
  double max_value_dbl;
};

void
randominit(struct rand_struct *rand_st, unsigned long seed1, unsigned long seed2)
{                               /* For mysql 3.21.# */
  rand_st->max_value = 0x3FFFFFFFL;
  rand_st->max_value_dbl = (double) rand_st->max_value;
  rand_st->seed1 = seed1 % rand_st->max_value;
  rand_st->seed2 = seed2 % rand_st->max_value;
}

double
rnd(struct rand_struct *rand_st)
{
  rand_st->seed1 = (rand_st->seed1 * 3 + rand_st->seed2) % rand_st->max_value;
  rand_st->seed2 = (rand_st->seed1 + rand_st->seed2 + 33) % rand_st->max_value;
  return (((double) rand_st->seed1) / rand_st->max_value_dbl);
}

void
hash_password(unsigned long *result, const char *password)
{
  register unsigned long nr = 1345345333L, add = 7, nr2 = 0x12345671L;
  unsigned long tmp;

  for (; *password; password++) {
    if (*password == ' ' || *password == '\t')
      continue;                 /* skipp space in password */
    tmp = (unsigned long) (unsigned char) *password;
    nr ^= (((nr & 63) + add) * tmp) + (nr << 8);
    nr2 += (nr2 << 8) ^ nr;
    add += tmp;
  }
  result[0] = nr & (((unsigned long) 1L << 31) - 1L); /* Don't use sign bit (str2int) */ ;
  result[1] = nr2 & (((unsigned long) 1L << 31) - 1L);
  return;
}

char *
scramble(char *to, const char *message, const char *password)
{
  struct rand_struct rand_st;
  unsigned long hash_pass[2], hash_message[2];
  char extra;

  if (password && password[0]) {
    char *to_start = to;

    hash_password(hash_pass, password);
    hash_password(hash_message, message);
    randominit(&rand_st, hash_pass[0] ^ hash_message[0], hash_pass[1] ^ hash_message[1]);
    while (*message++)
      *to++ = (char) (floor(rnd(&rand_st) * 31) + 64);
    extra = (char) (floor(rnd(&rand_st) * 31));
    while (to_start != to)
      *(to_start++) ^= extra;
  }
  *to = 0;
  return to;
}
