/*
 * This file is part of GNUnet
 * Copyright (C) 2009-2014 GNUnet e.V.
 *
 * GNUnet 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, or (at your
 * option) any later version.
 *
 * GNUnet 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 GNUnet; see the file COPYING.  If not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */
/**
 * @file namestore/plugin_gtk_namestore_tlsa.c
 * @brief namestore plugin for editing TLSA records
 * @author Christian Grothoff
 *
 * Please note that the code of this plugin (and its XML) is
 * included in the BOX plugin (and Box XML) as well and thus
 * particular care needs to be taken when changes are made
 * to make sure names are consistent across the plugins.
 */
#include "gnunet_gtk.h"
#include "gnunet_gtk_namestore_plugin.h"
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/abstract.h>
#include <gnunet/gnunet_resolver_service.h>

/**
 * The user has edited the target value.  Enable/disable 'save'
 * button depending on the validity of the value.
 *
 * @param textbuffer changed text buffer
 * @param user_data the plugin environment
 */
static void
tlsa_value_textbuffer_changed_cb (GtkTextBuffer *textbuffer, gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  edc->check_validity (edc);
}


/**
 * The user has changed the selector.  Enable/disable 'save'
 * button depending on the validity of the value.
 *
 * @param togglebutton button that changed editing widget
 * @param user_data the plugin environment
 */
static void
edit_dialog_tlsa_selector_radiobutton_toggled_cb (GtkToggleButton *togglebutton,
                                                  gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  edc->check_validity (edc);
}


/**
 * The user has changed the usage.  Enable/disable 'save'
 * button depending on the validity of the value.
 *
 * @param togglebutton button that changed editing widget
 * @param user_data the plugin environment
 */
static void
edit_dialog_tlsa_usage_radiobutton_toggled_cb (GtkToggleButton *togglebutton,
                                               gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  edc->check_validity (edc);
}


/**
 * The user has changed the matching type.  Enable/disable 'save'
 * button depending on the validity of the value.
 *
 * @param togglebutton button that changed editing widget
 * @param user_data the plugin environment
 */
static void
edit_dialog_tlsa_matching_type_radiobutton_toggled_cb (
  GtkToggleButton *togglebutton,
  gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  edc->check_validity (edc);
}


#ifndef EDP_CBC_DEF
#define EDP_CBC_DEF
/**
 * The user has changed the protocol selection.  Enable/disable 'save'
 * button depending on the validity of the value.
 *
 * @param entry editing widget
 * @param user_data the plugin environment
 */
static void
edit_dialog_protocol_combobox_changed_cb (GtkEditable *entry,
                                          gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;

  edc->check_validity (edc);
}
#endif


/**
 * Return the selected button from a list of radio buttons.
 *
 * @param builder builder to resolve button names
 * @param button_names NULL-terminated array of buttons
 * @return index of the selected button, -1 for none
 */
static int
get_selected_radio_value (GtkBuilder *builder, const char *const *button_names)
{
  GtkToggleButton *b;

  for (int i = 0; NULL != button_names[i]; i++)
  {
    b = GTK_TOGGLE_BUTTON (gtk_builder_get_object (builder, button_names[i]));
    if (gtk_toggle_button_get_active (b))
      return i;
  }
  return -1;
}


/**
 * NULL-terminated array with the names of the "usage" buttons,
 * in order to match the respective value in TLSA.
 */
static const char *const usage_buttons[] =
  {"edit_dialog_tlsa_usage_ca_radiobutton",
   "edit_dialog_tlsa_usage_service_cert_radiobutton",
   "edit_dialog_tlsa_usage_trust_anchor_radiobutton",
   "edit_dialog_tlsa_usage_domain_issued_cert_radiobutton",
   NULL};


/**
 * NULL-terminated array with the names of the "selector" buttons,
 * in order to match the respective value in TLSA.
 */
static const char *const selector_buttons[] =
  {"edit_dialog_tlsa_selector_full_cert_radiobutton",
   "edit_dialog_tlsa_selector_subject_public_key_radiobutton",
   NULL};


/**
 * NULL-terminated array with the names of the "matching type" buttons,
 * in order to match the respective value in TLSA.
 */
static const char *const matching_type_buttons[] =
  {"edit_dialog_tlsa_matching_type_full_contents_radiobutton",
   "edit_dialog_tlsa_matching_type_sha256_radiobutton",
   "edit_dialog_tlsa_matching_type_sha512_radiobutton",
   NULL};


/**
 * Function that will be called to initialize the builder's
 * widgets from the existing record (if there is one).
 * The `n_value` is the existing value of the record as a string.
 *
 * @param cls the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment *`
 * @param n_value the record as a string
 * @param builder the edit dialog's builder
 */
static void
tlsa_load (void *cls, gchar *n_value, GtkBuilder *builder)
{
  unsigned int protocol;
  GtkComboBox *cb;
  GtkTreeIter iter;
  GtkTreeModel *tm;
  unsigned int service;
  guint protocol_at_iter;
  unsigned int record_type;
  unsigned int usage;
  unsigned int selector;
  unsigned int matching_type;
  GtkTextBuffer *tb;
  size_t slen = strlen (n_value) + 1;
  char cert_data[slen];

  if (7 != sscanf (n_value,
                   "%u %u %u %u %u %u %s",
                   &protocol,
                   &service,
                   &record_type,
                   &usage,
                   &selector,
                   &matching_type,
                   cert_data))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Unable to parse (boxed) TLSA record `%s'\n"),
                n_value);
    return;
  }
  if (GNUNET_DNSPARSER_TYPE_TLSA != record_type)
  {
    GNUNET_break (0);
    return;
  }
  gtk_spin_button_set_value (
    GTK_SPIN_BUTTON (
      gtk_builder_get_object (builder, "edit_dialog_port_spinbutton")),
    service);
  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (builder, "edit_dialog_protocol_combobox"));
  tm = GTK_TREE_MODEL (
    gtk_builder_get_object (builder, "edit_dialog_protocol_liststore"));
  if (gtk_tree_model_get_iter_first (tm, &iter))
  {
    do
    {
      gtk_tree_model_get (tm, &iter, 1, &protocol_at_iter, -1);
      if (protocol_at_iter == protocol)
      {
        gtk_combo_box_set_active_iter (cb, &iter);
        break;
      }
    } while (gtk_tree_model_iter_next (tm, &iter));
  }
  switch (usage)
  {
  case 0: /* CA cert */
    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (
                                    builder,
                                    "edit_dialog_tlsa_usage_ca_radiobutton")),
                                  TRUE);
    break;
  case 1: /* Entity cert */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (builder,
                                "edit_dialog_tlsa_usage_service_cert_radiobutton")),
      TRUE);
    break;
  case 2: /* Trust anchor */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (builder,
                                "edit_dialog_tlsa_usage_trust_anchor_radiobutton")),
      TRUE);
    break;
  case 3: /* Domain-issued cert */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (gtk_builder_get_object (
        builder,
        "edit_dialog_tlsa_usage_domain_issued_cert_radiobutton")),
      TRUE);
    break;
  default:
    GNUNET_break_op (0);
    break;
  }
  switch (selector)
  {
  case 0: /* full cert, binary */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (
        gtk_builder_get_object (builder,
                                "edit_dialog_tlsa_selector_full_cert_radiobutton")),
      TRUE);
    break;
  case 1: /* full cert, DER */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (gtk_builder_get_object (
        builder,
        "edit_dialog_tlsa_selector_subject_public_key_radiobutton")),
      TRUE);
    break;
  }
  switch (matching_type)
  {
  case 0: /* exact match */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (gtk_builder_get_object (
        builder,
        "edit_dialog_tlsa_matching_type_full_contents_radiobutton")),
      TRUE);
    break;
  case 1: /* SHA-256 hash */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (gtk_builder_get_object (
        builder,
        "edit_dialog_tlsa_matching_type_sha256_radiobutton")),
      TRUE);
    break;
  case 2: /* SHA-512 hash */
    gtk_toggle_button_set_active (
      GTK_TOGGLE_BUTTON (gtk_builder_get_object (
        builder,
        "edit_dialog_tlsa_matching_type_sha512_radiobutton")),
      TRUE);
    break;
  }
  tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (
    gtk_builder_get_object (builder, "edit_dialog_tlsa_value_textview")));
  gtk_text_buffer_set_text (tb, cert_data, -1);
}


/**
 * Function that will be called to retrieve the final value of the
 * record (in string format) once the dialog is being closed.
 *
 * @param cls the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment *`
 * @param builder the edit dialog's builder
 * @return record value as a string, as specified in the dialog
 */
static gchar *
tlsa_store (void *cls, GtkBuilder *builder)
{
  unsigned int protocol;
  GtkComboBox *cb;
  GtkTreeIter iter;
  guint service;
  unsigned int usage;
  unsigned int selector;
  unsigned int matching_type;
  GtkTextBuffer *tb;
  gchar *value;
  char *result;
  GtkTreeModel *tm;
  GtkTextIter ti_start;
  GtkTextIter ti_end;

  service = gtk_spin_button_get_value (GTK_SPIN_BUTTON (
    gtk_builder_get_object (builder, "edit_dialog_port_spinbutton")));
  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (builder, "edit_dialog_protocol_combobox"));
  if (! gtk_combo_box_get_active_iter (cb, &iter))
  {
    GNUNET_break (0);
    return NULL;
  }
  tm = GTK_TREE_MODEL (
    gtk_builder_get_object (builder, "edit_dialog_protocol_liststore"));
  gtk_tree_model_get (tm, &iter, 1, &protocol, -1);
  usage = get_selected_radio_value (builder, usage_buttons);
  selector = get_selected_radio_value (builder, selector_buttons);
  matching_type = get_selected_radio_value (builder, matching_type_buttons);
  if ((-1 == usage) || (-1 == selector) || (-1 == matching_type))
  {
    GNUNET_break (0);
    return NULL;
  }

  tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (
    gtk_builder_get_object (builder, "edit_dialog_tlsa_value_textview")));

  gtk_text_buffer_get_iter_at_offset (tb, &ti_start, 0);
  gtk_text_buffer_get_iter_at_offset (tb, &ti_end, -1);

  value = gtk_text_buffer_get_text (tb, &ti_start, &ti_end, FALSE);
  GNUNET_asprintf (&result,
                   "%u %u %u %u %u %u %s",
                   protocol,
                   (unsigned int) service,
                   GNUNET_DNSPARSER_TYPE_TLSA,
                   usage,
                   selector,
                   matching_type,
                   value);
  g_free (value);
  return result;
}


/**
 * Function to call to validate the state of the dialog.  Should
 * return #GNUNET_OK if the information in the dialog is valid, and
 * #GNUNET_SYSERR if some fields contain invalid values.  The
 * function should highlight fields with invalid inputs for the
 * user.
 *
 * @param cls the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment *`
 * @param builder the edit dialog's builder
 * @return #GNUNET_OK if there is a valid record value in the dialog
 */
static int
tlsa_validate (void *cls, GtkBuilder *builder)
{
  GtkComboBox *cb;
  GtkTreeIter iter;
  gchar *value;
  GtkTextBuffer *tb;
  GtkTextIter ti_start;
  GtkTextIter ti_end;
  gnutls_datum_t datum;
  gnutls_x509_crt_t cert;
  gnutls_pubkey_t pk;
  int ret;
  unsigned int matching_type;
  unsigned int selector;
  int err;

  cb = GTK_COMBO_BOX (
    gtk_builder_get_object (builder, "edit_dialog_protocol_combobox"));
  if (! gtk_combo_box_get_active_iter (cb, &iter))
  {
    return GNUNET_SYSERR;
  }

  tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (
    gtk_builder_get_object (builder, "edit_dialog_tlsa_value_textview")));
  gtk_text_buffer_get_iter_at_offset (tb, &ti_start, 0);
  gtk_text_buffer_get_iter_at_offset (tb, &ti_end, -1);
  value = gtk_text_buffer_get_text (tb, &ti_start, &ti_end, FALSE);
  if (0 == strlen (value))
    return GNUNET_SYSERR;
  {
    size_t slen = strlen (value);
    uint8_t bin[slen / 2];

    if (slen / 2 != GNUNET_DNSPARSER_hex_to_bin (value, bin))
    {
      /* not hex */
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  _ ("Certificate value is not in hex...\n"));
      return GNUNET_SYSERR;
    }
    matching_type = get_selected_radio_value (builder, matching_type_buttons);
    selector = get_selected_radio_value (builder, selector_buttons);
    switch (matching_type)
    {
    case 0: /* exact match */
      datum.size = sizeof (bin);
      datum.data = bin;
      switch (selector)
      {
      case 0: /* full Cert */
        if (GNUTLS_E_SUCCESS != (err = gnutls_x509_crt_init (&cert)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      _ ("Failed to initialize CERT: %s\n"),
                      gnutls_strerror_name (err));
          ret = GNUNET_SYSERR;
          break;
        }
        if (GNUTLS_E_SUCCESS !=
            (err = gnutls_x509_crt_import (cert, &datum, GNUTLS_X509_FMT_DER)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      _ ("Failed to parse CERT: %s\n"),
                      gnutls_strerror_name (err));
          ret = GNUNET_SYSERR;
        }
        else
          ret = GNUNET_OK;
        gnutls_x509_crt_deinit (cert);
        break;
      case 1: /* subject public key only */
        if (GNUTLS_E_SUCCESS != (err = gnutls_pubkey_init (&pk)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      _ ("Failed to initialize PK: %s\n"),
                      gnutls_strerror_name (err));
          ret = GNUNET_SYSERR;
          break;
        }
        if (GNUTLS_E_SUCCESS !=
            (err = gnutls_pubkey_import (pk, &datum, GNUTLS_X509_FMT_DER)))
        {
          GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                      _ ("Failed to parse PK: %s\n"),
                      gnutls_strerror_name (err));
          ret = GNUNET_SYSERR;
        }
        else
          ret = GNUNET_OK;
        gnutls_pubkey_deinit (pk);
        break;
      default:
        GNUNET_break (0);
        ret = GNUNET_SYSERR;
        break;
      }
      break;
    case 1: /* SHA-256 hash */
      ret = (256 / 8 == slen / 2) ? GNUNET_OK : GNUNET_SYSERR;
      break;
    case 2: /* SHA-512 hash */
      ret = (512 / 8 == slen / 2) ? GNUNET_OK : GNUNET_SYSERR;
      break;
    default:
      GNUNET_break (0);
      ret = GNUNET_SYSERR;
      break;
    }
  }
  return ret;
}


/**
 * Context for TLS certificate import from network.
 */
struct ImportContext
{

  /**
   * Network handle for the session.
   */
  struct GNUNET_NETWORK_Handle *sock;

  /**
   * DNS resolution request to resolve the domain name.
   */
  struct GNUNET_RESOLVER_RequestHandle *rh;

  /**
   * Builder for accessing widgets.
   */
  GtkBuilder *builder;

  /**
   * Domain name of the site we use to get the TLS cert record from.
   */
  char *name;

  /**
   * We succeeded with our TLS handshake, ignore further DNS replies.
   */
  int done;
};


/**
 * We have successfully established a TLS session to
 * import a certificate from the server.  Import the
 * X509 certificate into the GUI.
 *
 * @param session TLS session to import from
 * @param ic the import context
 */
static void
import_x509_certificate (gnutls_session_t session, struct ImportContext *ic)
{
  GtkBuilder *builder = ic->builder;
  const gnutls_datum_t *cert_list;
  unsigned int cert_list_size = 0;
  gnutls_x509_crt_t cert;
  unsigned int usage;
  unsigned int matching_type;
  unsigned int selector;
  gnutls_pubkey_t pk;
  char buf[4092];
  size_t bsize;
  char *hex;
  gnutls_datum_t datum;
  uint8_t sha256[256 / 8];
  uint8_t sha512[512 / 8];
  size_t ssize;
  GtkTextBuffer *tb;
  unsigned int i;

  cert_list = gnutls_certificate_get_peers (session, &cert_list_size);
  if (0 == cert_list_size)
  {
    /* is it possible to succeed with TLS handshake and have
       NO certificates!? If so, how do we get the public key?*/
    GNUNET_break (0);
    return;
  }

  usage = get_selected_radio_value (builder, usage_buttons);
  /* Find out which certificate we care about based on usage */
  for (i = 0; i < cert_list_size; i++)
  {
    char san[256];
    size_t san_size = sizeof (san);
    unsigned int critical;
    int matches_dn;
    gnutls_x509_dn_t dn;
    gnutls_datum_t str;

    gnutls_x509_crt_init (&cert);
    if (GNUTLS_E_SUCCESS !=
        gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER))
    {
      GNUNET_break (0);
      gnutls_x509_crt_deinit (cert);
      return;
    }
    if (1 == usage)
      break; /* RFC 6394: first certificate (i==0) is to be pinned */
    matches_dn = GNUNET_NO;
    if ((0 == gnutls_x509_crt_get_subject (cert, &dn)) &&
        (0 == gnutls_x509_dn_get_str (dn, &str)))
    {
      const char *cn;

      cn = memmem (str.data, str.size, ",CN=", 4);
      if ((NULL != cn) &&
          (('\0' == cn[4 + strlen (ic->name)]) ||
           (',' == cn[4 + strlen (ic->name)])) &&
          (0 == strncasecmp (cn + 4, ic->name, strlen (ic->name))))
        matches_dn = GNUNET_YES;
    }
    for (unsigned int seq = 0; GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE !=
                               gnutls_x509_crt_get_subject_alt_name (cert,
                                                                     seq,
                                                                     san,
                                                                     &san_size,
                                                                     &critical);
         seq++)
    {
      if (0 == strcasecmp (san, ic->name))
      {
        matches_dn = GNUNET_YES;
        break;
      }
    }
    /* usage = 3: we want to match DN, otherwise we do NOT want to
       match DN */
    if ((GNUNET_NO == matches_dn) ^ /* XOR */
        (3 == usage) /* Domain issued certificate */)
      break;
    gnutls_x509_crt_deinit (cert);
  }
  if (i == cert_list_size)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "None of the %u certificates matches the usage %u\n",
                cert_list_size,
                usage);
    return;
  }

  selector = get_selected_radio_value (builder, selector_buttons);
  switch (selector)
  {
  case 0: /* full cert */
    bsize = sizeof (buf);
    if (GNUTLS_E_SUCCESS !=
        gnutls_x509_crt_export (cert, GNUTLS_X509_FMT_DER, buf, &bsize))
    {
      GNUNET_break (0);
      gnutls_x509_crt_deinit (cert);
      return;
    }
    break;
  case 1: /* subject public key only */
    if (GNUTLS_E_SUCCESS != gnutls_pubkey_init (&pk))
    {
      GNUNET_break (0);
      gnutls_x509_crt_deinit (cert);
      return;
    }
    if (GNUTLS_E_SUCCESS != gnutls_pubkey_import_x509 (pk, cert, 0))
    {
      GNUNET_break (0);
      gnutls_x509_crt_deinit (cert);
      gnutls_pubkey_deinit (pk);
      return;
    }
    bsize = sizeof (buf);
    if (GNUTLS_E_SUCCESS !=
        gnutls_pubkey_export (pk, GNUTLS_X509_FMT_DER, buf, &bsize))
    {
      GNUNET_break (0);
      gnutls_x509_crt_deinit (cert);
      gnutls_pubkey_deinit (pk);
      return;
    }
    gnutls_pubkey_deinit (pk);
    break;
  default:
    GNUNET_break (0);
    gnutls_x509_crt_deinit (cert);
    return;
  }
  gnutls_x509_crt_deinit (cert);
  /* 'buf' now contains 'bsize' bytes of the binary data to
     hash or store in the TLSA record; hash depending on
     user preferences. */
  matching_type = get_selected_radio_value (builder, matching_type_buttons);
  switch (matching_type)
  {
  case 0: /* exact match */
    hex = GNUNET_DNSPARSER_bin_to_hex (buf, bsize);
    break;
  case 1: /* SHA-256 hash */
    datum.size = bsize;
    datum.data = (void *) buf;
    ssize = sizeof (sha256);
    GNUNET_assert (
      GNUTLS_E_SUCCESS ==
      gnutls_fingerprint (GNUTLS_MAC_SHA256, &datum, sha256, &ssize));
    hex = GNUNET_DNSPARSER_bin_to_hex (sha256, sizeof (sha256));
    break;
  case 2: /* SHA-512 hash */
    datum.size = bsize;
    datum.data = (void *) buf;
    ssize = sizeof (sha512);
    GNUNET_assert (
      GNUTLS_E_SUCCESS ==
      gnutls_fingerprint (GNUTLS_MAC_SHA512, &datum, sha512, &ssize));
    hex = GNUNET_DNSPARSER_bin_to_hex (sha512, sizeof (sha512));
    break;
  default:
    GNUNET_break (0);
    return;
  }

  /* Finally store 'hex' to the text buffer */
  tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (
    gtk_builder_get_object (builder, "edit_dialog_tlsa_value_textview")));
  gtk_text_buffer_set_text (tb, hex, -1);
  GNUNET_free (hex);
}


/**
 * We got an address from DNS, start TLS handshake.
 *
 * @param cls our `struct ImportContext`
 * @param addr one of the addresses of the host, NULL for the last address
 * @param addrlen length of @a addr
 */
static void
import_address_cb (void *cls, const struct sockaddr *addr, socklen_t addrlen)
{
  struct ImportContext *ic = cls;
  int pf;
  int ret;
  gnutls_certificate_credentials_t xcred;
  struct sockaddr_in v4;
  struct sockaddr_in6 v6;
  struct sockaddr *a;
  unsigned int port;
  gnutls_certificate_type_t type;
  gnutls_session_t session;

  if (GNUNET_YES == ic->done)
    return;
  if (NULL == addr)
  {
    if (GNUNET_YES != ic->done)
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  _ ("Name resolution for `%s' failed\n"),
                  ic->name);
    GNUNET_free (ic->name);
    GNUNET_free (ic);
    return;
  }
  port = gtk_spin_button_get_value (GTK_SPIN_BUTTON (
    gtk_builder_get_object (ic->builder, "edit_dialog_port_spinbutton")));
  switch (addr->sa_family)
  {
  case AF_INET:
    pf = PF_INET;
    memcpy (&v4, addr, addrlen);
    v4.sin_port = htons ((uint16_t) port);
    a = (struct sockaddr *) &v4;
    break;
  case AF_INET6:
    pf = PF_INET6;
    memcpy (&v6, addr, addrlen);
    v6.sin6_port = htons ((uint16_t) port);
    a = (struct sockaddr *) &v6;
    break;
  default:
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Unsupported address family %d\n"),
                addr->sa_family);
    return;
  }
  ic->sock = GNUNET_NETWORK_socket_create (pf, SOCK_STREAM, 0);
  if (NULL == ic->sock)
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "socket");
    return;
  }
  GNUNET_break (GNUNET_OK ==
                GNUNET_NETWORK_socket_set_blocking (ic->sock, GNUNET_YES));
  if ((GNUNET_OK != GNUNET_NETWORK_socket_connect (ic->sock, a, addrlen)) &&
      (EINPROGRESS != errno))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                _ ("Failed to connect to target address `%s': %s\n"),
                GNUNET_a2s (addr, addrlen),
                strerror (errno));
    GNUNET_NETWORK_socket_close (ic->sock);
    return;
  }

  /* initialize TLS session */
  gnutls_init (&session, GNUTLS_CLIENT);
  gnutls_session_set_ptr (session, ic);
  gnutls_server_name_set (session,
                          GNUTLS_NAME_DNS,
                          ic->name,
                          strlen (ic->name));
  gnutls_set_default_priority (session);

  /* Use default priorities */
  gnutls_certificate_allocate_credentials (&xcred);
  if (GNUTLS_E_SUCCESS !=
      (ret = gnutls_priority_set_direct (session, "PERFORMANCE", NULL)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("Failed to initialize cipher suite: %s\n"),
                gnutls_strerror (ret));
    goto cleanup;
  }
  /* put the x509 credentials to the current session */
  gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, xcred);
  gnutls_transport_set_int (session, GNUNET_NETWORK_get_fd (ic->sock));
  gnutls_handshake_set_timeout (session, 2000 /* 2s */);

  /* TODO: do this in event loop, with insensitive GUI,
     with possibly higher timeout ... */
  /* Perform the TLS handshake */
  do
  {
    ret = gnutls_handshake (session);
  } while ((ret < 0) && (0 == gnutls_error_is_fatal (ret)));

  /* finally, access the certificate */
  if (GNUTLS_E_SUCCESS == ret)
  {
    type = gnutls_certificate_type_get (session);
    switch (type)
    {
    case GNUTLS_CRT_UNKNOWN:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  _ ("Server certificate type not supported\n"));
      break;
    case GNUTLS_CRT_X509:
      import_x509_certificate (session, ic);
      break;
    default:
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  _ ("Server certificate type not supported\n"));
      break;
    }
  }
  else
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                _ ("TLS handshake failed: %s\n"),
                gnutls_strerror (ret));
  }
  gnutls_bye (session, GNUTLS_SHUT_RDWR);
  ic->done = GNUNET_YES;

cleanup:
  GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (ic->sock));
  gnutls_deinit (session);
  gnutls_certificate_free_credentials (xcred);
}


/**
 * The user clicked the "import" button.  Try to import
 * certificate from the server.
 *
 * @param button the 'import' button
 * @param user_data the plugin environment
 */
static void
tlsa_import_button_clicked_cb (GtkButton *button, gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;
  struct ImportContext *ic;
  const gchar *name;
  GtkWidget *entry;

  entry = GTK_WIDGET (
    gtk_builder_get_object (edc->builder, "edit_dialog_tlsa_import_entry"));
  name = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
  if ((NULL == name) || (0 == strlen (name)) ||
      (GNUNET_OK != GNUNET_DNSPARSER_check_name (name)))
  {
    /* import button should not have been sensitive */
    GNUNET_break (0);
    return;
  }
  ic = GNUNET_new (struct ImportContext);
  ic->builder = edc->builder;
  ic->name = GNUNET_strdup (name);
  ic->rh = GNUNET_RESOLVER_ip_get (name,
                                   AF_UNSPEC,
                                   GNUNET_TIME_UNIT_SECONDS,
                                   &import_address_cb,
                                   ic);
}


/**
 * The user has edited the hostname used for the import button.
 * Update the import button's sensitivity.
 *
 * @param entry edited entry
 * @param user_data our plugin environment
 */
static void
edit_dialog_tlsa_import_entry_changed_cb (GtkEditable *entry,
                                          gpointer user_data)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data;
  const gchar *preedit;
  gboolean sens;
  GtkWidget *button;

  button = GTK_WIDGET (
    gtk_builder_get_object (edc->builder, "edit_dialog_tlsa_import_button"));
  preedit = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1);
  if ((NULL == preedit) || (0 == strlen (preedit)) ||
      (GNUNET_OK != GNUNET_DNSPARSER_check_name (preedit)))
    sens = FALSE;
  else
    sens = TRUE;
  gtk_widget_set_sensitive (button, sens);
}


/**
 * Entry point for the plugin.
 *
 * @param cls the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment`
 * @return NULL on error, otherwise the plugin context
 */
void *
libgnunet_plugin_gtk_namestore_tlsa_init (void *cls)
{
  struct GNUNET_GTK_NAMESTORE_PluginEnvironment *env = cls;
  struct GNUNET_GTK_NAMESTORE_PluginFunctions *plugin;
  static struct GNUNET_GTK_NAMESTORE_Symbol symbols[] =
    {{"tlsa_value_textbuffer_changed_cb",
      G_CALLBACK (tlsa_value_textbuffer_changed_cb)},
     {"edit_dialog_tlsa_selector_radiobutton_toggled_cb",
      G_CALLBACK (edit_dialog_tlsa_selector_radiobutton_toggled_cb)},
     {"edit_dialog_tlsa_usage_radiobutton_toggled_cb",
      G_CALLBACK (edit_dialog_tlsa_usage_radiobutton_toggled_cb)},
     {"edit_dialog_tlsa_matching_type_radiobutton_toggled_cb",
      G_CALLBACK (edit_dialog_tlsa_matching_type_radiobutton_toggled_cb)},
     {"tlsa_import_button_clicked_cb",
      G_CALLBACK (tlsa_import_button_clicked_cb)},
     {"edit_dialog_tlsa_import_entry_changed_cb",
      G_CALLBACK (edit_dialog_tlsa_import_entry_changed_cb)},
     /* generic CBs */
     {"edit_dialog_protocol_combobox_changed_cb",
      G_CALLBACK (edit_dialog_protocol_combobox_changed_cb)},
     {NULL, NULL}};

  gnutls_global_init ();
  plugin = GNUNET_new (struct GNUNET_GTK_NAMESTORE_PluginFunctions);
  plugin->cls = env;
  plugin->dialog_glade_filename = "gnunet_namestore_edit_tlsa.glade";
  plugin->dialog_widget_name = "edit_tlsa_dialog";
  plugin->symbols = symbols;
  plugin->load = &tlsa_load;
  plugin->store = &tlsa_store;
  plugin->validate = &tlsa_validate;
  /* we will not produce a 'native' TLSA record, but one in a BOX */
  plugin->record_type = GNUNET_GNSRECORD_TYPE_BOX;
  return plugin;
}


/**
 * Exit point from the plugin.
 *
 * @param cls the plugin context (as returned by "init")
 * @return always NULL
 */
void *
libgnunet_plugin_gtk_namestore_tlsa_done (void *cls)
{
  struct GNUNET_GTK_NAMESTORE_PluginFunctions *plugin = cls;

  GNUNET_free (plugin);
  gnutls_global_deinit ();
  return NULL;
}

/* end of plugin_gtk_namestore_tlsa.c */
