/* This file is part of GNUnet (C) 2012 Christian Grothoff (and other contributing authors) 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 2, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file src/setup/gnunet-setup-gns.c * @author Christian Grothoff * @brief everything releated to the main GNS zone tree view */ #include "gnunet_gtk.h" #include "gnunet-setup-gns.h" #include "gnunet-setup-gns-edit.h" #include #include #include /** * Text we use for the 'name' entry for the user to select * for creating a new name. */ #define NEW_NAME_STR gettext_noop ("") /** * Text we use for the 'type' selection for the user to * select when adding a new record. */ #define NEW_RECORD_STR gettext_noop ("") /** * Text we use for the expiration to mean 'never expires'. */ #define EXPIRE_NEVER_STRING gettext_noop ("never") /** * Text we use for invalid values. */ #define EXPIRE_INVALID_STRING gettext_noop ("invalid") /** * Height and width of the QR code we display */ #define QRCODE_IMAGE_SIZE 64 /** * Columns in the gns model. */ enum GNSTreestoreColumn { /** * A gchararray with the value for the 'name' column. */ GNS_TREESTORE_COL_NAME = 0, /** * A gboolean, TRUE if the record is public, FALSE if it is private. */ GNS_TREESTORE_COL_IS_PUBLIC, /** * A guint with the record type (numeric value) */ GNS_TREESTORE_COL_RECORD_TYPE, /** * A gchararray with the record type (human-readable string) */ GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, /** * A guint64 with the expiration time (relative or absolute) */ GNS_TREESTORE_COL_EXP_TIME, /** * A gboolean, TRUE if the expiration time is relative. */ GNS_TREESTORE_COL_EXP_TIME_IS_REL, /** * A gchararray with the expiration time as a human-readable string. */ GNS_TREESTORE_COL_EXP_TIME_AS_STR, /** * A gchararray with the value of the record as a human-readable string. */ GNS_TREESTORE_COL_VAL_AS_STR, /** * A gchararray with the background color to use for the value. */ GNS_TREESTORE_COL_VAL_COLOR, /** * A gboolean; TRUE if the 'name' column should be shown, * FALSE for the lines with the individual records under a name. */ GNS_TREESTORE_COL_NAME_IS_VISIBLE, /** * A gboolean, TRUE if this row is for editing a record, * FALSE if the 'public', 'type', 'expiration' and 'value' * columns should be hidden. */ GNS_TREESTORE_COL_IS_RECORD_ROW, /** * A gboolean, FALSE if this is one of our 'dummy' rows that * is used to create a new name or record, TRUE if this is * a normal row with either a name or a record. */ GNS_TREESTORE_COL_NOT_DUMMY_ROW, /** * A gchararray with the name of the color to use for the * expiration column. */ GNS_TREESTORE_COL_EXP_TIME_COLOR, /** * A gchararray with the name of the color to use for the * name column. */ GNS_TREESTORE_COL_NAME_COLOR, /** * A gboolean; TRUE if the 'type' column can still be changed; * FALSE once we have edited the value. */ GNS_TREESTORE_COL_TYPE_IS_EDITABLE, /** * A gboolean; TRUE if the value is a shadow record. */ GNS_TREESTORE_COL_IS_SHADOW }; /** * Columns in the gns type model. */ enum LIST_COLUMNS { /** * A guint */ GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPE = 0, /** * A gchararray */ GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPENAME }; /** * Closure for 'zone_iteration_proc'. */ struct ZoneIteration_Context { /** * Kept in a DLL. */ struct ZoneIteration_Context *next; /** * Kept in a DLL. */ struct ZoneIteration_Context *prev; /** * Short hash of the public key of the zone. */ struct GNUNET_CRYPTO_ShortHashCode zone; /** * Iterator for loading the records from the zone. */ struct GNUNET_NAMESTORE_ZoneIterator *it; /** * Context for loading/generating the zone key for this zone. */ struct GNUNET_CRYPTO_RsaKeyGenerationContext *rkgc; }; /** * Context we use for making changes to the namestore. * (closure for 'add_new_records_after_removing_old_records'). */ struct UpdateContext { /** * Kept in a DLL. */ struct UpdateContext *next; /** * Kept in a DLL. */ struct UpdateContext *prev; /** * Array of records to add. */ struct GNUNET_NAMESTORE_RecordData *rd; /** * Name under which we should add the records. */ char *name; /** * Associated namestore operation. */ struct GNUNET_NAMESTORE_QueueEntry *qe; /** * Size of the 'rd' array. */ unsigned int rd_count; /** * Current position for record creation. */ unsigned int rd_pos; }; /** * Closure for 'check_name_validity_and_remove_proc'. */ struct RemoveContext { /** * Kept in a DLL. */ struct RemoveContext *next; /** * Kept in a DLL. */ struct RemoveContext *prev; /** * Associated namestore operation. */ struct GNUNET_NAMESTORE_QueueEntry *qe; /** * Path for 'gtk_tree_model_get_iter_from_string' for removing * the record from the tree view IF the operation was successful. * FIXME: replace with a 'GtkTreeIter'? */ char *path; }; /** * Handle created for pseudonym-operations. */ struct PseuContext { /** * Kept in a DLL. */ struct PseuContext *next; /** * Kept in a DLL. */ struct PseuContext *prev; /** * Associated namestore operation. */ struct GNUNET_NAMESTORE_QueueEntry *qe; }; /** * Head of linked list of active zone operations. */ static struct ZoneIteration_Context *zc_head; /** * Tail of linked list of active zone operations. */ static struct ZoneIteration_Context *zc_tail; /** * Head of linked list of active update operations. */ static struct UpdateContext *uc_head; /** * Tail of linked list of active update operations. */ static struct UpdateContext *uc_tail; /** * Head of linked list of active remove operations. */ static struct RemoveContext *rc_head; /** * Tail of linked list of active remove operations. */ static struct RemoveContext *rc_tail; /** * Head of linked list of active pseudonym operations. */ static struct PseuContext *pc_head; /** * Tail of linked list of active pseudonym operations. */ static struct PseuContext *pc_tail; /** * Name of our zone as a string. */ static char *zone_as_string; /** * Handle to the namestore. */ static struct GNUNET_NAMESTORE_Handle *namestore; /** * Tree store with all of the values we're modifying for GNS. */ static GtkTreeStore *ts; /** * Tree model (same object as 'ts', just different type). */ static GtkTreeModel *tm; /** * The main tree view for 'gns' that shows the records. */ static GtkTreeView *tv; /** * Private key of the zone we are currently editing. */ static struct GNUNET_CRYPTO_RsaPrivateKey *pkey; /** * Public key of the zone we are currently editing. */ static struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pubkey; /** * Short hash of the public key of the zone we are currently editing. */ static struct GNUNET_CRYPTO_ShortHashCode zone; /** * Pseudonym of the current zone we are editing. */ static char *current_pseudonym; /** * Pointer to name of the configuration option that gives the * zone key for the zone we are editing right now. */ static const char *current_zone_option; #if HAVE_QRENCODE_H #include #include /** * Create the QR code image for our zone. * * @param scale factor for scaling up the size of the image to create * @return NULL on error */ static GdkPixbuf * create_qrcode (unsigned int scale) { QRinput * qri; QRcode *qrc; char *str; const gchar *pseu; GtkEntry *entry; GdkPixbuf *pb; unsigned int x; unsigned int y; unsigned int off; guchar *pixels; int n_channels; int c; const char *dir; char *fn; unsigned int size; qri = QRinput_new2 (0, QR_ECLEVEL_Q); if (NULL == qri) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2"); return NULL; } entry = GTK_ENTRY (GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_entry")); pseu = gtk_entry_get_text (GTK_ENTRY(entry)); GNUNET_asprintf (&str, "gnunet://gns/%s/%s\n", zone_as_string, pseu); if (0 != QRinput_append (qri, QR_MODE_8, strlen (str), (unsigned char*) str)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_append"); GNUNET_free (str); return NULL; } GNUNET_free (str); qrc = QRcode_encodeInput (qri); if (NULL == qrc) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRcode_encodeInput"); QRinput_free (qri); return NULL; } /* We use a trick to create a pixbuf in a way that works for both Gtk2 and Gtk3 by loading a dummy file from disk; all other methods are not portable to both Gtk2 and Gtk3. */ dir = GNUNET_GTK_get_data_dir (); GNUNET_asprintf (&fn, "%s%s", dir, "qr_dummy.png"); size = qrc->width * scale; size += 8 - (size % 8); pb = gdk_pixbuf_new_from_file_at_size (fn, size, size, NULL); GNUNET_free (fn); if (NULL == pb) { QRinput_free (qri); return NULL; } pixels = gdk_pixbuf_get_pixels (pb); n_channels = gdk_pixbuf_get_n_channels (pb); for (x=0;xwidth / size) + (y * qrc->width / size) * qrc->width; for (c = 0; c < n_channels; c++) pixels[(y * size + x) * n_channels + c] = (0 == (qrc->data[off] & 1)) ? 0xFF : 0; } QRcode_free (qrc); QRinput_free (qri); return pb; } /** * Create the QR code image for our zone. */ static void setup_qrcode () { GdkPixbuf *pb; GtkImage *image; pb = create_qrcode (2); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Failed to initialize QR-code pixbuf")); return; } image = GTK_IMAGE (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_image")); if (NULL == image) { GNUNET_break (0); return; } gtk_image_set_from_pixbuf (image, pb); g_object_unref (pb); } #endif /** * Function called upon completion of the qr-code 'save as' dialog. * * @param dialog the dialog * @param response_id reason for the dialog closing * @param user_data the 'GtkBuilder' we used to create the dialog */ void GNUNET_setup_qr_save_as_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { #if HAVE_QRENCODE_H GtkBuilder *builder = user_data; GdkPixbuf *pb; char *filename; if (GTK_RESPONSE_OK != response_id) { gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (builder)); return; } filename = GNUNET_GTK_filechooser_get_filename_utf8 (GTK_FILE_CHOOSER (dialog)); pb = create_qrcode (8); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Failed to initialize QR-code pixbuf")); return; } gdk_pixbuf_save (pb, filename, "png", NULL, NULL); g_free (filename); g_object_unref (pb); gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (builder)); #else GNUNET_break (0); #endif } /** * User clicked on 'save as' to extract the QR code. Open 'save as' * dialog to get the desired filename and file type. */ void GNUNET_setup_gns_qr_saveas_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkBuilder *builder; GtkWindow *dialog; const gchar *pseu; GtkEntry *entry; char *suggestion; entry = GTK_ENTRY (GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_entry")); pseu = gtk_entry_get_text (GTK_ENTRY(entry)); builder = GNUNET_GTK_get_new_builder ("gnunet_setup_qr_save_as_dialog.glade", NULL); if (NULL == builder) { GNUNET_break (0); return; } GNUNET_asprintf (&suggestion, "%s.png", pseu); dialog = GTK_WINDOW (gtk_builder_get_object (builder, "GNUNET_setup_qr_save_as_dialog")); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggestion); GNUNET_free (suggestion); gtk_window_present (dialog); } /** * Load a particular zone into the main tree view. * * @param zonename name of the option in the configuration file * with the name of the file with the private key of the * zone to load */ static void load_zone (const char *zonename); /** * Our model is somehow are inconsistent with the namestore database. * Clear our model and resync by loading all of it from the namestore. * (Naturally, this should never happen in normal operation; however, * if multiple users edit the same namestore, it might happen without * there being a bug ...). */ static void resync_db () { load_zone (current_zone_option); } /** * Display an error message for the user. * * @param title title of the error message * @param emsg error message to show */ static void show_error_message (const char *title, const char *emsg) { GtkWindow *main_window; GtkDialog *dialog; /* FIXME: consider replacing with widget in the main window */ main_window = GTK_WINDOW (GNUNET_SETUP_get_object ("GNUNET_setup_dialog")); dialog = GTK_DIALOG(gtk_message_dialog_new (main_window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("%s\n%s\n"), title, emsg)); g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_widget_show_all (GTK_WIDGET(dialog)); } /** * Release resources of this update context. * * @param uc context to free */ static void free_update_context (struct UpdateContext *uc) { unsigned int c; if (NULL != uc->qe) { GNUNET_NAMESTORE_cancel (uc->qe); uc->qe = NULL; } GNUNET_CONTAINER_DLL_remove (uc_head, uc_tail, uc); for (c = 0; c < uc->rd_count; c++) GNUNET_free ((void *) uc->rd[c].data); GNUNET_free (uc->rd); GNUNET_free (uc->name); GNUNET_free (uc); } /** * Function called to transmit the next record from this update context * to the database. * * @param uc update context */ static void create_next_record (struct UpdateContext *uc); /** * Function called after we created a new record. If the creation was * successful, add more records from the update context. * * @param cls the 'struct UpdateContext' * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate) * GNUNET_NO if content was already there or not found * GNUNET_YES (or other positive value) on success * @param emsg NULL on success, otherwise an error message */ static void create_more_records (void *cls, int32_t success, const char *emsg) { struct UpdateContext *uc = cls; uc->qe = NULL; switch (success) { case GNUNET_OK: uc->rd_pos++; create_next_record (uc); return; case GNUNET_NO: case GNUNET_SYSERR: show_error_message (_("Failed to create record"), emsg); resync_db (); break; default: GNUNET_break (0); break; } free_update_context (uc); } /** * Function called to transmit the next record from this update context * to the database. * * @param uc update context */ static void create_next_record (struct UpdateContext *uc) { if (uc->rd_pos == uc->rd_count) { free_update_context (uc); return; } uc->qe = GNUNET_NAMESTORE_record_create (namestore, pkey, uc->name, &uc->rd[uc->rd_pos], &create_more_records, uc); } /** * Function called after we removed the old record. If the * removal was successful, add the new records from the * update context. * * @param cls the 'struct UpdateContext' * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate) * GNUNET_NO if content was already there or not found * GNUNET_YES (or other positive value) on success * @param emsg NULL on success, otherwise an error message */ static void add_new_records_after_removing_old_records (void *cls, int32_t success, const char *emsg) { struct UpdateContext *uc = cls; uc->qe = NULL; switch (success) { case GNUNET_OK: case GNUNET_NO: create_next_record (uc); return; case GNUNET_SYSERR: show_error_message (_("Failed to remove record"), emsg); resync_db (); break; default: GNUNET_break (0); resync_db (); break; } free_update_context (uc); } /** * Check that the data at the given 'path' (see gtk_tree_model_get_iter_from_string) * is valid, and if so commit it after removing the old data. * * @param it iter identifying the new record * @param oldname name of the old record, NULL if this is a fresh name */ static void check_name_validity_and_commit (GtkTreeIter *it, const char *oldname) { GtkTreeIter parent; /* parent record with the 'name' */ char *name; /* name of the records */ struct GNUNET_NAMESTORE_RecordData *rd; unsigned int records; /* number of records in 'rd' */ int children; /* number of records below 'parent' */ int append_pseu; /* do we need to create a '+' PSEU record? */ struct UpdateContext * uc; int c; if (! gtk_tree_model_iter_parent (tm, &parent, it)) { if (NULL != oldname) GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Name of existing record `%s' was changed, moving associated records\n", oldname); parent = *it; } gtk_tree_model_get (tm, &parent, GNS_TREESTORE_COL_NAME, &name, -1); children = gtk_tree_model_iter_n_children (tm, &parent); if (children < 1) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Changed name `%s' has no associated records, not committing to namestore\n", name); g_free (name); return; } if ( (0 == strcmp (name, GNUNET_GNS_MASTERZONE_STR)) && (0 == strcmp (current_zone_option, "ZONEKEY")) ) { /* We have to append PSEU RECORD, this is the 'master' zone */ append_pseu = GNUNET_YES; records = children + 1; } else { append_pseu = GNUNET_NO; records = children; } rd = GNUNET_malloc (records * sizeof (struct GNUNET_NAMESTORE_RecordData)); GNUNET_assert (gtk_tree_model_iter_children (tm, it, &parent)); for (c = 0; c < children; c++) { gchar *n_name; gint n_type; gboolean n_public; guint64 n_exp_time; gboolean n_is_relative; gchar *n_value; void * data; size_t data_size; gtk_tree_model_get (tm, it, GNS_TREESTORE_COL_NAME, &n_name, GNS_TREESTORE_COL_RECORD_TYPE, &n_type, GNS_TREESTORE_COL_IS_PUBLIC, &n_public, GNS_TREESTORE_COL_EXP_TIME, &n_exp_time, GNS_TREESTORE_COL_EXP_TIME_IS_REL, &n_is_relative, GNS_TREESTORE_COL_VAL_AS_STR, &n_value, -1); if ( (NULL == n_name) || (GNUNET_SYSERR == GNUNET_DNSPARSER_check_label (n_name)) || (0 == n_type) || (0 == n_exp_time) || (NULL == n_value) || (GNUNET_OK != GNUNET_NAMESTORE_string_to_value(n_type, n_value, &data, &data_size)) ) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Invalid record, skipping\n"); records--; children--; c--; } else { if (n_public) rd[c].flags = GNUNET_NAMESTORE_RF_AUTHORITY; else rd[c].flags = GNUNET_NAMESTORE_RF_AUTHORITY | GNUNET_NAMESTORE_RF_PRIVATE; rd[c].record_type = n_type; rd[c].expiration_time = n_exp_time; if (n_is_relative) rd[c].flags |= GNUNET_NAMESTORE_RF_RELATIVE_EXPIRATION; rd[c].data_size = data_size; rd[c].data = GNUNET_malloc(data_size); memcpy ((void *) rd[c].data, data, data_size); } g_free (n_name); g_free (n_value); GNUNET_assert (gtk_tree_model_iter_next (tm, it) ^ (c + 1 == children)); } if (GNUNET_YES == append_pseu) { GtkEntry *entry; const gchar *pseu; /* Append PSEU record */ GNUNET_assert (children == (records -1)); entry = GTK_ENTRY (GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_entry")); pseu = gtk_entry_get_text (GTK_ENTRY(entry)); if ( (NULL == pseu) || (0 == strcmp ("", pseu)) ) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No zone pseudonym given, not creating PSEU record\n"); records--; } else { if (GNUNET_OK != GNUNET_NAMESTORE_string_to_value (GNUNET_NAMESTORE_TYPE_PSEU, pseu, (void **) &rd[records - 1].data, &rd[records - 1].data_size)) { show_error_message (_("Invalid pseudonym specified for zone"), pseu); records--; } else { rd[records - 1].record_type = GNUNET_NAMESTORE_TYPE_PSEU; rd[records - 1].expiration_time = UINT64_MAX; rd[records - 1].flags = GNUNET_NAMESTORE_RF_AUTHORITY | GNUNET_NAMESTORE_RF_NONE; } } } if (0 == records) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No valid records remaining, not storing to namestore\n"); GNUNET_free (rd); return; } /* Store update information in context and remove old entries */ uc = GNUNET_malloc (sizeof (struct UpdateContext)); uc->rd = rd; uc->rd_count = records; uc->name = name; GNUNET_CONTAINER_DLL_insert (uc_head, uc_tail, uc); uc->qe = GNUNET_NAMESTORE_record_remove (namestore, pkey, (NULL != oldname) ? oldname : name, NULL, &add_new_records_after_removing_old_records, uc); if (NULL == uc->qe) { show_error_message (_("Failed to commit record to database"), _("Internal error")); resync_db (); free_update_context (uc); return; } } /** * Release resources of the given remove context. * * @param rc remove context to release */ static void free_remove_context (struct RemoveContext *rc) { if (NULL != rc->qe) { GNUNET_NAMESTORE_cancel (rc->qe); rc->qe = NULL; } GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); GNUNET_free (rc->path); GNUNET_free (rc); } /** * We tried to remove a record from the namestore, if we were * successful, also remove it from the model. * * @param cls the 'struct RemoveContext' * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate) * GNUNET_NO if content was already there or not found * GNUNET_YES (or other positive value) on success * @param emsg NULL on success, otherwise an error message */ static void update_treemodel_after_remove (void *cls, int32_t success, const char *emsg) { struct RemoveContext *rc = cls; GtkTreeIter it; rc->qe = NULL; switch (success) { case GNUNET_YES: case GNUNET_NO: gtk_tree_model_get_iter_from_string(tm, &it, rc->path); gtk_tree_store_remove (ts, &it); break; case GNUNET_SYSERR: show_error_message (_("Failed to remove record"), emsg); resync_db (); break; default: GNUNET_break (0); break; } free_remove_context (rc); } /** * Remove a record from the model (and if it is valid, also from * the namestore). If the given path identifies an entire 'name', * remove all records under that name. * * @param path identifying record(s) to remove; FIXME: consider doing this with an 'iter' instead */ static void remove_records_by_path (const gchar *path) { GtkTreeIter it; GtkTreeIter parent; char *name; struct GNUNET_NAMESTORE_RecordData rd; struct RemoveContext *rc; char *n_name; int n_type; gboolean n_public; guint64 n_exp_time; gboolean n_is_relative; char *n_value; gtk_tree_model_get_iter_from_string (tm, &it, path); gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NAME, &name, -1); if (gtk_tree_model_iter_parent (tm, &parent, &it)) { /* Removing a single record */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Removing single record for name `%s'\n", name); gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NAME, &n_name, GNS_TREESTORE_COL_RECORD_TYPE, &n_type, GNS_TREESTORE_COL_IS_PUBLIC, &n_public, GNS_TREESTORE_COL_EXP_TIME, &n_exp_time, GNS_TREESTORE_COL_EXP_TIME_IS_REL, &n_is_relative, GNS_TREESTORE_COL_VAL_AS_STR, &n_value, -1); /* valid name */ if (n_public) rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY; else rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY | GNUNET_NAMESTORE_RF_PRIVATE; if (n_is_relative) rd.flags |= GNUNET_NAMESTORE_RF_RELATIVE_EXPIRATION; rd.record_type = n_type; rd.expiration_time = n_exp_time; GNUNET_NAMESTORE_string_to_value (n_type, n_value, (void**)&rd.data, &rd.data_size); rc = GNUNET_malloc (sizeof (struct RemoveContext)); GNUNET_CONTAINER_DLL_insert (rc_head, rc_tail, rc); rc->path = strdup (path); rc->qe = GNUNET_NAMESTORE_record_remove (namestore, pkey, name, &rd, &update_treemodel_after_remove, rc); GNUNET_free ((void *) rd.data); g_free (n_name); g_free (n_value); } else if (0 != strcmp (name, GNUNET_GNS_MASTERZONE_STR)) { /* Removing the whole name record */ rc = GNUNET_malloc(sizeof (struct RemoveContext)); GNUNET_CONTAINER_DLL_insert (rc_head, rc_tail, rc); rc->path = strdup (path); rc->qe = GNUNET_NAMESTORE_record_remove (namestore, pkey, name, NULL, &update_treemodel_after_remove, rc); } g_free (name); } /** * The edit dialog completed; update the namestore and the * view based on the new values in 'edc'. * * @param edc editing context information * @param ret return code of the dialog */ static void edit_dialog_continuation (struct EditDialogContext *edc, GtkResponseType ret) { char *path; /* FIXME: logic to move records between zones is missing here! (#2473) */ switch (ret) { case GTK_RESPONSE_REJECT: /* code for 'delete' */ if (GNUNET_YES == edc->old_record_in_namestore) { /* remove item from tree view and namestore */ path = gtk_tree_model_get_string_from_iter (tm, &edc->it); remove_records_by_path (path); g_free (path); } else { /* invalid choice, 'delete' should have been hidden... */ GNUNET_break (0); gtk_tree_store_remove (ts, &edc->it); } break; case GTK_RESPONSE_CANCEL: case GTK_RESPONSE_DELETE_EVENT: /* window deletion counts as 'cancel' */ if (GNUNET_NO == edc->old_record_in_namestore) { /* re-enable type selection */ gtk_tree_store_set (ts, &edc->it, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, TRUE, GNS_TREESTORE_COL_VAL_COLOR, "red", -1); } else { /* do nothing */ } break; case GTK_RESPONSE_OK: /* update model */ gtk_tree_store_set (ts, &edc->it, GNS_TREESTORE_COL_NAME, edc->n_name, GNS_TREESTORE_COL_IS_PUBLIC, edc->n_public, GNS_TREESTORE_COL_EXP_TIME, edc->n_exp_time, GNS_TREESTORE_COL_EXP_TIME_IS_REL, edc->n_is_relative, GNS_TREESTORE_COL_IS_SHADOW, edc->n_is_shadow, GNS_TREESTORE_COL_VAL_AS_STR, edc->n_value, GNS_TREESTORE_COL_VAL_COLOR, NULL, -1); if (GNUNET_YES == edc->old_record_in_namestore) { /* replace record in database with that from model */ check_name_validity_and_commit (&edc->it, edc->n_name); } else { /* add record in database based on model */ check_name_validity_and_commit (&edc->it, NULL); } break; default: GNUNET_break (0); break; } g_free (edc->n_name); g_free (edc->n_new_name); g_free (edc->n_value); GNUNET_free (edc); } /** * Edit the record at the currently selected row. If the old record * exists, allow the user to modify or delete it; if it does not * exist, make the record type editable again (by offering the user the * 'cancel' option; hide 'delete' in this case). * * @param old_record_in_namestore GNUNET_YES if the old record exists in the namestore, * GNUNET_NO if this is a new record that doesn't exist yet */ static void edit_selected_row (int old_record_in_namestore) { GtkTreeSelection *sel; gint n_type; struct EditDialogContext *edc; sel = gtk_tree_view_get_selection (tv); edc = GNUNET_malloc (sizeof (struct EditDialogContext)); if (! gtk_tree_selection_get_selected (sel, NULL, &edc->it)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No row selected\n"); GNUNET_free (edc); return; } gtk_tree_model_get (tm, &edc->it, GNS_TREESTORE_COL_NAME, &edc->n_name, GNS_TREESTORE_COL_RECORD_TYPE, &n_type, GNS_TREESTORE_COL_IS_PUBLIC, &edc->n_public, GNS_TREESTORE_COL_EXP_TIME, &edc->n_exp_time, GNS_TREESTORE_COL_EXP_TIME_IS_REL, &edc->n_is_relative, GNS_TREESTORE_COL_IS_SHADOW, &edc->n_is_shadow, GNS_TREESTORE_COL_VAL_AS_STR, &edc->n_value, -1); edc->old_record_in_namestore = old_record_in_namestore; edc->new_zone_option = current_zone_option; edc->n_new_name = g_strdup (edc->n_name); edc->cont = &edit_dialog_continuation; switch (n_type) { case GNUNET_DNSPARSER_TYPE_A: GNS_edit_dialog_a (edc); break; case GNUNET_DNSPARSER_TYPE_NS: case GNUNET_DNSPARSER_TYPE_CNAME: case GNUNET_DNSPARSER_TYPE_SOA: case GNUNET_DNSPARSER_TYPE_PTR: case GNUNET_DNSPARSER_TYPE_MX: case GNUNET_DNSPARSER_TYPE_TXT: case GNUNET_DNSPARSER_TYPE_AAAA: case GNUNET_DNSPARSER_TYPE_SRV: case GNUNET_DNSPARSER_TYPE_TLSA: case GNUNET_NAMESTORE_TYPE_PKEY: case GNUNET_NAMESTORE_TYPE_LEHO: case GNUNET_NAMESTORE_TYPE_VPN: GNUNET_break (0); /* FIXME - implement (#2465) */ edc->cont (edc, GTK_RESPONSE_CANCEL); /* treat as 'cancel' */ break; } } /** * The user has selected a new record type. Update the * model and then start the 'edit' dialog. * * @param renderer updated renderer * @param path the path identifying the edited cell * @param new_iter selected cell in the combo's model (with the record type) * @param user_data unused */ void GNUNET_setup_gns_type_cellrenderercombo_edited_cb (GtkCellRendererCombo *combo, gchar *path, gchar *new_text, gpointer user_data) { GtkTreeSelection *sel; GtkTreeIter it; GtkTreeIter child; guint type; char *name_str; if (0 == strcmp (new_text, _(NEW_RECORD_STR))) return; /* no record type was selected */ type = GNUNET_NAMESTORE_typename_to_number (new_text); if (UINT32_MAX == type) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Invalid or unsupported record type `%s'\n", new_text); show_error_message (_("Unsupported record type"), new_text); return; } /* check if this is a new record */ gtk_tree_model_get_iter_from_string (tm, &it, path); gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NAME, &name_str, -1); gtk_tree_store_insert_with_values (ts, &child , &it, -1, GNS_TREESTORE_COL_NAME, name_str, GNS_TREESTORE_COL_NAME_IS_VISIBLE, FALSE, GNS_TREESTORE_COL_RECORD_TYPE, type, GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, new_text, GNS_TREESTORE_COL_EXP_TIME_AS_STR, EXPIRE_NEVER_STRING, GNS_TREESTORE_COL_EXP_TIME, GNUNET_TIME_UNIT_FOREVER_ABS, GNS_TREESTORE_COL_EXP_TIME_IS_REL, FALSE, GNS_TREESTORE_COL_IS_RECORD_ROW, TRUE, GNS_TREESTORE_COL_NOT_DUMMY_ROW, TRUE, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, FALSE, -1); /* select new row and start editing 'value' */ gtk_tree_view_expand_row (tv, gtk_tree_model_get_path (tm, &it), 0); sel = gtk_tree_view_get_selection (tv); gtk_tree_selection_select_iter (sel, &child); g_free (name_str); edit_selected_row (GNUNET_NO); } /** * The user has toggled the 'public' checkmark of a cell. Update the * model. * * @param renderer updated renderer * @param path the path identifying the edited cell * @param user_data unused */ void GNUNET_setup_gns_ispublic_cellrenderertoggle_toggled_cb (GtkCellRendererToggle *cell_renderer, gchar *path, gpointer user_data) { GtkTreeIter it; gboolean value; gtk_tree_model_get_iter_from_string (tm, &it, path); gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_IS_PUBLIC, &value, -1); gtk_tree_store_set (ts, &it, GNS_TREESTORE_COL_IS_PUBLIC, !value, -1); check_name_validity_and_commit (&it, NULL); } /** * The user has edited a 'expiration' cell. Update the model. * * @param renderer updated renderer * @param path the path identifying the edited cell * @param new_text the new expiration time * @param user_data unused */ void GNUNET_setup_gns_expiration_cellrenderertext_edited_cb (GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data) { GtkTreeIter it; struct GNUNET_TIME_Absolute abstime; struct GNUNET_TIME_Relative reltime; if (NULL == new_text) return; /* can this happen? */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New expiration time: `%s'\n", new_text); gtk_tree_model_get_iter_from_string (tm, &it, path); if (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (new_text, &abstime)) { gtk_tree_store_set (ts, &it, GNS_TREESTORE_COL_EXP_TIME_AS_STR, new_text, GNS_TREESTORE_COL_EXP_TIME, abstime.abs_value, GNS_TREESTORE_COL_EXP_TIME_IS_REL, FALSE, GNS_TREESTORE_COL_EXP_TIME_COLOR, NULL, -1); check_name_validity_and_commit (&it, NULL); return; } if (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_relative (new_text, &reltime)) { gtk_tree_store_set (ts, &it, GNS_TREESTORE_COL_EXP_TIME_AS_STR, new_text, GNS_TREESTORE_COL_EXP_TIME, reltime.rel_value, GNS_TREESTORE_COL_EXP_TIME_IS_REL, TRUE, GNS_TREESTORE_COL_EXP_TIME_COLOR, NULL, -1); check_name_validity_and_commit (&it, NULL); return; } show_error_message (_("Invalid time value"), new_text); } /** * The user has edited a 'name' cell. Update the model (and if needed * create another fresh line for additional records). * * @param renderer updated renderer * @param path the path identifying the edited cell * @param new_text the new name (not modified) * @param user_data unused */ void GNUNET_setup_gns_name_cellrenderertext_edited_cb (GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data) { GtkTreeIter it; GtkTreeIter child; gboolean not_dummy; char *name; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New text for `%s' is `%s'\n", path, new_text); if ((0 == strcmp (new_text, NEW_NAME_STR)) || (0 == strcmp (new_text, ""))) return; if ( (GNUNET_OK != GNUNET_DNSPARSER_check_label (new_text)) && (0 != strcmp (new_text, GNUNET_GNS_MASTERZONE_STR)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Name `%s' invalid for GADS/DNS (too long for a DNS label?)\n"), new_text); gdk_beep (); return; } gtk_tree_model_get_iter_from_string (tm, &it, path); gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, GNS_TREESTORE_COL_NAME, &name, -1); if (! not_dummy) { /* change dummy line to new name, then add new dummy */ gtk_tree_store_set (ts, &it, GNS_TREESTORE_COL_NAME, new_text, GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE, GNS_TREESTORE_COL_RECORD_TYPE, 0, GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, _(NEW_RECORD_STR), GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE, GNS_TREESTORE_COL_IS_RECORD_ROW, TRUE, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, TRUE, -1); check_name_validity_and_commit (&it, name); if (0 == strcmp (name, _(NEW_NAME_STR))) { /* add a new dummy line */ gtk_tree_store_insert_with_values (ts, &it,NULL, 0, GNS_TREESTORE_COL_NAME, _(NEW_NAME_STR), GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE, GNS_TREESTORE_COL_RECORD_TYPE, GNUNET_DNSPARSER_TYPE_A, GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE, GNS_TREESTORE_COL_IS_RECORD_ROW, FALSE, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, FALSE, -1); } } else { /* update name */ gtk_tree_store_set (ts, &it, GNS_TREESTORE_COL_NAME, new_text, -1); if (gtk_tree_model_iter_children (tm, &child, &it)) { do { gtk_tree_store_set (ts, &child, GNS_TREESTORE_COL_NAME, new_text, -1); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New text for `%s' is `%s'\n", path, new_text); } while (gtk_tree_model_iter_next (tm, &child)); } check_name_validity_and_commit (&it, name); } } /** * Create a context (popup) menu for the zone iteration treeview * (if applicable). * * @return TRUE if a menu was activated */ static gboolean create_popup_menu () { GtkTreeIter it; GtkMenu *popup; GtkTreeSelection *sel; gboolean name_vis; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Considering creating popup menu...\n"); sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, NULL, &it)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No row selected\n"); return FALSE; } gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NAME_IS_VISIBLE, &name_vis, -1); if (name_vis) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Selected row is not a record row\n"); return FALSE; } popup = GTK_MENU (GNUNET_SETUP_get_object ("GNUNET_setup_gns_edit_popup_menu")); gtk_widget_show_all (GTK_WIDGET (popup)); gtk_menu_popup (popup, NULL, NULL, NULL, NULL, 0, 0); return TRUE; } /** * The zone treeview pop up menu is supposed to be created. * (Note: this is not the only method that might need to be * written to handle events to create pop up menus; right-clicks * might need to be managed separately). * * @param widget the widget * @param user_data unused * @return TRUE if a menu was activated */ gboolean GNUNET_setup_gns_main_treeview_popup_menu_cb (GtkWidget *widget, gpointer user_data) { return create_popup_menu (); } /** * Delete the selected row from the GtkTreeView (unless it is a dummy row). */ static void delete_selected_row () { GtkTreeIter it; GtkTreeSelection *sel; int not_dummy; char *path; sel = gtk_tree_view_get_selection(tv); if (! gtk_tree_selection_get_selected (sel, NULL, &it)) return; /* nothing selected */ gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, -1); if (GNUNET_NO == not_dummy) return; /* do not delete the dummy line */ path = gtk_tree_model_get_string_from_iter (tm, &it); remove_records_by_path (path); g_free (path); } /** * User selected 'edit' in the popup menu. Edit the * selected row. * * @param widget the GtkTreeView * @param user_data main window builder */ void GNUNET_setup_gns_popup_edit_button_activate_cb (GtkWidget *widget, gpointer user_data) { edit_selected_row (GNUNET_YES); } /** * Function called upon completion of the calendar dialog. * * @param dialog the dialog * @param response_id reason for the dialog closing * @param user_data the 'GtkBuilder' we used to create the dialog */ void GNUNET_setup_calendar_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { GtkBuilder *builder = user_data; guint year; guint month; guint day; GtkTreeIter it; GtkTreeIter parent; GtkCellRendererText *renderer; GtkTreeSelection *sel; gboolean has_parent; char *path; int not_dummy; char fancydate[128]; const char *gndate; struct GNUNET_TIME_Absolute atime; if (GTK_RESPONSE_OK != response_id) { gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (builder)); return; } gtk_calendar_get_date (GTK_CALENDAR (gtk_builder_get_object (builder, "GNUNET_setup_calendar")), &year, &month, &day); GNUNET_snprintf (fancydate, sizeof (fancydate), "%u-%u-%u", (unsigned int) year, (unsigned int) month + 1, (unsigned int) day); GNUNET_break (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (fancydate, &atime)); gndate = GNUNET_STRINGS_absolute_time_to_string (atime); sel = gtk_tree_view_get_selection (tv); if (gtk_tree_selection_get_selected (sel, NULL, &it)) { gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, -1); if (GNUNET_NO != not_dummy) { /* Has parent? */ has_parent = gtk_tree_model_iter_parent (tm, &parent, &it); if (FALSE != has_parent) { /* this is a single record */ renderer = GTK_CELL_RENDERER_TEXT((GNUNET_SETUP_get_object ("GNUNET_setup_gns_name_cellrenderertext"))); path = gtk_tree_model_get_string_from_iter (tm, &it); GNUNET_setup_gns_expiration_cellrenderertext_edited_cb (renderer, path, (gchar *) gndate, NULL); } } } gtk_widget_destroy (GTK_WIDGET (dialog)); g_object_unref (G_OBJECT (builder)); } /** * User selected the 'calendar' option in the expiration context menu. * Popup the calendar dialog and allow the user to select a date. * * @param widget unused * @param user_data unused */ void GNUNET_setup_gns_popup_cal_button_activate_cb (GtkWidget *widget, gpointer user_data) { GtkBuilder *builder; GtkWindow *dialog; GtkTreeSelection *sel; gboolean has_parent; int not_dummy; guint64 et; gboolean is_relative; GtkTreeIter it; GtkTreeIter parent; struct GNUNET_TIME_Absolute now; time_t tp; struct tm *ymd; sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, NULL, &it)) return; gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, GNS_TREESTORE_COL_EXP_TIME_IS_REL, &is_relative, GNS_TREESTORE_COL_EXP_TIME, &et, -1); if (! not_dummy) return; has_parent = gtk_tree_model_iter_parent (tm, &parent, &it); if (! has_parent) return; now = GNUNET_TIME_absolute_get (); now = GNUNET_TIME_absolute_get (); if (is_relative) et = now.abs_value; /* use today as starting point */ if (et < now.abs_value) et = now.abs_value; /* no not allow starting point in the past */ tp = (time_t) (et / 1000LL); /* convert to seconds */ builder = GNUNET_GTK_get_new_builder ("gnunet_setup_calendar_dialog.glade", NULL); if (NULL == builder) { GNUNET_break (0); return; } ymd = gmtime (&tp); gtk_calendar_select_month (GTK_CALENDAR (gtk_builder_get_object (builder, "GNUNET_setup_calendar")), ymd->tm_mon, ymd->tm_year + 1900); gtk_calendar_mark_day (GTK_CALENDAR (gtk_builder_get_object (builder, "GNUNET_setup_calendar")), ymd->tm_mday); dialog = GTK_WINDOW (gtk_builder_get_object (builder, "GNUNET_setup_calendar_dialog")); gtk_window_present (dialog); } /** * A button was pressed in the GtkTreeView, check for right button and * if applicable create the popup menu. * * @param widget the GtkTreeView * @param event the event * @param user_data unused * @return TRUE if a menu was activated (event was handled) */ gboolean GNUNET_setup_gns_main_treeview_button_press_event_cb (GtkWidget *widget, GdkEventButton *event, gpointer user_data) { /* Check for right click*/ if (NULL == widget) return FALSE; if ( (GDK_BUTTON_PRESS == event->type) && (3 == event->button) ) return create_popup_menu (); return FALSE; } /** * User pushed a key in the GtkTreeView. Check for 'del' and if so, delete * the currently selected row. */ gboolean GNUNET_setup_gns_main_treeview_key_press_event_cb (GtkWidget *widget, GdkEventKey *event, gpointer user_data) { /* Check for delete key */ if ( (GDK_KEY_PRESS == event->type) && (GDK_KEY_Delete == event->keyval) ) { delete_selected_row (); return TRUE; } return FALSE; } /** * Function called upon completion of a 'pseu' operation. * * @param cls the 'struct PseuContext' of the operation that completed * @param success GNUNET_OK if the operation succeeded * @param emsg error message if the operation failed */ static void pseu_change_cont (void *cls, int32_t success, const char *emsg) { struct PseuContext *pc = cls; GtkWidget *dialog; GtkWindow *main_window; pc->qe = NULL; GNUNET_CONTAINER_DLL_remove (pc_head, pc_tail, pc); if (GNUNET_SYSERR == success) { main_window = GTK_WINDOW (GNUNET_SETUP_get_object ("GNUNET_setup_dialog")); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("New Pseudonym could not be set: `%s'\n"), emsg); dialog = gtk_message_dialog_new (main_window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("New Pseudonym could not be set: `%s'\n"), emsg); g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show_all (dialog); resync_db (); } } /** * The user edited the preferred name (PSEU) of this namespace. * Push the update to the namestore. * * @param editable the edited widget * @param user_data unused */ void GNUNET_setup_gns_pseu_entry_changed_cb (GtkEditable *editable, gpointer user_data) { struct GNUNET_NAMESTORE_RecordData rd; const gchar *pseu; struct PseuContext *pc; pseu = gtk_entry_get_text (GTK_ENTRY (editable)); if (GNUNET_OK != GNUNET_DNSPARSER_check_label (pseu)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _("Name `%s' invalid for GADS/DNS (too long for a DNS label?)\n"), pseu); gdk_beep (); gtk_entry_set_text (GTK_ENTRY (editable), pseu); return; } if ( (pseu != NULL) && (0 != strcmp ("", pseu)) ) { rd.record_type = GNUNET_NAMESTORE_TYPE_PSEU; rd.expiration_time = UINT64_MAX; rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY; rd.data_size = strlen (pseu) + 1; rd.data = pseu; pc = GNUNET_malloc (sizeof (struct PseuContext)); GNUNET_CONTAINER_DLL_insert (pc_head, pc_tail, pc); pc->qe = GNUNET_NAMESTORE_record_create (namestore, pkey, "+", &rd, &pseu_change_cont, pc); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New Pseudonym is `%s' %u\n", (char *) rd.data, rd.data_size); GNUNET_free_non_null (current_pseudonym); current_pseudonym = GNUNET_strdup (pseu); } else if (NULL != current_pseudonym) { rd.record_type = GNUNET_NAMESTORE_TYPE_PSEU; rd.expiration_time = UINT64_MAX; rd.flags = GNUNET_NAMESTORE_RF_AUTHORITY; rd.data_size = strlen (current_pseudonym) + 1; rd.data = current_pseudonym; pc = GNUNET_malloc (sizeof (struct PseuContext)); GNUNET_CONTAINER_DLL_insert (pc_head, pc_tail, pc); pc->qe = GNUNET_NAMESTORE_record_remove (namestore, pkey, "+", &rd, &pseu_change_cont, pc); gtk_entry_set_text (GTK_ENTRY(editable), ""); GNUNET_free_non_null (current_pseudonym); current_pseudonym = NULL; } #if HAVE_QRENCODE_H setup_qrcode (); #endif } /** * The user clicked on the 'copy' button. Copy the full string * with the hash of our public key to the clipboard. * * @param button the button that was clicked * @param user_data unused */ void GNUNET_setup_gns_public_key_copy_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkClipboard *cb; if (NULL == zone_as_string) return; cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, zone_as_string, -1); } /** * Function called for each record in the current zone. Update the * widgets accordingly. Once the zone iteration is done, unfreeze * the editing functions. * * @param cls the 'strucct ZoneIteration_Context' * @param zone_key public key of the zone * @param freshness when does the corresponding block in the DHT expire (until * when should we never do a DHT lookup for the same name again)?; * GNUNET_TIME_UNIT_ZERO_ABS if there are no records of any type in the namestore, * or the expiration time of the block in the namestore (even if there are zero * records matching the desired record type) * @param name name that is being mapped (at most 255 characters long) * @param rd_count number of entries in 'rd' array * @param rd array of records with data to store * @param signature signature of the record block, NULL if signature is unavailable (i.e. * because the user queried for a particular record type only) */ static void zone_iteration_proc (void *cls, const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key, struct GNUNET_TIME_Absolute expire, const char *name, unsigned int rd_count, const struct GNUNET_NAMESTORE_RecordData *rd, const struct GNUNET_CRYPTO_RsaSignature *signature) { struct ZoneIteration_Context * zc_ctx = cls; GtkTreeIter iter_name; GtkTreeIter iter_record; GtkEntry *pseu_entry; int c; struct GNUNET_CRYPTO_ShortHashAsciiEncoded shenc; const char *exp; char *val; char *type_str; gboolean time_is_relative; gboolean public; guint64 exp_t; GNUNET_assert (NULL != zc_ctx); if ((NULL == zone_key) && (NULL == name)) { GNUNET_CRYPTO_short_hash_to_enc (&zc_ctx->zone, &shenc); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Zone `%s 'iteration done\n", &shenc); pseu_entry = GTK_ENTRY((GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_entry"))); if (0 == strcmp (current_zone_option, "ZONEKEY")) gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_hbox"))); GNUNET_setup_gns_pseu_entry_changed_cb (GTK_EDITABLE (pseu_entry), NULL); GNUNET_CONTAINER_DLL_remove (zc_head, zc_tail, zc_ctx); GNUNET_free (zc_ctx); #if HAVE_QRENCODE_H setup_qrcode (); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_image"))); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_saveas_button"))); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_vseparator"))); #else gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_image"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_saveas_button"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_vseparator"))); #endif gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_status_label"))); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_main_scrolledwindow"))); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_zone_selection_hbuttonbox"))); return; } if ( (GNUNET_SYSERR == GNUNET_DNSPARSER_check_label (name)) && (0 != strcmp (name, GNUNET_GNS_MASTERZONE_STR)) ) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Got invalid record name `%s' from namestore\n"), name); GNUNET_NAMESTORE_zone_iterator_next (zc_ctx->it); return; } GNUNET_CRYPTO_short_hash_to_enc (&zc_ctx->zone, &shenc); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Zone `%s' iteration result `%s', %u records\n", &shenc, name, rd_count); gtk_tree_store_append (ts, &iter_name, NULL); gtk_tree_store_set (ts, &iter_name, GNS_TREESTORE_COL_NAME, name, GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE, GNS_TREESTORE_COL_RECORD_TYPE, GNUNET_NAMESTORE_TYPE_ANY, GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, _(NEW_RECORD_STR), GNS_TREESTORE_COL_IS_RECORD_ROW, TRUE, GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, TRUE, -1); /* Append elements for records */ for (c = 0; c < rd_count; c ++) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Record %u: type %u flags %u expiration %llu data_size %u\n", c, rd[c].record_type, rd[c].flags, rd[c].expiration_time, rd[c].data_size); /* Set public toggle */ public = ((rd[c].flags & GNUNET_NAMESTORE_RF_PRIVATE) != GNUNET_NAMESTORE_RF_PRIVATE); /* Expiration time */ time_is_relative = (0 != (rd[c].flags & GNUNET_NAMESTORE_RF_RELATIVE_EXPIRATION)); if (time_is_relative) { struct GNUNET_TIME_Relative rel_time; rel_time.rel_value = rd[c].expiration_time; exp_t = rel_time.rel_value; exp = GNUNET_STRINGS_relative_time_to_string (rel_time, GNUNET_NO); } else { struct GNUNET_TIME_Absolute exp_abs; exp_abs.abs_value = rd[c].expiration_time; exp_t = exp_abs.abs_value; exp = GNUNET_STRINGS_absolute_time_to_string (exp_abs); } /* value */ val = GNUNET_NAMESTORE_value_to_string (rd[c].record_type, rd[c].data, rd[c].data_size); if (NULL == val) GNUNET_asprintf (&val, "%s", EXPIRE_INVALID_STRING); if (NULL != GNUNET_NAMESTORE_number_to_typename (rd[c].record_type)) type_str = strdup (GNUNET_NAMESTORE_number_to_typename (rd[c].record_type)); else GNUNET_asprintf (&type_str, "%s", EXPIRE_INVALID_STRING); if ( (0 == strcmp (name, GNUNET_GNS_MASTERZONE_STR)) && (GNUNET_NAMESTORE_TYPE_PSEU == rd[c].record_type) ) { pseu_entry = GTK_ENTRY((GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_entry"))); gtk_entry_set_text (pseu_entry, val); } else { gtk_tree_store_insert_with_values (ts, &iter_record , &iter_name, 0, GNS_TREESTORE_COL_NAME, name, GNS_TREESTORE_COL_NAME_IS_VISIBLE, FALSE, GNS_TREESTORE_COL_RECORD_TYPE, rd[c].record_type, GNS_TREESTORE_COL_RECORD_TYPE_AS_STR, type_str, GNS_TREESTORE_COL_IS_PUBLIC, public, GNS_TREESTORE_COL_EXP_TIME, exp_t, GNS_TREESTORE_COL_EXP_TIME_AS_STR, exp, GNS_TREESTORE_COL_EXP_TIME_IS_REL, time_is_relative, GNS_TREESTORE_COL_VAL_AS_STR, val, GNS_TREESTORE_COL_IS_RECORD_ROW, TRUE, GNS_TREESTORE_COL_NOT_DUMMY_ROW, TRUE, -1); } GNUNET_free (type_str); GNUNET_free (val); } GNUNET_NAMESTORE_zone_iterator_next (zc_ctx->it); } /** * Function called upon completion of 'GNUNET_CRYPTO_rsa_key_create_async'. * Displays an error message upon failure, otherwise beings loading the * zone's information from the namestore. * * @param cls closure * @param pk NULL on error, otherwise the private key (which must be free'd by the callee) * @param emsg NULL on success, otherwise an error message */ static void zone_key_loaded_callback (void *cls, struct GNUNET_CRYPTO_RsaPrivateKey *pk, const char *emsg) { struct ZoneIteration_Context *zc_ctx = cls; struct GNUNET_CRYPTO_ShortHashAsciiEncoded shenc; char *label; GtkTreeIter toplevel; zc_ctx->rkgc = NULL; if (NULL == pk) { GNUNET_CONTAINER_DLL_remove (zc_head, zc_tail, zc_ctx); GNUNET_free (zc_ctx); show_error_message (_("Failed to load zone"), gettext(emsg)); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_zone_selection_hbuttonbox"))); return; } pkey = pk; GNUNET_CRYPTO_rsa_key_get_public (pkey, &pubkey); GNUNET_CRYPTO_short_hash (&pubkey, sizeof (struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded), &zone); GNUNET_CRYPTO_short_hash_to_enc(&zone, &shenc); zone_as_string = GNUNET_strdup ((char *) &shenc); label = g_markup_printf_escaped (_("Editing zone %s"), zone_as_string); gtk_label_set_markup (GTK_LABEL (GNUNET_SETUP_get_object ("GNUNET_setup_gns_zone_label")), label); g_free (label); /* Append a top level row and leave it empty */ gtk_tree_store_insert_with_values (ts, &toplevel, NULL, 0, GNS_TREESTORE_COL_NAME, _(NEW_NAME_STR), GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE, GNS_TREESTORE_COL_RECORD_TYPE, GNUNET_DNSPARSER_TYPE_A, GNS_TREESTORE_COL_IS_RECORD_ROW, FALSE, GNS_TREESTORE_COL_NOT_DUMMY_ROW, FALSE, GNS_TREESTORE_COL_TYPE_IS_EDITABLE, TRUE, -1); /* Load zone from namestore! */ zc_ctx->zone = zone; zc_ctx->it = GNUNET_NAMESTORE_zone_iteration_start (namestore, &zone, GNUNET_NAMESTORE_RF_RELATIVE_EXPIRATION, GNUNET_NAMESTORE_RF_NONE, &zone_iteration_proc, zc_ctx); } /** * Stop a zone iteration. * * @param zc_ctx zone iteration to stop. */ static void abort_zone_iteration (struct ZoneIteration_Context *zc_ctx) { if (NULL != zc_ctx->rkgc) GNUNET_CRYPTO_rsa_key_create_stop (zc_ctx->rkgc); if (NULL != zc_ctx->it) GNUNET_NAMESTORE_zone_iteration_stop (zc_ctx->it); GNUNET_CONTAINER_DLL_remove (zc_head, zc_tail, zc_ctx); GNUNET_free (zc_ctx); } /** * Load a particular zone into the main tree view. * * @param zonename name of the option in the configuration file * with the name of the file with the private key of the * zone to load */ static void load_zone (const char *zonename) { char *keyfile; struct ZoneIteration_Context *zc_ctx; char *emsg; /* clear previous zone */ current_zone_option = zonename; gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_zone_selection_hbuttonbox"))); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_status_label"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_main_scrolledwindow"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_image"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_saveas_button"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_qr_vseparator"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_pseu_hbox"))); gtk_tree_store_clear (ts); /* setup crypto keys */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "gns", zonename, &keyfile)) { GNUNET_asprintf (&emsg, _("Option `%s' missing in section `%s'\n"), zonename, "gns"); show_error_message (_("Failed to load zone"), emsg); GNUNET_free (emsg); gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_zone_selection_hbuttonbox"))); return; } while (NULL != (zc_ctx = zc_head)) abort_zone_iteration (zc_head); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Using `%s'\n", keyfile); zc_ctx = GNUNET_malloc (sizeof (struct ZoneIteration_Context)); GNUNET_CONTAINER_DLL_insert (zc_head, zc_tail, zc_ctx); zc_ctx->rkgc = GNUNET_CRYPTO_rsa_key_create_start (keyfile, &zone_key_loaded_callback, zc_ctx); GNUNET_free (keyfile); } /** * A different zone was selected in the zone toggle bar. Load the * appropriate zone. * * @param togglebutton button that was toggled (could be to "on" or "off", we only react to "on") * @param user_data builder, unused */ void GNUNET_setup_gns_shorten_zone_selection_radiobutton_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data) { if (gtk_toggle_button_get_active (togglebutton)) load_zone ("SHORTEN_ZONEKEY"); } /** * A different zone was selected in the zone toggle bar. Load the * appropriate zone. * * @param togglebutton button that was toggled (could be to "on" or "off", we only react to "on") * @param user_data builder, unused */ void GNUNET_setup_gns_private_zone_selection_radiobutton_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data) { if (gtk_toggle_button_get_active (togglebutton)) load_zone ("PRIVATE_ZONEKEY"); } /** * A different zone was selected in the zone toggle bar. Load the * appropriate zone. * * @param togglebutton button that was toggled (could be to "on" or "off", we only react to "on") * @param user_data builder, unused */ void GNUNET_setup_gns_master_zone_selection_radiobutton_toggled_cb (GtkToggleButton *togglebutton, gpointer user_data) { if (gtk_toggle_button_get_active (togglebutton)) load_zone ("ZONEKEY"); } /** * Connect to the namestore and initialize the main * GNS tree view. */ void GNUNET_SETUP_gns_init () { gchar *label; GtkLabel *status_label; namestore = GNUNET_NAMESTORE_connect (cfg); if (NULL == namestore) { status_label = GTK_LABEL (GNUNET_SETUP_get_object ("GNUNET_setup_gns_status_label")); label = g_markup_printf_escaped (_("Failed to connect to namestore")); gtk_label_set_markup (status_label, label); g_free (label); return; } ts = GTK_TREE_STORE (GNUNET_SETUP_get_object ("GNUNET_setup_gns_treestore")); tv = GTK_TREE_VIEW (GNUNET_SETUP_get_object ("GNUNET_setup_gns_main_treeview")); tm = GTK_TREE_MODEL (ts); load_zone ("ZONEKEY"); } /** * Disconnect from the namestore and clean up the main * GNS tree view. */ void GNUNET_SETUP_gns_done () { gtk_widget_show (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_status_label"))); gtk_widget_hide (GTK_WIDGET (GNUNET_SETUP_get_object ("GNUNET_setup_gns_main_scrolledwindow"))); gtk_tree_store_clear (ts); while (NULL != zc_head) abort_zone_iteration (zc_head); while (NULL != uc_head) free_update_context (uc_head); while (NULL != rc_head) free_remove_context (rc_head); if (NULL != namestore) { GNUNET_NAMESTORE_disconnect (namestore); namestore = NULL; } if (NULL != pkey) { GNUNET_CRYPTO_rsa_key_free (pkey); pkey = NULL; } if (NULL != current_pseudonym) { GNUNET_free (current_pseudonym); current_pseudonym = NULL; } } /* end of gnunet-setup-gns.c */