#include "hydra-mod.h"

#ifdef LIBOPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

#define MAX_CONNECT_RETRY 1
#define WAIT_BETWEEN_CONNECT_RETRY 3
#define HYDRA_DUMP_ROWS 16

int intern_socket, extern_socket;
char pair[260];
char HYDRA_EXIT[5] = "\x00\xff\x00\xff\x00";
char *HYDRA_EMPTY = "\x00\x00\x00\x00";
int fail = 0;
int alarm_went_off = 0;
int use_ssl = 0;
char ipaddr_str[INET_ADDRSTRLEN];
int src_port=0;
int __fck=0;
int ssl_first=1;

#ifdef LIBOPENSSL
SSL *ssl = NULL;
SSL_CTX *sslContext = NULL;
RSA *rsa = NULL;
#endif

/* prototype */
int my_select(int fd,fd_set *fdread, fd_set *fdwrite,fd_set *fdex,long sec, long usec);

/* ----------------- alarming functions ---------------- */

void
alarming()
{
  fail++;
  alarm_went_off++;

/* uh, I think it's not good for performance if we try to reconnect to a timeout system!
 *  if (fail > MAX_CONNECT_RETRY) {
 */
  fprintf(stderr, "Process %d: Can not connect [timeout], process exiting\n", (int) getpid());
  if (debug)
    printf("DEBUG_CONNECT_TIMEOUT\n");
  hydra_child_exit(1);

/*
 *   } else {
 *     if (verbose) fprintf(stderr, "Process %d: Can not connect [timeout], retrying (%d of %d retries)\n", (int)getpid(), fail, MAX_CONNECT_RETRY);
 *   }
 */
}

void
interrupt()
{
  if (debug)
    printf("DEBUG_INTERRUPTED\n");
}

/* ----------------- internal functions ----------------- */

int
internal__hydra_connect(unsigned long int host, int port, int protocol, int type)
{
  int s, ret = -1;
  struct sockaddr_in target;
  char *buf, *tmpptr = NULL;
  struct sockaddr_in sin;

#ifndef CYGWIN
  char out[16];
#endif

  if ((s = socket(PF_INET, protocol, type)) >= 0) {
    if (src_port != 0)
    {
      int bind_ok=0;
      sin.sin_family = PF_INET;
      sin.sin_port = htons(src_port);
      sin.sin_addr.s_addr = INADDR_ANY;
      
      //we will try to find a free port down to 512
      while (!bind_ok && src_port >= 512)
      {
	if (bind(s, (struct sockaddr *)&sin, sizeof(sin))==-1)
	{
	      if (verbose)
	        perror("error:");
	      if (errno == EADDRINUSE)
	      {
	      	  src_port--;
		  sin.sin_port = htons(src_port);
	      }
	      else
	      {
		if (errno == EACCES && (getuid() > 0))
		{
			printf("You need to be root to test this service\n");
			return -1;
		}
	      }
	}
	else
		bind_ok=1;
      }
    }  
    if (use_proxy > 0) {
      target.sin_port = htons(proxy_string_port);
      memcpy(&target.sin_addr.s_addr, &proxy_string_ip, 4);
    } else {
      target.sin_port = htons(port);
      memcpy(&target.sin_addr.s_addr, &host, 4);
    }
    target.sin_family = AF_INET;
    signal(SIGALRM, alarming);
    do {
      if (fail > 0)
        sleep(WAIT_BETWEEN_CONNECT_RETRY);
      alarm_went_off = 0;
      alarm(waittime);
      ret = connect(s, (struct sockaddr *) &target, sizeof(target));
      alarm(0);
      if (ret < 0 && alarm_went_off == 0) {
        fail++;
        if (verbose && fail <= MAX_CONNECT_RETRY)
          fprintf(stderr, "Process %d: Can not connect [unreachable], retrying (%d of %d retries)\n", (int) getpid(), fail, MAX_CONNECT_RETRY);
      }
    } while (ret < 0 && fail <= MAX_CONNECT_RETRY);
    if (ret < 0 && fail > MAX_CONNECT_RETRY) {
      if (debug)
        printf("DEBUG_CONNECT_UNREACHABLE\n");

/* we wont quit here, thats up to the module to decide what to do 
 *              fprintf(stderr, "Process %d: Can not connect [unreachable], process exiting\n", (int)getpid());
 *              hydra_child_exit(1);
 */
      extern_socket = -1;
      ret = -1;
      return ret;
    }
    ret = s;
    extern_socket = s;
    if (debug)
      printf("DEBUG_CONNECT_OK\n");

    if (use_proxy == 2) {
      if ((buf = malloc(4096)) == NULL) {
        fprintf(stderr, "ERROR: could not malloc()\n");
        return -1;
      }
      memset(&target, 0, sizeof(target));
      memcpy(&target.sin_addr.s_addr, &host, 4);
      target.sin_family = AF_INET;
#ifdef CYGWIN
      if (proxy_authentication == NULL)
        snprintf(buf, 4096, "CONNECT %s:%d HTTP/1.0\r\n\r\n", inet_ntoa((struct in_addr) target.sin_addr), port);
      else
        snprintf(buf, 4096, "CONNECT %s:%d HTTP/1.0\r\nProxy-Authorization: Basic %s\r\n\r\n", inet_ntoa((struct in_addr) target.sin_addr), port, proxy_authentication);
#else
      if (proxy_authentication == NULL)
        snprintf(buf, 4096, "CONNECT %s:%d HTTP/1.0\r\n\r\n", inet_ntop(AF_INET, &target.sin_addr, out, sizeof(out)), port);
      else
        snprintf(buf, 4096, "CONNECT %s:%d HTTP/1.0\r\nProxy-Authorization: Basic %s\r\n\r\n", inet_ntop(AF_INET, &target.sin_addr, out, sizeof(out)), port,
                 proxy_authentication);
#endif
      send(s, buf, strlen(buf), 0);
      recv(s, buf, 4096, 0);
      if (strncmp("HTTP/", buf, strlen("HTTP/")) == 0 && (tmpptr = index(buf, ' ')) != NULL && *++tmpptr == '2') {
        if (debug)
          printf("DEBUG_CONNECT_SSL_OK\n");
      } else {
        if (debug)
          printf("DEBUG_CONNECT_SSL_FAILED (Code: %c%c%c)\n", *tmpptr, *(tmpptr + 1), *(tmpptr + 2));
        if (verbose)
          fprintf(stderr, "Error: CONNECT call to proxy failed with code %c%c%c\n", *tmpptr, *(tmpptr + 1), *(tmpptr + 2));
        close(s);
        extern_socket = -1;
        ret = -1;
        free(buf);
        return ret;
      }
      free(buf);
    }
    fail = 0;
    return ret;
  }
  return ret;
}

#ifdef LIBOPENSSL
RSA *
ssl_temp_rsa_cb(SSL * ssl, int export, int keylength)
{
  if (rsa == NULL)
    rsa = RSA_generate_key(512, RSA_F4, NULL, NULL);
  return rsa;
}

int
internal__hydra_connect_ssl(unsigned long int host, int port, int protocol, int type)
{
  int socket, err;

  if (ssl_first) {
    SSL_load_error_strings();
    SSL_library_init();	
//    SSLeay_add_ssl_algorithms();
    ssl_first = 0;
  }

  if (sslContext == NULL) {
    /* context: ssl2 + ssl3 is allowed, whatever the server demands */
    if ((sslContext = SSL_CTX_new(SSLv23_client_method())) == NULL) {
      if (verbose) {
        err = ERR_get_error();
        fprintf(stderr, "SSL: Error allocating context: %s\n", ERR_error_string(err, NULL));
      }
      return -1;
    }
    /* set the compatbility mode */
    SSL_CTX_set_options(sslContext, SSL_OP_ALL);

    /* we set the default verifiers and dont care for the results */
    (void) SSL_CTX_set_default_verify_paths(sslContext);
    SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb);
    SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL);
  }

  if ((ssl = SSL_new(sslContext)) == NULL) {
    if (verbose) {
      err = ERR_get_error();
      fprintf(stderr, "Error preparing an SSL context: %s\n", ERR_error_string(err, NULL));
    }
    SSL_set_bio(ssl, NULL, NULL);
    SSL_clear(ssl);
    return -1;
  }

  if ((socket = internal__hydra_connect(host, port, protocol, type)) < 0)
    return -1;

  SSL_set_fd(ssl, socket);
  if (SSL_connect(ssl) <= 0) {
    printf("ERROR %d\n", SSL_connect(ssl));
    if (verbose) {
      err = ERR_get_error();
      fprintf(stderr, "Could not create an SSL session: %s\n", ERR_error_string(err, NULL));
    }
    close(socket);
    return -1;
  }

  if (debug)
    fprintf(stderr, "SSL negotiated cipher: %s\n", SSL_get_cipher(ssl));

  use_ssl = 1;

  return socket;
}
#endif

int
internal__hydra_recv(int socket, char *buf, int length)
{
#ifdef LIBOPENSSL
  if (use_ssl) {
    return SSL_read(ssl, buf, length);
  } else
#endif
    return recv(socket, buf, length, 0);
}

int
internal__hydra_send(int socket, char *buf, int size, int options)
{
#ifdef LIBOPENSSL
  if (use_ssl) {
    return SSL_write(ssl, buf, size);
  } else
#endif
    return send(socket, buf, size, options);
}

/* ------------------ public functions ------------------ */

void
hydra_child_exit(int code)
{
  if (code == 0)                /* normal quitting */
    __fck=write(intern_socket, "Q", 1);
  if (code == 1)                /* no connect possible */
    __fck=write(intern_socket, "C", 1);
  if (code == 2)                /* application protocol error or service shutdown */
    __fck=write(intern_socket, "E", 1);
  exit(-1);
}

void
hydra_register_socket(int s)
{
  intern_socket = s;
}

char *
hydra_get_next_pair()
{
  if (pair[0] == 0) {
    __fck=read(intern_socket, pair, sizeof(pair));
    if (memcmp(&HYDRA_EXIT, &pair, sizeof(HYDRA_EXIT)) == 0)
      return HYDRA_EXIT;
    if (pair[0] == 0)
      return HYDRA_EMPTY;
  }
  return pair;
}

char *
hydra_get_next_login()
{
  if (pair[0] == 0)
    return HYDRA_EMPTY;
  return pair;
}

char *
hydra_get_next_password()
{
  char *ptr = pair;

  while (*ptr != '\0')
    ptr++;
  ptr++;
  if (*ptr == 0)
    return HYDRA_EMPTY;
  return ptr;
}

void
hydra_completed_pair()
{
  __fck=write(intern_socket, "N", 1);
  pair[0] = 0;
}

void
hydra_completed_pair_found()
{
  char *login;

  __fck=write(intern_socket, "F", 1);
  login = hydra_get_next_login();
  __fck=write(intern_socket, login, strlen(login) + 1);
  pair[0] = 0;
}

void
hydra_completed_pair_skip()
{
  char *login;

  __fck=write(intern_socket, "f", 1);
  login = hydra_get_next_login();
  __fck=write(intern_socket, login, strlen(login) + 1);
  pair[0] = 0;
}

void
hydra_report_found(int port, char *svc, FILE * fp)
{
  if (!strcmp(svc,"rsh"))
	fprintf(fp, "[%d][%s] login: %s\n", port, svc, hydra_get_next_login());  
  else
  	fprintf(fp, "[%d][%s] login: %s   password: %s\n", port, svc, hydra_get_next_login(), hydra_get_next_password());
  fflush(fp);
}

void
hydra_report_found_host(int port, unsigned int ip, char *svc, FILE * fp)
{
#ifdef CYGWIN
  struct in_addr *ipptr = (struct in_addr *) &ip;

  strcpy(ipaddr_str, inet_ntoa((struct in_addr) *ipptr));
#else
  inet_ntop(AF_INET, &ip, ipaddr_str, sizeof(ipaddr_str));
#endif
  if (!strcmp(svc,"rsh"))
	fprintf(fp, "[%d][%s] host: %s   login: %s\n", port, svc, ipaddr_str, hydra_get_next_login());
  else
  	fprintf(fp, "[%d][%s] host: %s   login: %s   password: %s\n", port, svc, ipaddr_str, hydra_get_next_login(), hydra_get_next_password());
  if (stdout != fp)
  {
   if (!strcmp(svc,"rsh"))
    	printf("[%d][%s] host: %s   login: %s\n", port, svc, ipaddr_str, hydra_get_next_login());
    else
        printf("[%d][%s] host: %s   login: %s   password: %s\n", port, svc, ipaddr_str, hydra_get_next_login(), hydra_get_next_password());
   } 
   fflush(fp); 
}

void
hydra_report_found_host_msg(int port, unsigned int ip, char *svc, FILE *fp, char *msg)
{
#ifdef CYGWIN
  struct in_addr *ipptr = (struct in_addr *) &ip;

  strcpy(ipaddr_str, inet_ntoa((struct in_addr) *ipptr));
#else
  inet_ntop(AF_INET, &ip, ipaddr_str, sizeof(ipaddr_str));
#endif
  if (!strcmp(svc,"rsh"))
  	fprintf(fp, "[%d][%s] host: %s   login: %s [%s]\n", port, svc, ipaddr_str, hydra_get_next_login(), msg);
  else
	fprintf(fp, "[%d][%s] host: %s   login: %s   password: %s  [%s]\n", port, svc, ipaddr_str, hydra_get_next_login(), hydra_get_next_password(), msg);  	

  if (stdout != fp)
  {
	if (!strcmp(svc,"rsh"))
		printf("[%d][%s] host: %s   login: %s\n", port, svc, ipaddr_str, hydra_get_next_login());
	else
    		printf("[%d][%s] host: %s   login: %s   password: %s\n", port, svc, ipaddr_str, hydra_get_next_login(), hydra_get_next_password());
  }   
  fflush(fp);
}

int
hydra_connect_ssl(unsigned long int host, int port)
{
#ifdef LIBOPENSSL
  return (internal__hydra_connect_ssl(host, port, SOCK_STREAM, 6));
#else
  return (internal__hydra_connect(host, port, SOCK_STREAM, 6));
#endif
}

int
hydra_connect_tcp(unsigned long int host, int port)
{
  return (internal__hydra_connect(host, port, SOCK_STREAM, 6));
}

int
hydra_connect_udp(unsigned long int host, int port)
{
  return (internal__hydra_connect(host, port, SOCK_DGRAM, 17));
}

int
hydra_disconnect(int socket)
{
#ifdef LIBOPENSSL
  if (use_ssl && SSL_get_fd(ssl) == socket) {
    /* SSL_shutdown(ssl); ...skip this--it slows things down */
    SSL_set_bio(ssl, NULL, NULL);
    SSL_clear(ssl);
    use_ssl = 0;
  }
#endif
  close(socket);
  if (debug)
    printf("DEBUG_DISCONNECT\n");
  return -1;
}

int
hydra_data_ready_writing_timed(int socket, long sec, long usec)
{
  fd_set fds;
  
  FD_ZERO(&fds);
  FD_SET(socket, &fds);
  return(my_select(socket+1,&fds,NULL,NULL,sec,usec));
}

int
hydra_data_ready_writing(int socket)
{
  return (hydra_data_ready_writing_timed(socket, 30, 0));
}

int
hydra_data_ready_timed(int socket, long sec, long usec)
{
  fd_set fds;

  FD_ZERO(&fds);
  FD_SET(socket, &fds);
  return(my_select(socket+1,&fds,NULL,NULL,sec,usec));
}

int
hydra_data_ready(int socket)
{
  return (hydra_data_ready_timed(socket, 0, 100));
}

int
hydra_recv(int socket, char *buf, int length)
{
  int ret;

  ret = internal__hydra_recv(socket, buf, length);
  if (debug)
    printf("DEBUG_RECV_BEGIN|%s|END\n", buf);
  return ret;
}

char *
hydra_receive_line(int socket)
{
  char buf[300], *buff;
  int i = 0, j = 0, k;

  if ((buff = malloc(sizeof(buf))) == NULL) {
    fprintf(stderr, "ERROR: could not malloc\n");
    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;
  } else {
    for (k = 0; k < i; k++)
      if (buff[k] == 0)
        buff[k] = 32;
  }

  j = 1;
  while (hydra_data_ready(socket) > 0 && j > 0) {
    j = internal__hydra_recv(socket, buf, sizeof(buf));
    if (j > 0)
      for (k = 0; k < j; k++)
        if (buf[k] == 0)
          buf[k] = 32;
    buff = realloc(buff, i + j);
    memcpy(buff + i, &buf, j);
    i = i + j;
  }

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

int
hydra_send(int socket, char *buf, int size, int options)
{
  if (debug) {
    int k;
    char *debugbuf = malloc(size + 1);
    if (debugbuf != NULL) {
      for (k = 0; k < size; k++)
        if (buf[k] == 0)
          debugbuf[k] = 32;
        else
          debugbuf[k] = buf[k];
      debugbuf[size] = 0;
      printf("DEBUG_SEND_BEGIN|%s|END\n", debugbuf);
      free(debugbuf);
    }
  }

/*    if (hydra_data_ready_writing(socket)) < 1) return -1; XXX maybe needed in the future */
  return (internal__hydra_send(socket, buf, size, options));
}

int
make_to_lower(char *buf)
{
  if (buf == NULL)
    return 1;
  while (buf[0] != 0) {
    buf[0] = tolower((int)buf[0]);
    buf++;
  }
  return 1;
}

unsigned char
hydra_conv64(unsigned char in)
{
  if (in < 26)
    return (in + 'A');
  else if (in >= 26 && in < 52)
    return (in + 'a' - 26);
  else if (in >= 52 && in < 62)
    return (in + '0' - 52);
  else if (in == 62)
    return '+';
  else if (in == 63)
    return '/';
  else {
    fprintf(stderr, "Too high for base64: %d\n", in);
    return 0;
  }
}

void
hydra_tobase64(unsigned char *buf, int buflen, int bufsize)
{
  unsigned char small[3] = { 0, 0, 0 };
  unsigned char big[5];
  unsigned char *ptr = buf;
  int i = bufsize;
  unsigned int len = 0;
  unsigned char bof[i];

  if (buf == NULL || strlen((char *) buf) == 0)
    return;
  bof[0] = 0;
  memset(big, 0, sizeof(big));
  memset(bof, 0, bufsize);

  for (i = 0; i < buflen / 3; i++) {
    memset(big, 0, sizeof(big));
    big[0] = hydra_conv64(*ptr >> 2);
    big[1] = hydra_conv64(((*ptr & 3) << 4) + (*(ptr + 1) >> 4));
    big[2] = hydra_conv64(((*(ptr + 1) & 15) << 2) + (*(ptr + 2) >> 6));
    big[3] = hydra_conv64(*(ptr + 2) & 63);
    len += strlen((char*)big);
    if (len > bufsize) {
      buf[0] = 0;
      return;
    }
    strcat((char *) bof, (char *) big);
    ptr += 3;
  }

  if (*ptr != 0) {
    small[0] = *ptr;
    if (*(ptr + 1) != 0)
      small[1] = *(ptr + 1);
    else
      small[1] = 0;
    ptr = small;
    big[0] = hydra_conv64(*ptr >> 2);
    big[1] = hydra_conv64(((*ptr & 3) << 4) + (*(ptr + 1) >> 4));
    big[2] = hydra_conv64(((*(ptr + 1) & 15) << 2) + (*(ptr + 2) >> 6));
    big[3] = hydra_conv64(*(ptr + 2) & 63);
    if (small[1] == 0)
      big[2] = '=';
    big[3] = '=';
    strcat((char *) bof, (char *) big);
  }

  strcpy((char *) buf, (char *) bof); /* can not overflow */
}

void
hydra_dump_asciihex(unsigned char *string, int length)
{
    unsigned char *p = (unsigned char *) string;
    unsigned char lastrow_data[16];
    int rows = length / HYDRA_DUMP_ROWS;
    int lastrow = length % HYDRA_DUMP_ROWS;
    int i, j;

    for (i = 0; i < rows; i++) {
        printf("%04hx:  ", i * 16);
        for (j = 0; j < HYDRA_DUMP_ROWS; j++) {
            printf("%02x", p[(i * 16) + j]);
            if (j % 2 == 1)
                printf(" ");
        }
        printf("   [ ");
        for (j = 0; j < HYDRA_DUMP_ROWS; j++) {
            if (isprint(p[(i * 16) + j]))
                printf("%c", p[(i * 16) + j]);
            else
                printf(".");
        }
        printf(" ]\n");
    }
    if (lastrow > 0) {
        memset(lastrow_data, 0, sizeof(lastrow_data));
        memcpy(lastrow_data, p + length - lastrow, lastrow);
        printf("%04hx:  ", i * 16);
        for (j = 0; j < lastrow; j++) {
            printf("%02x", p[(i * 16) + j]);
            if (j % 2 == 1)
                printf(" ");
        }
        while(j < HYDRA_DUMP_ROWS) {
            printf("  ");
            if (j % 2 == 1)
                printf(" ");
            j++;
        }
        printf("   [ ");
        for (j = 0; j < lastrow; j++) {
            if (isprint(p[(i * 16) + j]))
                printf("%c", p[(i * 16) + j]);
            else
                printf(".");
        }
        while(j < HYDRA_DUMP_ROWS) {
            printf(" ");
            j++;
        }
        printf(" ]\n");
    }
}

void
hydra_set_srcport(int port)
{
	src_port=port;
}
