/*

Copyright (C) 2015-2018 Olaf Till <i7tiol@t-online.de>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; If not, see <http://www.gnu.org/licenses/>.

*/

#include "parallel-gnutls.h"

#if defined _WIN32 && ! defined __CYGWIN__

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

static inline
int mlock_wrapper (void *addr, size_t len)
{
  return ! VirtualLock (addr, len);
}

static inline
int munlock_wrapper (void *addr, size_t len)
{
  return ! VirtualUnlock (addr, len);
}

#else

#include <sys/mman.h>

static inline
int mlock_wrapper (const void *addr, size_t len)
{
  return mlock (addr, len);
}

static inline
int munlock_wrapper (const void *addr, size_t len)
{
  return munlock (addr, len);
}

#endif

void verifier::free_val (void)
{
  if (val.data)
    {
      zero_val ();

      if (locked_length)
        {
          if (munlock_wrapper (val.data, locked_length))
            fprintf (stderr, "Could not unlock memory pages!\n");
          else
            locked_length = 0;
        }

      free (val.data);

      val.data = NULL;
    }

  val.size = 0;

  valid = false;
}

void verifier::do_assign (const char *&username, const char *&passwd,
                  const gnutls_datum_t *&salt, bool lock)
{
  if (gnutls_srp_verifier (username, passwd, salt,
                           &gnutls_srp_2048_group_generator,
                           &gnutls_srp_2048_group_prime,
                           &val) == GNUTLS_E_SUCCESS &&
      val.data)
    {
      if (lock)
        {
          if (mlock_wrapper (val.data, val.size))
            _p_error ("could not lock memory pages");
          else
            {
              valid = true;

              locked_length = val.size;
            }
        }
      else
        valid = true;
    }
}

void sensitive_string::fill (int n, bool lock)
{
  valid = false;

  bool err = false;

  if (internally_allocated || locked_length)
    {
      _p_error ("internal error in filling");

      return;
    }

  val.data = new unsigned char[n + 1];

  if (val.data)
    {
      internally_allocated = true;

      val.size = n;

      if (lock)
        {
          if (mlock_wrapper (val.data, n))
            {
              _p_error ("could not lock memory pages");

              return;
            }
          else
            locked_length = n;
        }
    }
  else
    {
      val.size = 0;

      _p_error ("could not allocate memory");

      return;
    }

  int rfd;

  if ((rfd = open ("/dev/random", O_RDONLY)) == -1)
    {
      _p_error ("could not open /dev/random");

      return;
    }

  // gnutls nowadays checks if the password is valid UTF8, so many
  // bytes larger than 0x7F are invalid. Also, bytes < 0x20 are
  // probably invalid, even if not 0x00. We provide bytes with
  // probability evenly distributed between 0x20 and 0x7F (96 values).
  for (int i = 0; i < n; i++)
    {
      do
        {
          if (read (rfd, &(val.data[i]), 1) != 1)
            {
              _p_error ("could not read from /dev/random");

              err = true;

              break;
            }

          val.data[i] >>= 1;
        }
      while (val.data[i] < 0x20);

      if (err)
        break;
    }

  val.data[n] = '\0';

  if (close (rfd))
    {
      _p_error ("could not close /dev/random");

      err = true;
    }

  if (! err)
    valid = true;
}

void sensitive_string::assign (char *s, bool lock)
{
  if (valid || locked_length || internally_allocated)
    {
      valid = false;

      return;
    }

  val.data = (unsigned char *) s;

  val.size = strlen ((char *) val.data);

  valid = true;

  if (val.size != 0 && lock)
    {
      if (mlock_wrapper (val.data, val.size))
        {
          _p_error ("could not lock memory pages");

          valid = false;
        }
      else
        locked_length = val.size;
    }
}

void sensitive_string::free_val (void)
{
  if (val.data)
    {
      zero_val ();

      if (locked_length)
        {
          if (munlock_wrapper (val.data, locked_length))
            fprintf (stderr, "Could not unlock memory pages!\n");
          else
            locked_length = 0;
        }

      if (internally_allocated)
        {
          delete [] val.data;

          internally_allocated = false;
        }

      val.data = NULL;
    }
  val.size = 0;

  valid = false;
}

void srp_base64_encode::encode (const gnutls_datum_t *data, bool lock)
{
  if (gnutls_srp_base64_encode_alloc (data, &val))
    {
      _p_error ("base64 encoding failed");

      valid = false;
    }
  else
    {
      valid = true;

      if (lock)
        {
          if (mlock_wrapper (val.data, val.size))
            {
              _p_error ("could not lock memory pages");

              valid = false;
            }
          else
            locked_length = val.size;
        }
    }
}

void srp_base64_encode::free_val (void)
{
  if (val.data)
    {
      zero_val ();

      if (locked_length)
        {
          if (munlock_wrapper (val.data, locked_length))
            fprintf (stderr, "Could not unlock memory pages!\n");
          else
            locked_length = 0;
        }

      gnutls_free ((void *) val.data);

      val.data = NULL;
    }
  val.size = 0;
}
