static void
infinoted_plugin_certificate_auth_certificate_func(InfXmppConnection* xmpp,
                                                   gnutls_session_t session,
                                                   InfCertificateChain* chain,
                                                   gpointer user_data)
{
  InfinotedPluginCertificateAuth* plugin;
  int res;
  int verify_result;
  GError* error;

  plugin = (InfinotedPluginCertificateAuth*)user_data;

  if(chain != NULL)
  {
    /* Note that we don't require client certificates to be signed by a CA.
     * We only require them to be signed by one of the certificates in our
     * list, but we don't care whether that's a CA or not. A common use case
     * is to sign client certificates with our own server certificate. */
    res = gnutls_x509_crt_list_verify(
      inf_certificate_chain_get_raw(chain),
      inf_certificate_chain_get_n_certificates(chain),
      plugin->cas,
      plugin->n_cas,
      NULL,
      0,
      GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT | GNUTLS_VERIFY_DISABLE_CA_SIGN,
      &verify_result
    );

    error = NULL;

    if(res != GNUTLS_E_SUCCESS)
      inf_gnutls_set_error(&error, res);
    else if( (verify_result & GNUTLS_CERT_INVALID) != 0)
      inf_gnutls_certificate_verification_set_error(&error, res);

    if(error != NULL)
    {
      inf_xmpp_connection_certificate_verify_cancel(xmpp, error);
      g_error_free(error);
    }
    else
    {
      inf_xmpp_connection_certificate_verify_continue(xmpp);
    }
  }
  else
  {
    /* If we do not accept unauthenticated clients, then GnuTLS should have
     * blocked the connection already, since we set the certificate request
     * to GNUTLS_CERT_REQUIRE in that case. */
    g_assert(plugin->accept_unauthenticated_clients == TRUE);
    inf_xmpp_connection_certificate_verify_continue(xmpp);
  }
}
static gboolean
inf_gtk_certificate_manager_compare_fingerprint(gnutls_x509_crt_t cert1,
                                                gnutls_x509_crt_t cert2,
                                                GError** error)
{
  static const unsigned int SHA256_DIGEST_SIZE = 32;

  size_t size;
  guchar cert1_fingerprint[SHA256_DIGEST_SIZE];
  guchar cert2_fingerprint[SHA256_DIGEST_SIZE];

  int ret;
  int cmp;

  size = SHA256_DIGEST_SIZE;

  ret = gnutls_x509_crt_get_fingerprint(
    cert1,
    GNUTLS_DIG_SHA256,
    cert1_fingerprint,
    &size
  );

  if(ret == GNUTLS_E_SUCCESS)
  {
    g_assert(size == SHA256_DIGEST_SIZE);

    ret = gnutls_x509_crt_get_fingerprint(
      cert2,
      GNUTLS_DIG_SHA256,
      cert2_fingerprint,
      &size
    );
  }

  if(ret != GNUTLS_E_SUCCESS)
  {
    inf_gnutls_set_error(error, ret);
    return FALSE;
  }

  cmp = memcmp(cert1_fingerprint, cert2_fingerprint, SHA256_DIGEST_SIZE);
  if(cmp != 0) return FALSE;

  return TRUE;
}
static InfdXmppServer*
inf_test_certificate_setup_server(InfIo* io,
                                  const char* key_file,
                                  const char* cert_file,
                                  GError** error)
{
  InfdTcpServer* tcp;
  InfdXmppServer* xmpp;

  gnutls_x509_privkey_t key;
  GPtrArray* certs;
  InfCertificateCredentials* creds;
  guint i;
  int res;

  key = inf_cert_util_read_private_key(key_file, error);
  if(!key) return NULL;

  certs = inf_cert_util_read_certificate(cert_file, NULL, error);
  if(!certs)
  {
    gnutls_x509_privkey_deinit(key);
    return NULL;
  }

  creds = inf_certificate_credentials_new();
  res = gnutls_certificate_set_x509_key(
    inf_certificate_credentials_get(creds),
    (gnutls_x509_crt_t*)certs->pdata,
    certs->len,
    key
  );

  gnutls_x509_privkey_deinit(key);
  for(i = 0; i < certs->len; ++i)
    gnutls_x509_crt_deinit(certs->pdata[i]);
  g_ptr_array_free(certs, TRUE);

  if(res != 0)
  {
    inf_certificate_credentials_unref(creds);
    inf_gnutls_set_error(error, res);
    return NULL;
  }

  tcp = g_object_new(
    INFD_TYPE_TCP_SERVER,
    "io", io,
    "local-port", 6524,
    NULL
  );

  if(infd_tcp_server_open(tcp, error) == FALSE)
  {
    inf_certificate_credentials_unref(creds);
    return NULL;
  }

  xmpp = infd_xmpp_server_new(
    tcp,
    INF_XMPP_CONNECTION_SECURITY_ONLY_TLS,
    creds,
    NULL,
    NULL
  );

  /* Keep client connections alive */
  g_signal_connect(
    G_OBJECT(xmpp),
    "new-connection",
    G_CALLBACK(inf_test_certificate_validate_new_connection_cb),
    NULL
  );

  inf_certificate_credentials_unref(creds);
  return xmpp;
}
static void
inf_gtk_certificate_manager_certificate_func(InfXmppConnection* connection,
                                             gnutls_session_t session,
                                             InfCertificateChain* chain,
                                             gpointer user_data)
{
  InfGtkCertificateManager* manager;
  InfGtkCertificateManagerPrivate* priv;

  InfGtkCertificateDialogFlags flags;
  gnutls_x509_crt_t presented_cert;
  gnutls_x509_crt_t known_cert;
  gchar* hostname;

  gboolean match_hostname;
  gboolean issuer_known;
  gnutls_x509_crt_t root_cert;

  int ret;
  unsigned int verify;
  GHashTable* table;
  gboolean cert_equal;
  time_t expiration_time;

  InfGtkCertificateManagerQuery* query;
  gchar* text;
  GtkWidget* vbox;
  GtkWidget* button;
  GtkWidget* image;
  GtkWidget* label;

  GError* error;

  manager = INF_GTK_CERTIFICATE_MANAGER(user_data);
  priv = INF_GTK_CERTIFICATE_MANAGER_PRIVATE(manager);

  g_object_get(G_OBJECT(connection), "remote-hostname", &hostname, NULL);
  presented_cert = inf_certificate_chain_get_own_certificate(chain);

  match_hostname = gnutls_x509_crt_check_hostname(presented_cert, hostname);

  /* First, validate the certificate */
  ret = gnutls_certificate_verify_peers2(session, &verify);
  error = NULL;

  if(ret != GNUTLS_E_SUCCESS)
    inf_gnutls_set_error(&error, ret);

  /* Remove the GNUTLS_CERT_ISSUER_NOT_KNOWN flag from the verification
   * result, and if the certificate is still invalid, then set an error. */
  if(error == NULL)
  {
    issuer_known = TRUE;
    if(verify & GNUTLS_CERT_SIGNER_NOT_FOUND)
    {
      issuer_known = FALSE;

      /* Re-validate the certificate for other failure reasons --
       * unfortunately the gnutls_certificate_verify_peers2() call
       * does not tell us whether the certificate is otherwise invalid
       * if a signer is not found already. */
      /* TODO: Here it would be good to use the verify flags from the
       * certificate credentials, but GnuTLS does not have API to
       * retrieve them. */
      root_cert = inf_certificate_chain_get_root_certificate(chain);

      ret = gnutls_x509_crt_list_verify(
        inf_certificate_chain_get_raw(chain),
        inf_certificate_chain_get_n_certificates(chain),
        &root_cert,
        1,
        NULL,
        0,
        GNUTLS_VERIFY_ALLOW_X509_V1_CA_CRT,
        &verify
      );

      if(ret != GNUTLS_E_SUCCESS)
        inf_gnutls_set_error(&error, ret);
      else if(verify & GNUTLS_CERT_INVALID)
        inf_gnutls_certificate_verification_set_error(&error, verify);
    }
  }

  /* Look up the host in our database of pinned certificates if we could not
   * fully verify the certificate, i.e. if either the issuer is not known or
   * the hostname of the connection does not match the certificate. */
  table = NULL;
  if(error == NULL)
  {
    known_cert = NULL;
    if(!match_hostname || !issuer_known)
    {
      /* If we cannot load the known host file, then cancel the connection.
       * Otherwise it might happen that someone shows us a certificate that we
       * tell the user we don't know, if though actually for that host we expect
       * a different certificate. */
      table = inf_gtk_certificate_manager_ref_known_hosts(manager, &error);
      if(table != NULL)
        known_cert = g_hash_table_lookup(table, hostname);
    }
  }

  /* Next, configure the flags for the dialog to be shown based on the
   * verification result, and on whether the pinned certificate matches
   * the one presented by the host or not. */
  flags = 0;
  if(error == NULL)
  {
    if(known_cert != NULL)
    {
      cert_equal = inf_gtk_certificate_manager_compare_fingerprint(
        known_cert,
        presented_cert,
        &error
      );

      if(error == NULL && cert_equal == FALSE)
      {
        if(!match_hostname)
          flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_HOSTNAME_MISMATCH;
        if(!issuer_known)
          flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_ISSUER_NOT_KNOWN;

        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_UNEXPECTED;
        expiration_time = gnutls_x509_crt_get_expiration_time(known_cert);
        if(expiration_time != (time_t)(-1))
        {
          expiration_time -= INF_GTK_CERTIFICATE_MANAGER_EXPIRATION_TOLERANCE;
          if(time(NULL) > expiration_time)
          {
            flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_OLD_EXPIRED;
          }
        }
      }
    }
    else
    {
      if(!match_hostname)
        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_HOSTNAME_MISMATCH;
      if(!issuer_known)
        flags |= INF_GTK_CERTIFICATE_DIALOG_CERT_ISSUER_NOT_KNOWN;
    }
  }

  /* Now proceed either by accepting the connection, rejecting it, or
   * bothering the user with an annoying dialog. */
  if(error == NULL)
  {
    if(flags == 0)
    {
      if(match_hostname && issuer_known)
      {
        /* Remove the pinned entry if we now have a valid certificate for
         * this host. */
        if(table != NULL && g_hash_table_remove(table, hostname) == TRUE)
        {
          inf_gtk_certificate_manager_write_known_hosts_with_warning(
            manager,
            table
          );
        }
      }

      inf_xmpp_connection_certificate_verify_continue(connection);
    }
    else
    {
      query = g_slice_new(InfGtkCertificateManagerQuery);
      query->manager = manager;
      query->known_hosts = table;
      query->connection = connection;
      query->dialog = inf_gtk_certificate_dialog_new(
        priv->parent_window,
        0,
        flags,
        hostname,
        chain
      );
      query->certificate_chain = chain;

      table = NULL;

      g_object_ref(query->connection);
      inf_certificate_chain_ref(chain);

      g_signal_connect(
        G_OBJECT(connection),
        "notify::status",
        G_CALLBACK(inf_gtk_certificate_manager_notify_status_cb),
        query
      );

      g_signal_connect(
        G_OBJECT(query->dialog),
        "response",
        G_CALLBACK(inf_gtk_certificate_manager_response_cb),
        query
      );

      image = gtk_image_new_from_stock(GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON);
      gtk_widget_show(image);

      button = gtk_dialog_add_button(
        GTK_DIALOG(query->dialog),
        _("_Cancel connection"),
        GTK_RESPONSE_REJECT
      );

      gtk_button_set_image(GTK_BUTTON(button), image);

      image = gtk_image_new_from_stock(GTK_STOCK_CONNECT, GTK_ICON_SIZE_BUTTON);
      gtk_widget_show(image);

      button = gtk_dialog_add_button(
        GTK_DIALOG(query->dialog),
        _("C_ontinue connection"),
        GTK_RESPONSE_ACCEPT
      );

      gtk_button_set_image(GTK_BUTTON(button), image);

      text = g_strdup_printf(
        _("Do you want to continue the connection to host \"%s\"? If you "
          "choose to continue, this certificate will be trusted in the "
          "future when connecting to this host."),
        hostname
      );

      label = gtk_label_new(text);
      gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
      gtk_label_set_line_wrap_mode(GTK_LABEL(label), PANGO_WRAP_WORD_CHAR);
      gtk_label_set_width_chars(GTK_LABEL(label), 60);
      gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
      gtk_widget_show(label);
      g_free(text);

      vbox = gtk_dialog_get_content_area(GTK_DIALOG(query->dialog));
      gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);

      priv->queries = g_slist_prepend(priv->queries, query);
      gtk_window_present(GTK_WINDOW(query->dialog));
    }
  }
  else
  {
    inf_xmpp_connection_certificate_verify_cancel(connection, error);
    g_error_free(error);
  }

  if(table != NULL) g_hash_table_unref(table);
  g_free(hostname);
}
static gboolean
inf_gtk_certificate_manager_write_known_hosts(InfGtkCertificateManager* mgr,
                                              GHashTable* table,
                                              GError** error)
{
  InfGtkCertificateManagerPrivate* priv;
  gchar* dirname;
  GIOChannel* channel;
  GIOStatus status;

  GHashTableIter iter;
  gpointer key;
  gpointer value;
  const gchar* hostname;
  gnutls_x509_crt_t cert;

  size_t size;
  int res;
  gchar* buffer;
  gchar* encoded_cert;

  priv = INF_GTK_CERTIFICATE_MANAGER_PRIVATE(mgr);
  
  /* Note that we pin the whole certificate and not only the public key of
   * our known hosts. This allows us to differentiate two cases when a
   * host presents a new certificate:
   *    a) The old certificate has expired or is very close to expiration. In
   *       this case we still show a message to the user asking whether they
   *       trust the new certificate.
   *    b) The old certificate was perfectly valid. In this case we show a
   *       message saying that the certificate change was unexpected, and
   *       unless it was expected the host should not be trusted.
   */
  dirname = g_path_get_dirname(priv->known_hosts_file);
  if(!inf_file_util_create_directory(dirname, 0755, error))
  {
    g_free(dirname);
    return FALSE;
  }

  g_free(dirname);

  channel = g_io_channel_new_file(priv->known_hosts_file, "w", error);
  if(channel == NULL) return FALSE;

  status = g_io_channel_set_encoding(channel, NULL, error);
  if(status != G_IO_STATUS_NORMAL)
  {
    g_io_channel_unref(channel);
    return FALSE;
  }

  g_hash_table_iter_init(&iter, table);
  while(g_hash_table_iter_next(&iter, &key, &value))
  {
    hostname = (const gchar*)key;
    cert = (gnutls_x509_crt_t)value;

    size = 0;
    res = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, NULL, &size);
    g_assert(res != GNUTLS_E_SUCCESS);

    buffer = NULL;
    if(res == GNUTLS_E_SHORT_MEMORY_BUFFER)
    {
      buffer = g_malloc(size);
      res = gnutls_x509_crt_export(cert, GNUTLS_X509_FMT_DER, buffer, &size);
    }

    if(res != GNUTLS_E_SUCCESS)
    {
      g_free(buffer);
      g_io_channel_unref(channel);
      inf_gnutls_set_error(error, res);
      return FALSE;
    }

    encoded_cert = g_base64_encode(buffer, size);
    g_free(buffer);

    status = g_io_channel_write_chars(channel, hostname, strlen(hostname), NULL, error);
    if(status == G_IO_STATUS_NORMAL)
      status = g_io_channel_write_chars(channel, ":", 1, NULL, error);
    if(status == G_IO_STATUS_NORMAL)
      status = g_io_channel_write_chars(channel, encoded_cert, strlen(encoded_cert), NULL, error);
    if(status == G_IO_STATUS_NORMAL)
      status = g_io_channel_write_chars(channel, "\n", 1, NULL, error);

    g_free(encoded_cert);

    if(status != G_IO_STATUS_NORMAL)
    {
      g_io_channel_unref(channel);
      return FALSE;
    }
  }

  g_io_channel_unref(channel);
  return TRUE;
}
static GHashTable*
inf_gtk_certificate_manager_load_known_hosts(InfGtkCertificateManager* mgr,
                                             GError** error)
{
  InfGtkCertificateManagerPrivate* priv;
  GHashTable* table;
  gchar* content;
  gsize size;
  GError* local_error;

  gchar* out_buf;
  gsize out_buf_len;
  gchar* pos;
  gchar* prev;
  gchar* next;
  gchar* sep;

  gsize len;
  gsize out_len;
  gint base64_state;
  guint base64_save;

  gnutls_datum_t data;
  gnutls_x509_crt_t cert;
  int res;

  priv = INF_GTK_CERTIFICATE_MANAGER_PRIVATE(mgr);

  table = g_hash_table_new_full(
    g_str_hash,
    g_str_equal,
    g_free,
    (GDestroyNotify)gnutls_x509_crt_deinit
  );

  local_error = NULL;
  g_file_get_contents(priv->known_hosts_file, &content, &size, &local_error);
  if(local_error != NULL)
  {
    if(local_error->domain == G_FILE_ERROR &&
       local_error->code == G_FILE_ERROR_NOENT)
    {
      return table;
    }

    g_propagate_prefixed_error(
      error,
      local_error,
      _("Failed to open known hosts file \"%s\": "),
      priv->known_hosts_file
    );

    g_hash_table_destroy(table);
    return NULL;
  }

  out_buf = NULL;
  out_buf_len = 0;
  prev = content;
  for(prev = content; prev != NULL; prev = next)
  {
    pos = strchr(prev, '\n');
    next = NULL;

    if(pos == NULL)
      pos = content + size;
    else
      next = pos + 1;

    sep = inf_gtk_certificate_manager_memrchr(prev, ':', pos - prev);
    if(sep == NULL) continue; /* ignore line */

    *sep = '\0';
    if(g_hash_table_lookup(table, prev) != NULL)
    {
      g_set_error(
        error,
        g_quark_from_static_string("INF_GTK_CERTIFICATE_MANAGER_ERROR"),
        INF_GTK_CERTIFICATE_MANAGER_ERROR_DUPLICATE_HOST_ENTRY,
        _("Certificate for host \"%s\" appears twice in "
          "known hosts file \"%s\""),
        prev,
        priv->known_hosts_file
      );

      g_hash_table_destroy(table);
      g_free(out_buf);
      g_free(content);
      return NULL;
    }

    /* decode base64, import DER certificate */
    len = (pos - (sep + 1));
    out_len = len * 3 / 4;

    if(out_len > out_buf_len)
    {
      out_buf = g_realloc(out_buf, out_len);
      out_buf_len = out_len;
    }

    base64_state = 0;
    base64_save = 0;

    out_len = g_base64_decode_step(
      sep + 1,
      len,
      out_buf,
      &base64_state,
      &base64_save
    );

    cert = NULL;
    res = gnutls_x509_crt_init(&cert);
    if(res == GNUTLS_E_SUCCESS)
    {
      data.data = out_buf;
      data.size = out_len;
      res = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_DER);
    }

    if(res != GNUTLS_E_SUCCESS)
    {
      inf_gnutls_set_error(&local_error, res);

      g_propagate_prefixed_error(
        error,
        local_error,
        _("Failed to read certificate for host \"%s\" from "
          "known hosts file \"%s\": "),
        prev,
        priv->known_hosts_file
      );

      if(cert != NULL)
        gnutls_x509_crt_deinit(cert);

      g_hash_table_destroy(table);
      g_free(out_buf);
      g_free(content);
      return NULL;
    }

    g_hash_table_insert(table, g_strdup(prev), cert);
  }

  g_free(out_buf);
  g_free(content);
  return table;
}
/**
 * infd_acl_account_info_from_xml:
 * @xml: The XML node from which to read the account information.
 * @error: Location to store error information, if any.
 *
 * Reads information for one account from a serialized XML node. The account
 * info can be written to XML with the infd_acl_account_info_to_xml()
 * function. If the function fails it returns %NULL and @error is set.
 *
 * Returns: A #InfdAclAccountInfo, or %NULL. Free with
 * infd_acl_account_info_free() when no longer needed.
 */
InfdAclAccountInfo*
infd_acl_account_info_from_xml(xmlNodePtr xml,
                               GError** error)
{
  InfdAclAccountInfo* info;
  InfAclAccount* account;

  GError* local_error;
  gboolean has_first_seen;
  gdouble first_seen;
  gboolean has_last_seen;
  gdouble last_seen;

  xmlChar* password_salt;
  xmlChar* password_hash;
  gnutls_datum_t datum;
  size_t hash_len;
  int res;
  gchar* binary_salt;
  gchar* binary_hash;

  xmlNodePtr child;
  GPtrArray* certificate_array;
  guint i;

  g_return_val_if_fail(xml != NULL, NULL);
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);

  local_error = NULL;

  has_first_seen = inf_xml_util_get_attribute_double(
    xml,
    "first-seen",
    &first_seen,
    &local_error
  );

  if(local_error != NULL)
    return NULL;

  has_last_seen = inf_xml_util_get_attribute_double(
    xml,
    "last-seen",
    &last_seen,
    &local_error
  );

  if(local_error != NULL)
    return NULL;

  account = inf_acl_account_from_xml(xml, error);
  if(account == NULL) return NULL;

  password_salt = inf_xml_util_get_attribute(xml, "password-salt");
  password_hash = inf_xml_util_get_attribute(xml, "password-hash");

  if( (password_salt != NULL && password_hash == NULL) ||
      (password_salt == NULL && password_hash != NULL))
  {
    g_set_error(
      error,
      inf_request_error_quark(),
      INF_REQUEST_ERROR_INVALID_ATTRIBUTE,
      "%s",
      _("If one of \"password-hash\" or \"password-salt\" is provided, the "
        "other must be provided as well.")
    );

    if(password_salt != NULL) xmlFree(password_salt);
    if(password_hash != NULL) xmlFree(password_hash);

    inf_acl_account_free(account);
    return NULL;
  }

  if(password_salt != NULL && password_hash != NULL)
  {
    datum.data = password_salt;
    datum.size = strlen(password_salt);

    hash_len = 32;
    binary_salt = g_malloc(hash_len);
    res = gnutls_hex_decode(&datum, binary_salt, &hash_len);
    xmlFree(password_salt);

    if(hash_len != 32)
    {
      g_set_error(
        error,
        inf_request_error_quark(),
        INF_REQUEST_ERROR_INVALID_ATTRIBUTE,
        "%s",
        _("The length of the password salt is incorrect, it should "
          "be 32 bytes")
      );

      xmlFree(password_hash);
      g_free(binary_salt);
      return NULL;
    }
    else if(res != GNUTLS_E_SUCCESS)
    {
      inf_gnutls_set_error(error, res);
      xmlFree(password_hash);
      g_free(binary_salt);
      return NULL;
    }

    datum.data = password_hash;
    datum.size = strlen(password_hash);

    hash_len = gnutls_hash_get_len(GNUTLS_DIG_SHA256);
    binary_hash = g_malloc(hash_len);
    res = gnutls_hex_decode(&datum, binary_hash, &hash_len);
    xmlFree(password_hash);
  
    if(hash_len != gnutls_hash_get_len(GNUTLS_DIG_SHA256))
    {
      g_set_error(
        error,
        inf_request_error_quark(),
        INF_REQUEST_ERROR_INVALID_ATTRIBUTE,
        _("The length of the password hash is incorrect, it should be "
          "%u bytes"),
        (unsigned int)gnutls_hash_get_len(GNUTLS_DIG_SHA256)
      );

      g_free(binary_salt);
      g_free(binary_hash);
      return NULL;
    }
    else if(res != GNUTLS_E_SUCCESS)
    {
      inf_gnutls_set_error(error, res);
      g_free(binary_salt);
      g_free(binary_hash);
      return NULL;
    }
  }
  else
  {
    binary_salt = NULL;
    binary_hash = NULL;\

    if(password_salt != NULL) xmlFree(password_salt);
    if(password_hash != NULL) xmlFree(password_hash);
  }

  certificate_array = g_ptr_array_new();
  for(child = xml->children; child != NULL; child = child->next)
  {
    if(child->type != XML_ELEMENT_NODE) continue;
    if(strcmp((const char*)child->name, "certificate") == 0)
      g_ptr_array_add(certificate_array, xmlNodeGetContent(child));
  }

  info = infd_acl_account_info_new(account->id, account->name, FALSE);
  inf_acl_account_free(account);

  info->certificates = g_malloc(sizeof(gchar*) * certificate_array->len);
  for(i = 0; i < certificate_array->len; ++i)
  {
    info->certificates[i] = g_strdup(certificate_array->pdata[i]);
    xmlFree(certificate_array->pdata[i]);
  }
  
  info->n_certificates = certificate_array->len;
  g_ptr_array_free(certificate_array, TRUE);

  info->password_salt = binary_salt;
  info->password_hash = binary_hash;
  if(has_first_seen == TRUE)
    info->first_seen = first_seen * 1e6;
  else
    info->first_seen = 0;

  if(has_last_seen == TRUE)
    info->last_seen = last_seen * 1e6;
  else
    info->last_seen = 0;

  return info;
}
/**
 * infd_acl_account_info_set_password:
 * @info: A #InfdAclAccountInfo.
 * @password: The new password for the account, or %NULL.
 * @error: Location to store error information, if any.
 *
 * Changes the password for the given account. If @password is %NULL the
 * password is unset, which means that it is not possible to log into this
 * account with password authentication.
 *
 * If @password is non-%NULL, a new random salt is generated and a SHA256
 * hash of the salt and the password is stored.
 *
 * If an error occurs while changing the password the functions set @error
 * and returns %FALSE.
 *
 * Returns: %TRUE in case of success or %FALSE otherwise.
 */
gboolean
infd_acl_account_info_set_password(InfdAclAccountInfo* info,
                                   const gchar* password,
                                   GError** error)
{
  gchar* new_salt;
  gchar* new_hash;

  gchar* salted_password;
  guint password_len;
  int res;

  g_return_val_if_fail(info != NULL, FALSE);

  if(password == NULL)
  {
    g_free(info->password_salt);
    g_free(info->password_hash);

    info->password_salt = NULL;
    info->password_hash = NULL;
  }
  else
  {
    new_salt = g_malloc(32);
    res = gnutls_rnd(GNUTLS_RND_RANDOM, new_salt, 32);
    if(res != GNUTLS_E_SUCCESS)
    {
      g_free(new_salt);
      inf_gnutls_set_error(error, res);
      return FALSE;
    }

    password_len = strlen(password);
    salted_password = g_malloc(32 + password_len);

    memcpy(salted_password, new_salt, 16);
    memcpy(salted_password + 16, password, password_len);
    memcpy(salted_password + 16 + password_len, new_salt + 16, 16);

    new_hash = g_malloc(gnutls_hash_get_len(GNUTLS_DIG_SHA256));

    res = gnutls_hash_fast(
      GNUTLS_DIG_SHA256,
      salted_password,
      32 + password_len,
      new_hash
    );

    g_free(salted_password);

    if(res != GNUTLS_E_SUCCESS)
    {
      g_free(new_hash);
      g_free(new_salt);
      inf_gnutls_set_error(error, res);
      return FALSE;
    }

    g_free(info->password_salt);
    g_free(info->password_hash);

    info->password_salt = new_salt;
    info->password_hash = new_hash;
  }

  return TRUE;
}
static gboolean
infinoted_plugin_certificate_auth_initialize(InfinotedPluginManager* manager,
                                             gpointer plugin_info,
                                             GError** error)
{
  InfinotedPluginCertificateAuth* plugin;
  InfCertificateCredentials* creds;
  GPtrArray* read_certs;
  int res;
  guint i;

  gnutls_x509_crt_t* sign_certs;
  InfCertificateChain* sign_chain;

  gnutls_x509_privkey_t super_key;
  InfCertUtilDescription desc;
  gnutls_x509_crt_t super_cert;
  InfAclAccountId super_id;
  gnutls_x509_crt_t chain[2];
  gboolean written;

  InfdDirectory* directory;
  InfBrowserIter iter;
  InfAclSheetSet sheet_set;
  InfAclSheet sheet;
  InfRequest* request;

  plugin = (InfinotedPluginCertificateAuth*)plugin_info;
  plugin->manager = manager;

  creds = infinoted_plugin_manager_get_credentials(manager);
  if(creds == NULL)
  {
    g_set_error(
      error,
      infinoted_plugin_certificate_auth_error_quark(),
      INFINOTED_PLUGIN_CERTIFICATE_AUTH_ERROR_NO_CREDENTIALS,
      "%s",
      _("The certificate-auth plugin can only be used when TLS is enabled "
        "and a server certificate has been set.")
    );

    return FALSE;
  }

  read_certs =
    inf_cert_util_read_certificate(plugin->ca_list_file, NULL, error);
  if(read_certs == NULL) return FALSE;

  if(read_certs->len == 0)
  {
    g_set_error(
      error,
      infinoted_plugin_certificate_auth_error_quark(),
      INFINOTED_PLUGIN_CERTIFICATE_AUTH_ERROR_NO_CAS,
      _("File \"%s\" does not contain any CA certificates"),
      plugin->ca_list_file
    );

    g_ptr_array_free(read_certs, TRUE);
    return FALSE;
  }

  plugin->n_cas = read_certs->len;
  plugin->cas = (gnutls_x509_crt_t*)g_ptr_array_free(read_certs, FALSE);

  res = gnutls_certificate_set_x509_trust(
    inf_certificate_credentials_get(creds),
    plugin->cas,
    plugin->n_cas
  );

  if(res < 0)
  {
    inf_gnutls_set_error(error, res);
    return FALSE;
  }

  if(plugin->ca_key_file != NULL)
  {
    plugin->ca_key =
      inf_cert_util_read_private_key(plugin->ca_key_file, error);
    if(plugin->ca_key == NULL)
      return FALSE;

    /* Walk through certificates and find the certificate that the key
     * belongs to. */
    for(i = 0; i < plugin->n_cas; ++i)
      if(inf_cert_util_check_certificate_key(plugin->cas[i], plugin->ca_key))
        break;

    if(i == plugin->n_cas)
    {
      gnutls_x509_privkey_deinit(plugin->ca_key);
      plugin->ca_key = NULL;

      g_set_error(
        error,
        infinoted_plugin_certificate_auth_error_quark(),
        INFINOTED_PLUGIN_CERTIFICATE_AUTH_ERROR_NO_CA_FOR_KEY,
        "%s",
        _("The given CA key does not match with any of the CA certificates")
      );

      return FALSE;
    }

    plugin->ca_key_index = i;

    /* Set the signing certificate of the directory, so that it can handle
     * account creation requests. Note that this takes ownership of the
     * certificate, so we take special care in the cleanup code in
     * infinoted_plugin_certificate_auth_deinitialize(). */
    sign_certs = g_malloc(sizeof(gnutls_x509_crt_t));
    sign_certs[0] = plugin->cas[plugin->ca_key_index];
    sign_chain = inf_certificate_chain_new(sign_certs, 1);

    infd_directory_set_certificate(
      infinoted_plugin_manager_get_directory(plugin->manager),
      plugin->ca_key,
      sign_chain
    );

    inf_certificate_chain_unref(sign_chain);
  }

  if(plugin->super_user != NULL)
  {
    if(plugin->ca_key == NULL)
    {
      g_set_error(
        error,
        infinoted_plugin_certificate_auth_error_quark(),
        INFINOTED_PLUGIN_CERTIFICATE_AUTH_ERROR_NO_CA_KEY,
        "%s",
        _("Cannot generate a superuser certificate without CA key")
      );

      return FALSE;
    }

    /* Create a private key and certificate for the super user. */
    infinoted_log_info(
      infinoted_plugin_manager_get_log(plugin->manager),
      _("Creating 4096-bit RSA private key for the super user account...")
    );

    super_key =
      inf_cert_util_create_private_key(GNUTLS_PK_RSA, 4096, error);
    if(super_key == NULL)
      return FALSE;

    desc.validity = 12 * 3600; /* 12 hours */
    desc.dn_common_name = "Super User";
    desc.san_dnsname = NULL;

    super_cert = inf_cert_util_create_signed_certificate(
      super_key,
      &desc,
      plugin->cas[plugin->ca_key_index],
      plugin->ca_key,
      error
    );

    if(super_cert == NULL)
    {
      gnutls_x509_privkey_deinit(super_key);
      return FALSE;
    }

    super_id = infd_directory_create_acl_account(
      infinoted_plugin_manager_get_directory(plugin->manager),
      _("Super User"),
      TRUE, /* transient */
      &super_cert,
      1,
      error
    );

    if(super_id == 0)
    {
      gnutls_x509_crt_deinit(super_cert);
      gnutls_x509_privkey_deinit(super_key);
      return FALSE;
    }

    plugin->super_id = super_id;

    chain[0] = super_cert;
    chain[1] = plugin->cas[plugin->ca_key_index];

    written = inf_cert_util_write_certificate_with_key(
      super_key,
      chain,
      2,
      plugin->super_user,
      error
    );

    gnutls_x509_crt_deinit(super_cert);
    gnutls_x509_privkey_deinit(super_key);

    if(written == FALSE)
      return FALSE;

    inf_browser_get_root(
      INF_BROWSER(infinoted_plugin_manager_get_directory(plugin->manager)),
      &iter
    );

    directory = infinoted_plugin_manager_get_directory(plugin->manager);

    sheet.account = super_id;
    sheet.mask = INF_ACL_MASK_ALL;
    infd_directory_get_support_mask(directory, &sheet.perms);
    sheet_set.n_sheets = 1;
    sheet_set.own_sheets = NULL;
    sheet_set.sheets = &sheet;

    request = inf_browser_set_acl(
      INF_BROWSER(directory),
      &iter,
      &sheet_set,
      infinoted_plugin_certificate_auth_set_acl_cb,
      plugin
    );

    if(request != NULL)
    {
      plugin->set_acl_request = request;
      g_object_ref(plugin->set_acl_request);
    }
  }

  return TRUE;
}