/* The decryption_func thread function tests all the passwords of the form:
 *   prefix + x + combination + suffix
 * where x is a character in the range charset[arg[0]] -> charset[arg[1]]. */
void * decryption_func(void *arg)
{
  unsigned char *password, *key, *iv, *out;
  unsigned int password_len, index_start, index_end, len, out_len1, out_len2, i, j, k;
  int ret, found;
  unsigned int *tab;
  EVP_CIPHER_CTX ctx;

  index_start = ((unsigned int *) arg)[0];
  index_end = ((unsigned int *) arg)[1];
  key = (unsigned char *) malloc(EVP_CIPHER_key_length(cipher));
  iv = (unsigned char *) malloc(EVP_CIPHER_iv_length(cipher));
  out = (unsigned char *) malloc(data_len + EVP_CIPHER_block_size(cipher));
  if((key == NULL) || (iv == NULL) || (out == NULL))
    {
      fprintf(stderr, "Error: memory allocation failed.\n\n");
      exit(EXIT_FAILURE);
    }

  /* For every possible length */
  for(len = min_len - prefix_len - 1 - suffix_len; len + 1 <= max_len - prefix_len - suffix_len; len++)
    {
      /* For every first character in the range we were given */
      for(k = index_start; k <= index_end; k++)
        {
          password_len = prefix_len + 1 + len + suffix_len;
          password = (unsigned char *) malloc(password_len + 1);
          tab = (unsigned int *) malloc((len + 1) * sizeof(unsigned int));
          if((password == NULL) || (tab == NULL))
            {
              fprintf(stderr, "Error: memory allocation failed.\n\n");
              exit(EXIT_FAILURE);
            }
          strncpy(password, prefix, prefix_len);
          password[prefix_len] = charset[k];
          strncpy(password + prefix_len + 1 + len, suffix, suffix_len);
          password[password_len] = '\0';

          for(i = 0; i <= len; i++)
            tab[i] = 0;

          /* Test all the combinations */
          while((tab[len] == 0) && (stop == 0))
            {
              for(i = 0; i < len; i++)
                password[prefix_len + 1 + i] = charset[tab[len - 1 - i]];

              /* Decrypt data with password */
              EVP_BytesToKey(cipher, digest, salt, password, password_len, 1, key, iv);
              EVP_DecryptInit(&ctx, cipher, key, iv);
              EVP_DecryptUpdate(&ctx, out, &out_len1, data, data_len);
              ret = EVP_DecryptFinal(&ctx, out + out_len1, &out_len2);

              if(no_error || (ret == 1))
                {
                  if(magic == NULL)
                    found = valid_data(out, out_len1 + out_len2);
                  else
                    found = !strncmp(out, magic, strlen(magic));
                }
              else
                found = 0;

              if(found)
                {
                  /* We have a positive result */
                  pthread_mutex_lock(&found_password_lock);
                  if(binary == NULL)
                    printf("Password candidate: %s\n", password);
                  else
                    {
                      int fd;
                      char outfile[128];

                      sprintf(outfile, "%s-%d", binary, solution++);
                      fd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
                      if(fd == -1)
                        perror("open file");
                      else
                        {
                          printf("Password candidate saved to file: %s \n", outfile);
                          ret = write(fd, password, password_len);
                          close(fd);
                        }
                    }
                  if(only_one_password)
                    stop = 1;
                  pthread_mutex_unlock(&found_password_lock);
                }

              EVP_CIPHER_CTX_cleanup(&ctx);

              if(limit)
                {
                  pthread_mutex_lock(&found_password_lock);
                  if(limit <= count++)
                    {
                      if(only_one_password)
                        {
                          printf("Maximum number of passphrases tested, aborting.\n");
                          stop = 1;
                        }
                      else
                        {
                          overall = overall + count - 1;
                          if(binary == NULL)
                            printf("Just tested solution %lu: %s\n", overall, password);
                          else
                            printf("Just tested solution %lu\n", overall);
                          count -= limit;
                        }
                    }
                  pthread_mutex_unlock(&found_password_lock);
              }

              if(len == 0)
                break;
              tab[0]++;
              if(tab[0] == charset_len)
                tab[0] = 0;
              j = 0;
              while(tab[j] == 0)
                {
                  j++;
                  tab[j]++;
                  if((j < len) && (tab[j] == charset_len))
                    tab[j] = 0;
                }
            }
          free(tab);
          free(password);
        }
    }

  free(out);
  free(iv);
  free(key);

  pthread_exit(NULL);
}
void * decryption_func(void *arg)
{
  struct decryption_func_locals *dfargs;
  unsigned char *pwd, *key, *iv, *out;
  unsigned int pwd_len, len, out_len1, out_len2;
  int ret, found;
  EVP_CIPHER_CTX *ctx;

  dfargs = (struct decryption_func_locals *) arg;

  key = (unsigned char *) malloc(EVP_CIPHER_key_length(cipher));
  iv = (unsigned char *) malloc(EVP_CIPHER_iv_length(cipher));
  out = (unsigned char *) malloc(data_len + EVP_CIPHER_block_size(cipher));
  ctx = EVP_CIPHER_CTX_new();
  if((key == NULL) || (iv == NULL) || (out == NULL) || (ctx == NULL))
  {
    fprintf(stderr, "Error: memory allocation failed.\n\n");
    exit(EXIT_FAILURE);
  }

  do
  {
    if(dictionary == NULL)
    {
      if(binary)
        ret = generate_next_binary_password(&pwd, &pwd_len);
      else
        ret = generate_next_password(&pwd, &pwd_len);          
    }
    else
      ret = read_dictionary_line(&pwd, &pwd_len);
    if(ret == 0)
      break;

    /* Decrypt data with password */
    if(no_salt)
      EVP_BytesToKey(cipher, digest, NULL, pwd, pwd_len, 1, key, iv);
    else
      EVP_BytesToKey(cipher, digest, salt, pwd, pwd_len, 1, key, iv);
    EVP_DecryptInit(ctx, cipher, key, iv);
    EVP_DecryptUpdate(ctx, out, &out_len1, data, data_len);
    ret = EVP_DecryptFinal(ctx, out + out_len1, &out_len2);
    if(no_error || (ret == 1))
    {
      if(magic == NULL)
        found = valid_data(out, out_len1 + out_len2);
      else
        found = !strncmp(out, magic, strlen(magic));
    }
    else
      found = 0;

    if(found)
    {
      /* We have a positive result */
      handle_signal(SIGUSR1); /* Print some stats */
      pthread_mutex_lock(&found_password_lock);
      found_password++;
      printf("Password candidate: %s\n", pwd);
      if(only_one_password)
        stop = 1;
      pthread_mutex_unlock(&found_password_lock);
    }
    dfargs->counter++;

    EVP_CIPHER_CTX_cleanup(ctx);

    if(limit > 0)
    {
      pthread_mutex_lock(&found_password_lock);
      count_limit++;
      if(count_limit >= limit)
      {
        fprintf(stderr, "Maximum number of passphrases tested, aborting.\n");
        stop = 1;
      }
      pthread_mutex_unlock(&found_password_lock);
    }

    free(pwd);
  }
  while(stop == 0);

  EVP_CIPHER_CTX_free(ctx);
  free(out);
  free(iv);
  free(key);

  pthread_exit(NULL);
}