#include "hydra-mod.h"

extern char *HYDRA_EXIT;
extern int32_t child_head_no;
int32_t seq = 1;

const unsigned char icq5_table[] = {
  0x59, 0x60, 0x37, 0x6B, 0x65, 0x62, 0x46, 0x48, 0x53, 0x61, 0x4C,
  0x59, 0x60, 0x57, 0x5B, 0x3D, 0x5E, 0x34, 0x6D, 0x36, 0x50, 0x3F,
  0x6F, 0x67, 0x53, 0x61, 0x4C, 0x59, 0x40, 0x47, 0x63, 0x39, 0x50,
  0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43, 0x69, 0x48, 0x33, 0x31, 0x64,
  0x35, 0x5A, 0x4A, 0x42, 0x56, 0x40, 0x67, 0x53, 0x41, 0x07, 0x6C,
  0x49, 0x58, 0x3B, 0x4D, 0x46, 0x68, 0x43, 0x69, 0x48, 0x33, 0x31,
  0x44, 0x65, 0x62, 0x46, 0x48, 0x53, 0x41, 0x07, 0x6C, 0x69, 0x48,
  0x33, 0x51, 0x54, 0x5D, 0x4E, 0x6C, 0x49, 0x38, 0x4B, 0x55, 0x4A,
  0x62, 0x46, 0x48, 0x33, 0x51, 0x34, 0x6D, 0x36, 0x50, 0x5F, 0x5F,
  0x5F, 0x3F, 0x6F, 0x47, 0x63, 0x59, 0x40, 0x67, 0x33, 0x31, 0x64,
  0x35, 0x5A, 0x6A, 0x52, 0x6E, 0x3C, 0x51, 0x34, 0x6D, 0x36, 0x50,
  0x5F, 0x5F, 0x3F, 0x4F, 0x37, 0x4B, 0x35, 0x5A, 0x4A, 0x62, 0x66,
  0x58, 0x3B, 0x4D, 0x66, 0x58, 0x5B, 0x5D, 0x4E, 0x6C, 0x49, 0x58,
  0x3B, 0x4D, 0x66, 0x58, 0x3B, 0x4D, 0x46, 0x48, 0x53, 0x61, 0x4C,
  0x59, 0x40, 0x67, 0x33, 0x31, 0x64, 0x55, 0x6A, 0x32, 0x3E, 0x44,
  0x45, 0x52, 0x6E, 0x3C, 0x31, 0x64, 0x55, 0x6A, 0x52, 0x4E, 0x6C,
  0x69, 0x48, 0x53, 0x61, 0x4C, 0x39, 0x30, 0x6F, 0x47, 0x63, 0x59,
  0x60, 0x57, 0x5B, 0x3D, 0x3E, 0x64, 0x35, 0x3A, 0x3A, 0x5A, 0x6A,
  0x52, 0x4E, 0x6C, 0x69, 0x48, 0x53, 0x61, 0x6C, 0x49, 0x58, 0x3B,
  0x4D, 0x46, 0x68, 0x63, 0x39, 0x50, 0x5F, 0x5F, 0x3F, 0x6F, 0x67,
  0x53, 0x41, 0x25, 0x41, 0x3C, 0x51, 0x54, 0x3D, 0x5E, 0x54, 0x5D,
  0x4E, 0x4C, 0x39, 0x50, 0x5F, 0x5F, 0x5F, 0x3F, 0x6F, 0x47, 0x43,
  0x69, 0x48, 0x33, 0x51, 0x54, 0x5D, 0x6E, 0x3C, 0x31, 0x64, 0x35,
  0x5A, 0x00, 0x00
};

void fix_packet(char *buf, int32_t len) {
  unsigned long c1, c2;
  unsigned long r1, r2;
  int32_t pos, key, k;

  c1 = buf[8];
  c1 <<= 8;
  c1 |= buf[4];
  c1 <<= 8;
  c1 |= buf[2];
  c1 <<= 8;
  c1 |= buf[6];

  r1 = (rand() % (len - 0x18)) + 0x18;
  r2 = rand() & 0xff;

  c2 = r1;
  c2 <<= 8;
  c2 |= buf[r1];
  c2 <<= 8;
  c2 |= r2;
  c2 <<= 8;
  c2 |= icq5_table[r2];
  c2 ^= 0xff00ff;

  c1 ^= c2;
  buf[0x14] = c1 & 0xff;
  buf[0x15] = (c1 >> 8) & 0xff;
  buf[0x16] = (c1 >> 16) & 0xff;
  buf[0x17] = (c1 >> 24) & 0xff;

  key = len * 0x68656c6cL;
  key += c1;
  pos = 0xa;

  for (; pos < len; pos += 4)
    k = key + icq5_table[pos & 0xff];
}

void icq_header(char *buf, unsigned short cmd, unsigned long uin) {
  buf[0] = 0x02;
  buf[1] = 0x00;
  buf[2] = cmd & 0xff;
  buf[3] = (cmd >> 8) & 0xff;
  buf[4] = seq & 0xff;
  buf[5] = (seq++ >> 8) & 0xff;
  buf[6] = uin & 0xff;
  buf[7] = (uin >> 8) & 0xff;
  buf[8] = (uin >> 16) & 0xff;
  buf[9] = (uin >> 24) & 0xff;
}

int32_t icq_login(int32_t s, char *login, char *pass) {
  unsigned long uin = strtoul(login, NULL, 10);
  char buf[256];
  int32_t len;

  bzero(buf, sizeof(buf));

  icq_header(buf, 0x03e8, uin);
  len = strlen(pass) + 1;
  buf[14] = len;
  memcpy(&buf[16], pass, len);
  buf[16 + len] = 0x78;
  buf[24 + len] = 0x04;
  buf[29 + len] = 0x02;
  buf[39 + len] = 0x08;
  buf[41 + len] = 0x78;

  return (hydra_send(s, buf, 43 + len, 0));
}

int32_t icq_login_1(int32_t s, char *login) {
  unsigned long uin = strtoul(login, NULL, 10);
  char buf[64];

  icq_header(buf, 0x044c, uin);
  return (hydra_send(s, buf, 10, 0));
}

int32_t icq_disconnect(int32_t s, char *login) {
  unsigned long uin = strtoul(login, NULL, 10);
  char buf[64];

  bzero(buf, sizeof(buf));
  icq_header(buf, 0x0438, uin);
  buf[10] = 20;
  memcpy(&buf[12], "B_USER_DISCONNECTED", 20);
  buf[32] = 0x5;
  return (hydra_send(s, buf, 34, 0));
}

int32_t icq_ack(int32_t s, char *login) {
  unsigned long uin = strtoul(login, NULL, 10);
  char buf[64];

  buf[0] = 0x02;
  buf[1] = 0x00;
  buf[2] = 0x0a;
  buf[3] = 0x0;
  buf[4] = seq & 0xff;
  buf[5] = (seq >> 8) & 0xff;
  buf[6] = uin & 0xff;
  buf[7] = (uin >> 8) & 0xff;
  buf[8] = (uin >> 16) & 0xff;
  buf[9] = (uin >> 24) & 0xff;

  return (hydra_send(s, buf, 10, 0));
}

int32_t start_icq(int32_t sock, char *ip, int32_t port, FILE * output, char *miscptr, FILE * fp) {
  unsigned char buf[1024];
  char *login, *pass;
  char *empty = "";
  int32_t i, r;

  if (strlen(login = hydra_get_next_login()) == 0)
    return 2;
  if (strlen(pass = hydra_get_next_password()) == 0)
    pass = empty;

  for (i = 0; login[i]; i++)
    if (!isdigit((int32_t) login[i])) {
      fprintf(stderr, "[ERROR] Invalid UIN %s\n, ignoring.", login);
      hydra_completed_pair();
      return 2;
    }

  icq_login(sock, login, pass);

  while (1) {
    if ((r = hydra_recv(sock, (char *) buf, sizeof(buf))) == 0) {
      return 1;
    }

    if (r < 0) {
      if (verbose)
        fprintf(stderr, "[ERROR] Process %d: Can not connect [unreachable]\n", (int32_t) getpid());
      return 3;
    }

    if (buf[2] == 0x5a && buf[3] == 0x00) {
      hydra_report_found_host(port, ip, "icq", output);
      hydra_completed_pair_found();
      icq_ack(sock, login);
      icq_login_1(sock, login);
      hydra_recv(sock, (char *) buf, sizeof(buf));
      icq_ack(sock, login);
      hydra_recv(sock, (char *) buf, sizeof(buf));
      icq_ack(sock, login);
      icq_disconnect(sock, login);
      break;
    } else if ((buf[2] != 10 && buf[2] != 250) || buf[3] != 0) {
      hydra_completed_pair();
      break;
    }

/*       if((buf[2] != 10 || buf[3] != 0) && (buf[2] != 250 || buf[3] != 0)) */
  }

  if (memcmp(hydra_get_next_pair(), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0)
    return 3;
  return 1;
}

void service_icq(char *ip, int32_t sp, unsigned char options, char *miscptr, FILE * fp, int32_t port, char *hostname) {
  int32_t run = 1, next_run = 1, sock = -1;
  int32_t myport = PORT_ICQ;

  if (port)
    myport = port;

  port = myport;

  if ((options & OPTION_SSL) != 0 && child_head_no == 0) {
    fprintf(stderr, "[ERROR] You can not use SSL with ICQ!\n");
    hydra_child_exit(0);
  }

  hydra_register_socket(sp);
  if (memcmp(hydra_get_next_pair(), &HYDRA_EXIT, sizeof(HYDRA_EXIT)) == 0)
    return;

  while (1) {
    switch (run) {
    case 1:
      if (sock >= 0)
        sock = hydra_disconnect(sock);
      sock = hydra_connect_udp(ip, myport);
      if (sock < 0) {
        if (quiet != 1) fprintf(stderr, "[ERROR] Child with pid %d terminating, can not connect\n", (int32_t) getpid());
        hydra_child_exit(1);
      }
      next_run = 2;
      break;
    case 2:
      next_run = start_icq(sock, ip, port, fp, miscptr, fp);
      break;
    case 3:
      if (sock >= 0)
        sock = hydra_disconnect(sock);
      hydra_child_exit(2);
    default:
      fprintf(stderr, "[ERROR] Caught unknown return code, exiting!\n");
      hydra_child_exit(2);
    }
    run = next_run;
  }
}

int32_t service_icq_init(char *ip, int32_t sp, unsigned char options, char *miscptr, FILE * fp, int32_t port, char *hostname) {
  // called before the childrens are forked off, so this is the function
  // which should be filled if initial connections and service setup has to be
  // performed once only.
  //
  // fill if needed.
  // 
  // return codes:
  //   0 all OK
  //   -1  error, hydra will exit, so print a good error message here

  return 0;
}
