/* This file is part of GNUnet Copyright (C) 2012, 2013, 2014, 2018 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 src/namestore/gnunet-namestore-gtk.c * @author Christian Grothoff * @brief edit GNS zones */ #include "gnunet_gtk.h" #include "gnunet_gtk_namestore_plugin.h" #include #include #include #include #include /** * Text we use for the 'name' entry for the user to select * for creating a new name. */ #define NEW_ZONE_STR gettext_noop ("") /** * 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 /** * How many notifications do we allow the namestore to send us * before we need to start to catch up? */ #define NAMESTORE_MONITOR_WINDOW_SIZE 50 /** * 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, /** * A gboolean; TRUE if the name is editable (dummy line). */ GNS_TREESTORE_COL_NAME_IS_EDITABLE, /** * A guint; offset of this record in the array. */ GNS_TREESTORE_COL_RECORD_OFFSET }; /** * 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 }; /** * Columns in the zone list store. */ enum ZONE_COLUMNS { /** * A gchararray */ ZONE_LS_NAME = 0, /** * A `struct GNUNET_IDENTITY_Ego` */ ZONE_LS_EGO = 1 }; /** * Closure for #operation_done_cont(). */ struct OperationContext { /** * Kept in a DLL. */ struct OperationContext *next; /** * Kept in a DLL. */ struct OperationContext *prev; /** * Associated namestore operation. */ struct GNUNET_NAMESTORE_QueueEntry *qe; }; /** * Closure for #merge_with_existing_records(). */ struct MoveOperationContext { /** * Kept in a DLL. */ struct MoveOperationContext *next; /** * Kept in a DLL. */ struct MoveOperationContext *prev; /** * Associated namestore operation. */ struct GNUNET_NAMESTORE_ZoneIterator *it; /** * Info from editing dialog. */ struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc; /** * Private key of target zone. */ struct GNUNET_CRYPTO_PrivateKey pk; /** * Data to free. */ void *data; /** * Record to merge. */ struct GNUNET_GNSRECORD_Data rd; }; /** * Information we keep per name. */ struct RecordInfo { /** * Name for this record. */ char *name; /** * Location of this record in the tree view. */ GtkTreeRowReference *rr; /** * Serialized records under this name. */ void *data; /** * Number of bytes in @e data. */ size_t data_size; /** * Number of records serialized in @e data. */ unsigned int rd_count; }; /** * Representation of a TLD, mapping the respective TLD string * (i.e. ".gnu") to the respective public key of the zone. */ struct GNS_TopLevelDomain { /** * Kept in a DLL, as there are unlikely enough of these to * warrant a hash map. */ struct GNS_TopLevelDomain *next; /** * Kept in a DLL, as there are unlikely enough of these to * warrant a hash map. */ struct GNS_TopLevelDomain *prev; /** * Public key associated with the @a tld. */ struct GNUNET_CRYPTO_EddsaPublicKey pkey; /** * Top-level domain as a string, including leading ".". */ char *tld; }; /** * Head of DLL of TLDs we map to GNS zones. */ static struct GNS_TopLevelDomain *tld_head; /** * Tail of DLL of TLDs we map to GNS zones. */ static struct GNS_TopLevelDomain *tld_tail; /** * When do NICK records expire? */ static struct GNUNET_TIME_Relative nick_expiration_time; /** * Hash map from the H(name) in the zone to the 'struct RecordInfo' * for the respective entry in the tree view. */ static struct GNUNET_CONTAINER_MultiHashMap *n2r; /** * Our current zone monitor. */ static struct GNUNET_NAMESTORE_ZoneMonitor *zmon; /** * Head of linked list of active operations. */ static struct OperationContext *oc_head; /** * Tail of linked list of active operations. */ static struct OperationContext *oc_tail; /** * Head of linked list of active operations. */ static struct MoveOperationContext *moc_head; /** * Tail of linked list of active operations. */ static struct MoveOperationContext *moc_tail; /** * 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 const struct GNUNET_CRYPTO_PrivateKey *pkey; /** * Public key of the zone we are currently editing. */ static struct GNUNET_CRYPTO_PublicKey pubkey; /** * Pseudonym of the current zone we are editing. */ static char *current_pseudonym; /** * Currently selected entry in #zone_liststore. */ static GtkTreeIter zone_iter; /** * List of all known zones/egos. */ static GtkListStore *zone_liststore; /** * Connection to identity service. */ static struct GNUNET_IDENTITY_Handle *identity; /** * Main loop handle. */ static struct GNUNET_GTK_MainLoop *ml; /** * Main window. */ static GtkWidget *main_window; /** * Status label in main window. */ static GtkLabel *status_label; /** * Zone combo box in the main window. */ static GtkComboBox *zone_combo_box; /** * Our configuration. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Ongoing identity operation. */ static struct GNUNET_IDENTITY_Operation *iop; /** * Global return value (for success/failure of gnunet-setup). */ static int gret; /** * Get an object from the main window. * * @param name name of the object * @return NULL on error, otherwise the object */ static GObject * get_object (const char *name) { if (NULL == ml) return NULL; return GNUNET_GTK_main_loop_get_object (ml, name); } #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; GdkPixbuf *pb; guchar *pixels; int n_channels; const char *dir; char *fn; unsigned int size; char *upper; qri = QRinput_new2 (0, QR_ECLEVEL_M); if (NULL == qri) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_new2"); return NULL; } GNUNET_asprintf (&str, "gnunet://gns/%s/%s", GNUNET_GNSRECORD_z2s (&pubkey), current_pseudonym); upper = GNUNET_strdup (str); GNUNET_STRINGS_utf8_toupper (str, upper); /* first try encoding as uppercase-only alpha-numerical QR code (much smaller encoding); if that fails, also try using binary encoding (in case nick contains special characters). */ if ((0 != QRinput_append (qri, QR_MODE_AN, strlen (upper), (unsigned char *) upper)) && (0 != QRinput_append (qri, QR_MODE_8, strlen (str), (unsigned char *) str))) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "QRinput_append"); GNUNET_free (upper); GNUNET_free (str); return NULL; } GNUNET_free (upper); 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) { QRcode_free (qrc); QRinput_free (qri); return NULL; } pixels = gdk_pixbuf_get_pixels (pb); n_channels = gdk_pixbuf_get_n_channels (pb); for (unsigned int x = 0; x < size; x++) for (unsigned int y = 0; y < size; y++) { unsigned int off = (x * qrc->width / size) + (y * qrc->width / size) * qrc->width; for (int 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; GdkScreen *screen; GtkSettings *settings; gint dpi; int scale; image = GTK_IMAGE (get_object ("gnunet_namestore_gtk_qr_image")); if (NULL == image) { GNUNET_break (0); return; } /* adjust scale to screen resolution */ screen = gtk_widget_get_screen (GTK_WIDGET (image)); settings = gtk_settings_get_for_screen (screen); g_object_get (G_OBJECT (settings), "gtk-xft-dpi", &dpi, NULL); if (-1 == dpi) scale = 2; else if (dpi >= 122800) scale = 4; else if (dpi >= 98304) scale = 3; else scale = 2; pb = create_qrcode (scale); if (NULL == pb) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to initialize QR-code pixbuf\n")); 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_namestore_gtk_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. * * @param button the 'save as' button * @param user_data unused */ void gnunet_namestore_gtk_qr_saveas_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkBuilder *builder; GtkWindow *dialog; char *suggestion; builder = GNUNET_GTK_get_new_builder ("gnunet_namestore_gtk_qr_save_as_dialog.glade", NULL); if (NULL == builder) { GNUNET_break (0); return; } GNUNET_asprintf (&suggestion, "%s.png", current_pseudonym); dialog = GTK_WINDOW ( gtk_builder_get_object (builder, "gnunet_namestore_gtk_qr_save_as_dialog")); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), suggestion); GNUNET_free (suggestion); gtk_window_present (dialog); } /** * Check if adding a record of the given type is Ok given the other * records already present for the given name. * * @param rd_count size of the @a rd array * @param rd existing records * @param n_type new record to be added * @return #GNUNET_OK if adding this record is OK, #GNUNET_NO if not */ static int check_record_permitted (unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd, gint n_type) { unsigned int nick; nick = 0; for (unsigned int i = 0; i < rd_count; i++) { switch (rd[i].record_type) { case GNUNET_DNSPARSER_TYPE_CNAME: return GNUNET_NO; case GNUNET_GNSRECORD_TYPE_PKEY: return GNUNET_NO; case GNUNET_GNSRECORD_TYPE_GNS2DNS: return GNUNET_NO; case GNUNET_GNSRECORD_TYPE_NICK: nick++; break; default: break; } } rd_count -= nick; if (0 == rd_count) return GNUNET_OK; switch (n_type) { case GNUNET_DNSPARSER_TYPE_CNAME: return GNUNET_NO; case GNUNET_GNSRECORD_TYPE_PKEY: return GNUNET_NO; case GNUNET_GNSRECORD_TYPE_GNS2DNS: return GNUNET_NO; default: break; } return GNUNET_OK; } /** * Function called upon completion of an operation. * * @param cls the `struct OperationContext` of the operation that completed * @param ec the error code */ static void operation_done_cont (void *cls, enum GNUNET_ErrorCode ec) { struct OperationContext *oc = cls; GtkWidget *dialog; oc->qe = NULL; GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc); if (GNUNET_EC_NONE != ec) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Operation failed: `%s'\n"), GNUNET_ErrorCode_get_hint (ec)); dialog = gtk_message_dialog_new (GTK_WINDOW (main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _ ("Operation failed: `%s'\n"), GNUNET_ErrorCode_get_hint (ec)); g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog); gtk_widget_show_all (dialog); } GNUNET_free (oc); } /** * Release the record info. * * @param cls NULL * @param key key for @a value in the #n2r map * @param value a RecordInfo to release * @return #GNUNET_OK (continue to iterate) */ static int release_ri (void *cls, const struct GNUNET_HashCode *key, void *value) { struct RecordInfo *ri = value; (void) cls; gtk_tree_row_reference_free (ri->rr); GNUNET_free (ri->data); GNUNET_free (ri->name); GNUNET_break (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove (n2r, key, ri)); GNUNET_free (ri); return GNUNET_OK; } /** * Clear all entries in the zone view and hide the tree view. */ static void clear_zone_view () { GNUNET_free (current_pseudonym); current_pseudonym = NULL; GNUNET_CONTAINER_multihashmap_iterate (n2r, &release_ri, NULL); gtk_widget_hide (GTK_WIDGET (zone_combo_box)); gtk_widget_show (GTK_WIDGET (status_label)); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow"))); gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator"))); gtk_tree_store_clear (ts); } /** * 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) { GtkDialog *dialog; /* TODO: consider replacing with widget in the main window */ dialog = GTK_DIALOG (gtk_message_dialog_new (GTK_WINDOW (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 an edit dialog context. * * @param edc resources to free */ static void free_edit_dialog_context (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { g_free (edc->name); g_free (edc->n_value); g_free (edc->new_zone_option); GNUNET_free (edc); } /** * Process a record that was stored in the namestore. * * @param cls closure * @param rd_count number of entries in @a rd array * @param rd array of records with data to store */ static void merge_with_existing_records (void *cls, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct MoveOperationContext *moc = cls; struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = moc->edc; struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1]; struct OperationContext *oc; struct GNUNET_NAMESTORE_RecordInfo ri; unsigned int res; GNUNET_CONTAINER_DLL_remove (moc_head, moc_tail, moc); if (GNUNET_OK != check_record_permitted (rd_count, rd, edc->record_type)) { show_error_message ( _ ("Record combination not permitted"), _ ( "Given the existing records, adding a new record of this type is not allowed.")); GNUNET_free (moc->data); GNUNET_free (moc); free_edit_dialog_context (edc); return; } memcpy (rd_new, rd, rd_count * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[rd_count] = moc->rd; /* FIXME: sanity-check merge... */ oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = edc->name; ri.a_rd_count = rd_count + 1; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, &moc->pk, 1, &ri, &res, &operation_done_cont, oc); GNUNET_free (moc->data); GNUNET_free (moc); free_edit_dialog_context (edc); } /** * Process an encrypted that was stored in the namestore for * merging. * * @param cls closure with the `struct MoveOperationContext` * @param zone private key of the zone; NULL on disconnect * @param label label of the records; NULL on disconnect * @param rd_count number of entries in @a rd array, 0 if label was deleted * @param rd array of records with data to store */ static void handle_records_for_merge (void *cls, const struct GNUNET_CRYPTO_PrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct MoveOperationContext *moc = cls; if (0 != strcmp (label, moc->edc->name)) { GNUNET_NAMESTORE_zone_iterator_next (moc->it, 1); return; } GNUNET_NAMESTORE_zone_iteration_stop (moc->it); moc->it = NULL; merge_with_existing_records (moc, rd_count, rd); } /** * Error communicating with database, complain to user. * * @param cls the `struct MoveOperationContext` */ static void handle_records_for_merge_error (void *cls) { struct MoveOperationContext *moc = cls; struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = moc->edc; show_error_message (_ ("Failed to communicate with database"), _ ("Check that your peer is configured correctly.")); GNUNET_free (moc->data); GNUNET_free (moc); free_edit_dialog_context (edc); } /** * Finished zone iteration. Proceed. * * @param cls the `struct MoveOperationContext` */ static void handle_records_for_merge_end (void *cls) { struct MoveOperationContext *moc = cls; moc->it = NULL; merge_with_existing_records (moc, 0, NULL); } /** * The edit dialog completed; update the namestore and the * view based on the new values in @a edc. * * @param edc editing context information * @param ret return code of the dialog */ static void edit_dialog_continuation (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc, GtkResponseType ret) { struct RecordInfo *ric = edc->ri; unsigned int rd_count = (NULL == ric) ? 0 : ric->rd_count; struct GNUNET_GNSRECORD_Data rd_old[rd_count]; struct GNUNET_GNSRECORD_Data rd; void *data; size_t data_size; struct OperationContext *oc; struct GNUNET_NAMESTORE_RecordInfo ri; unsigned int res; if (NULL != edc->plugin) { GNUNET_PLUGIN_unload (edc->liblow, edc->plugin); edc->plugin = NULL; } if (NULL != edc->liblow) { GNUNET_free (edc->liblow); edc->liblow = NULL; } if ((NULL != ric) && (GNUNET_OK != GNUNET_GNSRECORD_records_deserialize (ric->data_size, ric->data, ric->rd_count, rd_old))) { GNUNET_break (0); free_edit_dialog_context (edc); return; } if ((GTK_RESPONSE_CANCEL == ret) || (GTK_RESPONSE_DELETE_EVENT == ret)) { free_edit_dialog_context (edc); return; } if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (edc->record_type, edc->n_value, &data, &data_size)) { GNUNET_break (0); free_edit_dialog_context (edc); return; } if (edc->n_public) rd.flags = GNUNET_GNSRECORD_RF_NONE; else rd.flags = GNUNET_GNSRECORD_RF_PRIVATE; if (edc->n_is_shadow) rd.flags |= GNUNET_GNSRECORD_RF_SHADOW; rd.record_type = edc->record_type; rd.expiration_time = edc->n_exp_time; if (edc->n_is_relative) rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; rd.data_size = data_size; rd.data = data; switch (ret) { case GTK_RESPONSE_REJECT: /* code for 'delete' */ if (GNUNET_YES == edc->old_record_in_namestore) { /* remove item from tree view and namestore */ struct GNUNET_GNSRECORD_Data rd_new[rd_count - 1]; GNUNET_assert (NULL != ric); memcpy (rd_new, rd_old, (rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[edc->off] = rd_old[rd_count - 1]; oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = edc->name; ri.a_rd_count = rd_count - 1; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } else { /* invalid choice, 'delete' should have been hidden... */ GNUNET_break (0); } break; case GTK_RESPONSE_OK: /* update model */ if (0 == strcmp (edc->new_zone_option, current_pseudonym)) { if (GNUNET_YES == edc->old_record_in_namestore) { struct GNUNET_GNSRECORD_Data rd_new[rd_count]; GNUNET_assert (NULL != ric); memcpy (rd_new, rd_old, rd_count * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[edc->off] = rd; oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = edc->name; ri.a_rd_count = rd_count; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } else { struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1]; memcpy (rd_new, rd_old, rd_count * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[rd_count] = rd; oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = edc->name; ri.a_rd_count = rd_count + 1; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } } else { const struct GNUNET_CRYPTO_PrivateKey *pk; struct MoveOperationContext *moc; struct GNUNET_HashCode query; /* determine target zone */ GNUNET_assert (NULL != edc->ego); pk = GNUNET_IDENTITY_ego_get_private_key (edc->ego); GNUNET_assert (NULL != pk); GNUNET_GNSRECORD_query_from_private_key (pk, edc->name, &query); moc = GNUNET_new (struct MoveOperationContext); moc->data = data; moc->rd = rd; moc->edc = edc; moc->pk = *pk; GNUNET_CONTAINER_DLL_insert (moc_head, moc_tail, moc); moc->it = GNUNET_NAMESTORE_zone_iteration_start (namestore, pk, &handle_records_for_merge_error, moc, &handle_records_for_merge, moc, &handle_records_for_merge_end, moc); /* zone changed, remove record from old zone, add to new zone! */ if (GNUNET_YES == edc->old_record_in_namestore) { /* remove item from tree view and namestore */ struct GNUNET_GNSRECORD_Data rd_new[rd_count - 1]; GNUNET_assert (NULL != ric); memcpy (rd_new, rd_old, (rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[edc->off] = rd_old[rd_count - 1]; oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = edc->name; ri.a_rd_count = rd_count - 1; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } return; } break; default: GNUNET_break (0); break; } GNUNET_free (data); free_edit_dialog_context (edc); } /** * Disable 'save' button, dialog state is not acceptable. * * @param edc dialog to modify */ static void edit_dialog_disable_save (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { gtk_widget_set_sensitive (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_save_button")), FALSE); } /** * Enable 'save' button, dialog state is acceptable. * * @param edc dialog to modify */ static void edit_dialog_enable_save (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { gtk_widget_set_sensitive (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_save_button")), TRUE); } /** * Function that should be called by the plugin whenever values in * the dialog were edited. It will check the validity of the dialog * and update the "save" button accordingly. * * @param edc the plugin environment */ static void check_validity (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { GtkEditable *entry; const gchar *name; /* check name */ entry = GTK_EDITABLE ( gtk_builder_get_object (edc->builder, "edit_dialog_name_entry")); name = gtk_editable_get_chars (entry, 0, -1); if ((GNUNET_OK != edc->plugin->validate (edc->plugin->cls, edc->builder)) || ((GNUNET_SYSERR == GNUNET_DNSPARSER_check_label (name)) && (0 != strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT)))) { edit_dialog_disable_save (edc); return; } edit_dialog_enable_save (edc); } /** * Setup the zone combobox. * * @param edc dialog to setup the combo box for */ static void setup_zone (struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { GtkTreeIter iter; GtkComboBox *cb; gchar *name; cb = GTK_COMBO_BOX ( gtk_builder_get_object (edc->builder, "edit_dialog_zone_combobox")); gtk_combo_box_set_model (cb, GTK_TREE_MODEL (edc->zone_liststore)); GNUNET_assert ( gtk_tree_model_get_iter_first (GTK_TREE_MODEL (edc->zone_liststore), &iter)); do { gtk_tree_model_get (GTK_TREE_MODEL (edc->zone_liststore), &iter, ZONE_LS_NAME, &name, -1); if (0 == strcmp (name, edc->new_zone_option)) { g_free (name); break; } g_free (name); } while ( gtk_tree_model_iter_next (GTK_TREE_MODEL (edc->zone_liststore), &iter)); gtk_combo_box_set_active_iter (cb, &iter); } /** * Initialize widgets of the edit dialog that are the same regardless of * the type of the record. * * @param edc dialog context */ static void edit_dialog_setup_common_elements ( struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { GtkComboBox *cb; GtkListStore *ls; GtkTreeIter iter; struct GNUNET_TIME_Absolute at; struct GNUNET_TIME_Relative rt; time_t tp; struct tm *ymd; GtkCalendar *cal; if (GNUNET_YES != edc->old_record_in_namestore) { gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_delete_button"))); edit_dialog_disable_save (edc); } gtk_entry_set_text (GTK_ENTRY ( gtk_builder_get_object (edc->builder, "edit_dialog_name_entry")), edc->name); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_options_public_checkbutton")), edc->n_public); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_options_shadow_checkbutton")), edc->n_is_shadow); if (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us == edc->n_exp_time) { gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_never_radiobutton")), TRUE); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_calendar"))); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_hbox"))); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_combobox"))); } if ((edc->n_is_relative) && (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us != edc->n_exp_time)) { gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_radiobutton")), TRUE); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_calendar"))); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_hbox"))); rt.rel_value_us = edc->n_exp_time; } else { /* select a sane default */ rt = GNUNET_TIME_UNIT_DAYS; } cb = GTK_COMBO_BOX ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_combobox")); ls = GTK_LIST_STORE (gtk_combo_box_get_model (cb)); gtk_list_store_insert_with_values ( ls, &iter, -1 /* position: append */, 0, GNUNET_STRINGS_relative_time_to_string (rt, GNUNET_NO), -1); gtk_combo_box_set_active_iter (cb, &iter); if ((! edc->n_is_relative) && (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us != edc->n_exp_time)) { gtk_toggle_button_set_active ( GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_radiobutton")), TRUE); gtk_widget_hide (GTK_WIDGET ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_combobox"))); at.abs_value_us = edc->n_exp_time; } else { /* select a sane default: right now */ at = GNUNET_TIME_absolute_get (); } tp = (time_t) (at.abs_value_us / 1000000LL); /* convert to seconds */ ymd = gmtime (&tp); cal = GTK_CALENDAR ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_calendar")); gtk_calendar_select_month (cal, ymd->tm_mon, ymd->tm_year + 1900); gtk_calendar_mark_day (cal, ymd->tm_mday); gtk_spin_button_set_value ( GTK_SPIN_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_hours_spinbutton")), (double) ymd->tm_hour); gtk_spin_button_set_value ( GTK_SPIN_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_expiration_absolute_minutes_spinbutton")), (double) ymd->tm_min); gtk_spin_button_set_value ( GTK_SPIN_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_expiration_absolute_seconds_spinbutton")), (double) ymd->tm_sec); } /** * Perform the reverse of the #edit_dialog_setup_common_elements() function, * that is, extract the values from the (common) widgets and store the * values in @a edc. * * @param edc edit dialog to extract data from */ static void edit_dialog_putes_common_elements ( struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc) { const char *rt_s; struct GNUNET_TIME_Relative rt; GtkComboBox *cb; GtkTreeModel *tm; GtkTreeIter iter; gchar *opt; struct GNUNET_IDENTITY_Ego *ego; /* is public flag */ edc->n_public = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( gtk_builder_get_object ( edc->builder, "edit_dialog_options_public_checkbutton"))); /* is shadow flag */ edc->n_is_shadow = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( gtk_builder_get_object ( edc->builder, "edit_dialog_options_shadow_checkbutton"))); /* 'forever' expiration time */ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_never_radiobutton")))) { edc->n_exp_time = GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us; edc->n_is_relative = TRUE; /* doesn't matter, but make sure it is well-defined anyway */ } /* 'relative' expiration time */ if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_radiobutton")))) { cb = GTK_COMBO_BOX ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_relative_combobox")); tm = gtk_combo_box_get_model (cb); if (! gtk_combo_box_get_active_iter (cb, &iter)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &iter, 0, &rt_s, -1); GNUNET_break (GNUNET_YES == GNUNET_STRINGS_fancy_time_to_relative (rt_s, &rt)); edc->n_exp_time = rt.rel_value_us; edc->n_is_relative = TRUE; } /* 'absolute' expiration time */ if ( gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_radiobutton")))) { guint year; guint month; guint day; guint hour; guint minute; guint second; char fancydate[128]; struct GNUNET_TIME_Absolute atime; gtk_calendar_get_date (GTK_CALENDAR (gtk_builder_get_object ( edc->builder, "edit_dialog_expiration_absolute_calendar")), &year, &month, &day); hour = gtk_spin_button_get_value (GTK_SPIN_BUTTON ( gtk_builder_get_object (edc->builder, "edit_dialog_expiration_absolute_hours_spinbutton"))); minute = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_expiration_absolute_minutes_spinbutton"))); second = gtk_spin_button_get_value (GTK_SPIN_BUTTON (gtk_builder_get_object ( edc->builder, "edit_dialog_expiration_absolute_seconds_spinbutton"))); GNUNET_snprintf (fancydate, sizeof (fancydate), "%u-%u-%u %u:%u:%u", (unsigned int) year, (unsigned int) month + 1, (unsigned int) day, (unsigned int) hour, (unsigned int) minute, (unsigned int) second); GNUNET_break (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (fancydate, &atime)); edc->n_exp_time = atime.abs_value_us; edc->n_is_relative = FALSE; } /* extract target zone! */ cb = GTK_COMBO_BOX ( gtk_builder_get_object (edc->builder, "edit_dialog_zone_combobox")); tm = gtk_combo_box_get_model (cb); if (! gtk_combo_box_get_active_iter (cb, &iter)) { GNUNET_break (0); } else { gtk_tree_model_get (tm, &iter, ZONE_LS_NAME, &opt, ZONE_LS_EGO, &ego, -1); if (NULL == opt) { GNUNET_break (0); } else { g_free (edc->new_zone_option); edc->new_zone_option = g_strdup (opt); g_free (opt); edc->ego = ego; } } } /** * Editing dialog was closed, get the data and call the * continuation. * * @param dialog editing dialog * @param response_id action that caused the dialog to be closed * @param user_data the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment` */ static void edit_dialog_response_cb (GtkDialog *dialog, gint response_id, gpointer user_data) { struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data; if (GTK_RESPONSE_OK == response_id) { edit_dialog_putes_common_elements (edc); g_free (edc->n_value); edc->n_value = edc->plugin->store (edc->plugin->cls, edc->builder); } gtk_widget_destroy (GTK_WIDGET (edc->dialog)); g_object_unref (edc->builder); edc->builder = NULL; edit_dialog_continuation (edc, response_id); } /** * Callback invoked from #GNUNET_GTK_get_new_builder2 to give * us a chance to add symbols from the plugin. * * @param builder the builder under construction * @param object object to bind signal to * @param signal_name name of the signal * @param handler_name name of the handler * @param connect_object a GObject, if non-NULL, use g_signal_connect_object() * @param flags GConnectFlags to use * @param user_data the `struct GNUNET_GTK_NAMESTORE_PluginEnvironment *` */ static void add_symbols (GtkBuilder *builder, GObject *object, const gchar *signal_name, const gchar *handler_name, GObject *connect_object, GConnectFlags flags, gpointer user_data) { struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc = user_data; GCallback cb; cb = NULL; if (NULL != edc->plugin->symbols) { for (unsigned int i = 0; NULL != edc->plugin->symbols[i].name; i++) { if (0 == strcmp (handler_name, edc->plugin->symbols[i].name)) { cb = edc->plugin->symbols[i].cb; break; } } } if (NULL == cb) { GModule *m; m = g_module_open (NULL, 0); if (! g_module_symbol (m, handler_name, (void **) &cb)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to find handler `%s'\n"), handler_name); g_module_close (m); return; } g_module_close (m); } if (NULL != connect_object) g_signal_connect_object (object, signal_name, cb, connect_object, flags); else if (flags & G_CONNECT_SWAPPED) g_signal_connect_swapped (object, signal_name, cb, user_data); else if (flags & G_CONNECT_AFTER) g_signal_connect_after (object, signal_name, cb, user_data); else g_signal_connect (object, signal_name, cb, user_data); } /** * Launch a record editing dialog. * * @param n_type type of the record to edit * @param name name of the record to edit * @param ri record information for this name (can be NULL * if this is the first record for the name) * @param off offset of the record being edited in the * @a ri's list; UINT_MAX if this is a new record */ static void launch_edit_dialog (gint n_type, const char *name, struct RecordInfo *ri, unsigned int off) { struct GNUNET_GTK_NAMESTORE_PluginEnvironment *edc; const char *typename; typename = GNUNET_GNSRECORD_number_to_typename (n_type); if (NULL == typename) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Record type `%u' not supported by this installation\n", (unsigned int) n_type); return; } if (NULL == current_pseudonym) { GNUNET_break (0); return; } edc = GNUNET_new (struct GNUNET_GTK_NAMESTORE_PluginEnvironment); if ((NULL != ri) && (off < ri->rd_count)) { struct GNUNET_GNSRECORD_Data rd_old[ri->rd_count]; GNUNET_break (GNUNET_OK == GNUNET_GNSRECORD_records_deserialize (ri->data_size, ri->data, ri->rd_count, rd_old)); edc->n_value = GNUNET_GNSRECORD_value_to_string (n_type, rd_old[off].data, rd_old[off].data_size); edc->n_public = (0 == (rd_old[off].flags & GNUNET_GNSRECORD_RF_PRIVATE)); edc->n_is_relative = (0 != (rd_old[off].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)); edc->n_is_shadow = (0 != (rd_old[off].flags & GNUNET_GNSRECORD_RF_SHADOW)); edc->n_exp_time = rd_old[off].expiration_time; edc->old_record_in_namestore = GNUNET_YES; } else { edc->n_exp_time = 1000000LL * (time (NULL) + 365 * 24 * 60 * 60); /* + 1y */ } edc->ri = ri; edc->off = off; edc->name = GNUNET_strdup (name); edc->new_zone_option = g_strdup (current_pseudonym); edc->zone_liststore = zone_liststore; edc->check_validity = &check_validity; GNUNET_asprintf (&edc->liblow, "libgnunet_plugin_gtk_namestore_%s", typename); GNUNET_STRINGS_utf8_tolower (edc->liblow, edc->liblow); edc->plugin = GNUNET_PLUGIN_load (edc->liblow, edc); if (NULL == edc->plugin) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to load plugin for record type %d\n"), (int) n_type); edit_dialog_continuation (edc, GTK_RESPONSE_CANCEL); return; } if (0 != edc->plugin->record_type) edc->record_type = edc->plugin->record_type; else edc->record_type = n_type; edc->builder = GNUNET_GTK_get_new_builder2 (edc->plugin->dialog_glade_filename, edc, &add_symbols); if (NULL == edc->builder) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Failed to load dialog resource `%s'\n"), edc->plugin->dialog_glade_filename); edit_dialog_continuation (edc, GTK_RESPONSE_CANCEL); return; } if (GNUNET_YES == edc->old_record_in_namestore) { edc->plugin->load (edc->plugin->cls, edc->n_value, edc->builder); } edc->dialog = GTK_DIALOG ( gtk_builder_get_object (edc->builder, edc->plugin->dialog_widget_name)); g_signal_connect (edc->dialog, "response", G_CALLBACK (&edit_dialog_response_cb), edc); edit_dialog_setup_common_elements (edc); setup_zone (edc); gtk_dialog_set_default_response (edc->dialog, GTK_RESPONSE_OK); gtk_window_present (GTK_WINDOW (edc->dialog)); } /** * Check if, with the given record info, we are allowed to add * another record of the given type. * * @param ri existing records * @param n_type type of the new record * @return #GNUNET_OK if this is allowed */ static int check_permissions (struct RecordInfo *ri, guint n_type) { struct GNUNET_GNSRECORD_Data rd[ri->rd_count]; GNUNET_break (GNUNET_OK == GNUNET_GNSRECORD_records_deserialize (ri->data_size, ri->data, ri->rd_count, rd)); if (GNUNET_OK != check_record_permitted (ri->rd_count, rd, n_type)) { show_error_message ( _ ("Record combination not permitted"), _ ( "Given the existing records, adding a new record of this type is not allowed.\n" "CNAME and PKEY records cannot co-exist with other records.\n" "NS records in GNS can only co-exist with A and AAAA records.\n")); return GNUNET_NO; } return GNUNET_OK; } /** * User selected 'edit' in the popup menu. Edit the selected row. * * @param widget the GtkTreeView * @param user_data main window builder */ void gnunet_namestore_gtk_popup_edit_button_activate_cb (GtkWidget *widget, gpointer user_data) { GtkTreeSelection *sel; gint n_type; gchar *n_name; guint off; struct RecordInfo *ri; GtkTreeIter iter; struct GNUNET_HashCode name_hash; sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, NULL, &iter)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No row selected\n"); return; } /* clang-format off */ gtk_tree_model_get (tm, &iter, GNS_TREESTORE_COL_NAME, &n_name, GNS_TREESTORE_COL_RECORD_TYPE, &n_type, GNS_TREESTORE_COL_RECORD_OFFSET, &off, -1); /* clang-format off */ if (NULL == n_name) { GNUNET_break (0); return; } GNUNET_CRYPTO_hash (n_name, strlen (n_name), &name_hash); ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash); if ( (NULL == ri) || (off >= ri->rd_count) ) { GNUNET_break (0); g_free (n_name); return; } launch_edit_dialog (n_type, n_name, ri, off); g_free (n_name); } /** * The user has selected a new record type. Update the * model and then start the 'edit' dialog. * * @param text renderer updated renderer * @param path_string the path identifying the edited cell * @param iter selected position * @param user_data unused */ void gnunet_namestore_gtk_type_cellrenderercombo_changed_cb ( GtkCellRendererText *text, gchar *path_string, GtkTreeIter *iter, gpointer user_data) { GtkTreeIter it; guint type; char *name_str; struct GNUNET_HashCode name_hash; struct RecordInfo *ri; GtkTreeModel *types; types = GTK_TREE_MODEL (get_object ("gnunet_namestore_gtk_type_liststore")); gtk_tree_model_get (types, iter, GNS_TYPE_TO_NAME_LISTSTORE_COLUMN_TYPE, &type, -1); /* check if this is a new record */ if (! gtk_tree_model_get_iter_from_string (tm, &it, path_string)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NAME, &name_str, -1); if (NULL == name_str) { GNUNET_break (0); return; } GNUNET_CRYPTO_hash (name_str, strlen (name_str), &name_hash); ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash); if ( (NULL == ri) || (GNUNET_OK == check_permissions (ri, type)) ) launch_edit_dialog (type, name_str, ri, UINT_MAX); g_free (name_str); } /** * 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_namestore_gtk_name_cellrenderertext_edited_cb ( GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer user_data) { GtkTreeIter it; gboolean not_dummy; char *name; struct RecordInfo *ri; struct GNUNET_HashCode name_hash; GtkTreePath *tpath; 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)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ( "Name `%s' invalid for GNS/DNS (too long for a DNS label?)\n"), new_text); gtk_widget_error_bell (GTK_WIDGET (main_window)); return; } if (! gtk_tree_model_get_iter_from_string (tm, &it, path)) { GNUNET_break (0); return; } gtk_tree_model_get (tm, &it, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, GNS_TREESTORE_COL_NAME, &name, -1); GNUNET_break (! not_dummy); GNUNET_break (0 == strcmp (name, _ (NEW_NAME_STR))); g_free (name); GNUNET_CRYPTO_hash (new_text, strlen (new_text), &name_hash); ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash); if (NULL != ri) { GtkTreeSelection *sel; GtkTreePath *sel_path; sel_path = gtk_tree_row_reference_get_path (ri->rr); sel = gtk_tree_view_get_selection (tv); gtk_tree_selection_select_path (sel, sel_path); gtk_tree_path_free (sel_path); return; } /* change dummy line to new name, then add new dummy */ ri = GNUNET_new (struct RecordInfo); 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, GNS_TREESTORE_COL_NAME_IS_EDITABLE, FALSE, -1); tpath = gtk_tree_model_get_path (tm, &it); ri->rr = gtk_tree_row_reference_new (tm, tpath); ri->name = GNUNET_strdup (name); gtk_tree_path_free (tpath); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (n2r, &name_hash, ri, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); /* 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, GNS_TREESTORE_COL_NAME_IS_EDITABLE, TRUE, GNS_TREESTORE_COL_RECORD_OFFSET, UINT_MAX, -1); } /** * Create a context (popup) menu for the zone iteration treeview * (if applicable). * * @return NULL if no menu could be created */ static GtkMenu * 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 NULL; } 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 NULL; } popup = GTK_MENU (get_object ("gnunet_namestore_gtk_edit_popup_menu")); gtk_widget_show_all (GTK_WIDGET (popup)); return popup; } /** * 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 * need to be managed separately). * * @param widget the widget * @param user_data unused * @return TRUE if a menu was activated */ gboolean gnunet_namestore_gtk_treeview_popup_menu_cb (GtkWidget *widget, gpointer user_data) { GtkMenu *menu; menu = create_popup_menu (); if (NULL == menu) return FALSE; gtk_menu_popup_at_widget (menu, widget, GDK_GRAVITY_CENTER, GDK_GRAVITY_CENTER, NULL); return TRUE; } /** * 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_namestore_gtk_treeview_button_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GdkEventButton *event_button = (GdkEventButton *) event; GtkMenu *menu; /* Check for right click*/ if (NULL == widget) return FALSE; if ( (GDK_BUTTON_PRESS != event_button->type) || (3 != event_button->button) ) return FALSE; menu = create_popup_menu (); if (NULL == menu) return FALSE; gtk_menu_popup_at_pointer (menu, event); return TRUE; } /** * User pushed a key in the GtkTreeView. Check for 'del' and if so, delete * the currently selected row. * * @param widget originating widget * @param event event information * @param user_data unused * @return TRUE if the key was processed ('del') */ gboolean gnunet_namestore_gtk_treeview_key_press_event_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) { GdkEventKey *event_key = (GdkEventKey *) event; GtkTreeIter iter; GtkTreeSelection *sel; int not_dummy; GtkTreeIter parent; char *name; struct GNUNET_HashCode name_hash; struct RecordInfo *ric; struct OperationContext *oc; struct GNUNET_NAMESTORE_RecordInfo ri; unsigned int res; /* Check for delete key */ if ( (GDK_KEY_PRESS != event_key->type) || (GDK_KEY_Delete != event_key->keyval) ) return FALSE; sel = gtk_tree_view_get_selection (tv); if (! gtk_tree_selection_get_selected (sel, NULL, &iter)) return TRUE; /* nothing selected */ /* clang-format off */ gtk_tree_model_get (tm, &iter, GNS_TREESTORE_COL_NOT_DUMMY_ROW, ¬_dummy, GNS_TREESTORE_COL_NAME, &name, -1); /* clang-format on */ if (GNUNET_NO == not_dummy) { g_free (name); return TRUE; /* do not delete the dummy line */ } GNUNET_CRYPTO_hash (name, strlen (name), &name_hash); ric = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash); GNUNET_assert (NULL != ric); if ((gtk_tree_model_iter_parent (tm, &parent, &iter)) && (ric->rd_count > 0)) { struct GNUNET_GNSRECORD_Data rd_old[ric->rd_count]; struct GNUNET_GNSRECORD_Data rd_new[ric->rd_count - 1]; struct GNUNET_GNSRECORD_Data rd; unsigned int off; int n_type; gboolean n_public; guint64 n_exp_time; gboolean n_is_relative; gboolean n_is_shadow; char *n_value; /* Removing a single record */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Removing single record for name `%s'\n", name); GNUNET_break (GNUNET_OK == GNUNET_GNSRECORD_records_deserialize (ric->data_size, ric->data, ric->rd_count, rd_old)); /* clang-format off */ gtk_tree_model_get (tm, &iter, 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_IS_SHADOW, &n_is_shadow, GNS_TREESTORE_COL_VAL_AS_STR, &n_value, -1); /* clang-format on */ /* valid name */ if (n_public) rd.flags = GNUNET_GNSRECORD_RF_NONE; else rd.flags = GNUNET_GNSRECORD_RF_PRIVATE; if (n_is_relative) rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; if (n_is_shadow) rd.flags |= GNUNET_GNSRECORD_RF_SHADOW; rd.record_type = n_type; rd.expiration_time = n_exp_time; if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (n_type, n_value, (void **) &rd.data, &rd.data_size)) { /* can't remove, value invalid */ GNUNET_assert (0); g_free (n_value); g_free (name); return TRUE; } for (off = 0; off < ric->rd_count; off++) if (GNUNET_YES == GNUNET_GNSRECORD_records_cmp (&rd, &rd_old[off])) break; GNUNET_assert (off != ric->rd_count); memcpy (rd_new, rd_old, (ric->rd_count - 1) * sizeof (struct GNUNET_GNSRECORD_Data)); rd_new[off] = rd_old[ric->rd_count - 1]; oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = name; ri.a_rd_count = ric->rd_count - 1; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); g_free (n_value); } else { oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = name; ri.a_rd_count = 0; ri.a_rd = NULL; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } g_free (name); return TRUE; } /** * 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_namestore_gtk_public_key_copy_button_clicked_cb (GtkButton *button, gpointer user_data) { GtkClipboard *cb; if (NULL == pkey) return; cb = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); gtk_clipboard_set_text (cb, GNUNET_GNSRECORD_z2s (&pubkey), -1); } /** * Function called once our model should be consistent with * the current zone. Makes the view visible. * * @param cls NULL */ static void zone_sync_proc (void *cls) { (void) cls; gtk_tree_view_set_model (tv, tm); #if HAVE_QRENCODE_H setup_qrcode (); gtk_widget_show (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image"))); gtk_widget_show ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button"))); gtk_widget_show ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator"))); #else gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator"))); #endif gtk_widget_hide (GTK_WIDGET (status_label)); gtk_widget_show ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow"))); gtk_widget_show (GTK_WIDGET (zone_combo_box)); } /** * Function called once our model is again inconsistent with * the current zone. Hides the view. * * @param cls NULL */ static void zone_iteration_error (void *cls) { (void) cls; clear_zone_view (); #if HAVE_QRENCODE_H setup_qrcode (); gtk_widget_hide (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button"))); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator"))); #else gtk_widget_show (GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_image"))); gtk_widget_show ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_saveas_button"))); gtk_widget_show ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_qr_vseparator"))); #endif } /** * A set of records is completely empty, remove it * from the hashmap and free associated memory. * * @param ri record info to release */ static void free_ri (struct RecordInfo *ri) { GtkTreePath *path; GtkTreeIter iter_name; struct GNUNET_HashCode name_hash; path = gtk_tree_row_reference_get_path (ri->rr); GNUNET_assert (gtk_tree_model_get_iter (tm, &iter_name, path)); gtk_tree_path_free (path); gtk_tree_store_remove (ts, &iter_name); GNUNET_CRYPTO_hash (ri->name, strlen (ri->name), &name_hash); release_ri (NULL, &name_hash, ri); } /** * Function called for each record in the current zone. Update the * widgets accordingly. * * @param cls NULL * @param zone_key private key of the zone * @param name name that is being mapped (at most 255 characters long) * @param rd_count number of entries in @a rd array * @param rd array of records with data to store */ static void zone_iteration_proc (void *cls, const struct GNUNET_CRYPTO_PrivateKey *zone_key, const char *name, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { GtkTreeRowReference *rr; GtkTreePath *path; GtkTreeIter iter_name; GtkTreeIter iter_record; const char *exp; char *val; char *type_str; gboolean time_is_relative; gboolean is_public; gboolean is_shadow; guint64 exp_t; struct GNUNET_HashCode name_hash; struct RecordInfo *ri; GtkTreeSelection *sel; GtkTreeIter sel_iter; GtkTreePath *sel_path; (void) cls; if (0 != GNUNET_memcmp (zone_key, pkey)) { GNUNET_break (0); return; } GNUNET_NAMESTORE_zone_monitor_next (zmon, 1); if ((GNUNET_SYSERR == GNUNET_DNSPARSER_check_name (name)) && (0 != strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT))) { GNUNET_break (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Got invalid record name `%s' from namestore\n"), name); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Zone iteration result `%s', %u records\n", name, rd_count); GNUNET_CRYPTO_hash (name, strlen (name), &name_hash); sel = gtk_tree_view_get_selection (tv); if (gtk_tree_selection_get_selected (sel, NULL, &sel_iter)) sel_path = gtk_tree_model_get_path (tm, &sel_iter); else sel_path = NULL; ri = GNUNET_CONTAINER_multihashmap_get (n2r, &name_hash); if (NULL != ri) { rr = ri->rr; path = gtk_tree_row_reference_get_path (rr); GNUNET_assert (gtk_tree_model_get_iter (tm, &iter_name, path)); gtk_tree_path_free (path); /* remove all records, we'll re-add those that are left next */ if (gtk_tree_model_iter_children (tm, &iter_record, &iter_name)) while (gtk_tree_store_remove (ts, &iter_record)) ; } else { ri = GNUNET_new (struct RecordInfo); gtk_tree_store_insert_with_values (ts, &iter_name, NULL, -1, GNS_TREESTORE_COL_NAME, name, GNS_TREESTORE_COL_NAME_IS_VISIBLE, TRUE, GNS_TREESTORE_COL_RECORD_TYPE, GNUNET_GNSRECORD_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, GNS_TREESTORE_COL_RECORD_OFFSET, UINT_MAX, -1); path = gtk_tree_model_get_path (tm, &iter_name); rr = gtk_tree_row_reference_new (tm, path); ri->rr = rr; ri->name = GNUNET_strdup (name); gtk_tree_path_free (path); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put ( n2r, &name_hash, ri, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); } if (0 == rd_count) { /* all records removed, remove name as well */ free_ri (ri); return; } /* update record info serialized version of the records */ GNUNET_free (ri->data); ri->rd_count = rd_count; ri->data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd); if (0 != ri->data_size) ri->data = GNUNET_malloc (ri->data_size); GNUNET_break ( ri->data_size == GNUNET_GNSRECORD_records_serialize (rd_count, rd, ri->data_size, ri->data)); /* Append elements for records in tree view */ for (unsigned int c = 0; c < rd_count; c++) { if (GNUNET_GNSRECORD_TYPE_NICK == rd[c].record_type) continue; 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, (unsigned long long) rd[c].expiration_time, (unsigned int) rd[c].data_size); /* Set public toggle */ is_public = ((rd[c].flags & GNUNET_GNSRECORD_RF_PRIVATE) != GNUNET_GNSRECORD_RF_PRIVATE); /* Set shadow toggle */ is_shadow = ((rd[c].flags & GNUNET_GNSRECORD_RF_SHADOW) == GNUNET_GNSRECORD_RF_SHADOW); /* Expiration time */ time_is_relative = (0 != (rd[c].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)); if (time_is_relative) { struct GNUNET_TIME_Relative rel_time; rel_time.rel_value_us = rd[c].expiration_time; exp_t = rel_time.rel_value_us; exp = GNUNET_STRINGS_relative_time_to_string (rel_time, GNUNET_NO); } else { struct GNUNET_TIME_Absolute exp_abs; exp_abs.abs_value_us = rd[c].expiration_time; exp_t = exp_abs.abs_value_us; exp = GNUNET_STRINGS_absolute_time_to_string (exp_abs); } /* value */ val = GNUNET_GNSRECORD_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_GNSRECORD_number_to_typename (rd[c].record_type)) type_str = strdup (GNUNET_GNSRECORD_number_to_typename (rd[c].record_type)); else GNUNET_asprintf (&type_str, "%s", EXPIRE_INVALID_STRING); if ((0 == strcmp (name, GNUNET_GNS_EMPTY_LABEL_AT)) && (GNUNET_GNSRECORD_TYPE_NICK == rd[c].record_type) && (NULL == current_pseudonym)) { #if HAVE_QRENCODE_H setup_qrcode (); #endif } gtk_tree_store_insert_with_values (ts, &iter_record, &iter_name, -1, 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, is_public, GNS_TREESTORE_COL_IS_SHADOW, is_shadow, 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, GNS_TREESTORE_COL_RECORD_OFFSET, (guint) c, -1); GNUNET_free (type_str); GNUNET_free (val); } if (NULL != sel_path) { gtk_tree_selection_select_path (sel, sel_path); gtk_tree_view_expand_to_path (tv, sel_path); gtk_tree_path_free (sel_path); } } /** * Method called to switch the model to a new zone. * * @param name name of the zone * @param ego ego handle */ static void load_zone (const char *name, struct GNUNET_IDENTITY_Ego *ego) { char *label; GtkTreeIter toplevel; /* setup crypto keys */ clear_zone_view (); if (NULL != zmon) { GNUNET_NAMESTORE_zone_monitor_stop (zmon); zmon = NULL; } GNUNET_free (current_pseudonym); current_pseudonym = NULL; gtk_tree_view_set_model (tv, NULL); if (NULL == name) return; /* empty zone */ current_pseudonym = GNUNET_strdup (name); pkey = GNUNET_IDENTITY_ego_get_private_key (ego); GNUNET_IDENTITY_ego_get_public_key (ego, &pubkey); label = g_markup_printf_escaped ("%s", GNUNET_GNSRECORD_z2s (&pubkey)); gtk_label_set_markup (GTK_LABEL ( get_object ("gnunet_namestore_gtk_zone_label")), label); g_free (label); /* Append a top level row and leave it empty */ gtk_tree_store_insert_with_values (ts, &toplevel, NULL, -1, 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, FALSE, GNS_TREESTORE_COL_NAME_IS_EDITABLE, TRUE, GNS_TREESTORE_COL_RECORD_OFFSET, UINT_MAX, -1); /* Load zone from namestore! */ #if HAVE_QRENCODE_H setup_qrcode (); #endif GNUNET_assert (NULL != n2r); zmon = GNUNET_NAMESTORE_zone_monitor_start (cfg, pkey, GNUNET_YES, &zone_iteration_error, NULL, &zone_iteration_proc, NULL, &zone_sync_proc, NULL); GNUNET_NAMESTORE_zone_monitor_next (zmon, NAMESTORE_MONITOR_WINDOW_SIZE - 1); } /** * Check if @a label is a valid label or is already used for the name * of any of our zones OR configured as a TLD in the configuration. * * @param label label or domain name to check * @return NULL if label is fresh and valid * error message explaining why @a label is not valid */ static const char * fresh_label (const char *label) { GtkTreeIter iter; struct GNS_TopLevelDomain *tld; if ((NULL == label) || (0 == strlen (label))) return _ ("Name must not be empty\n"); if (GNUNET_OK != GNUNET_DNSPARSER_check_name (label)) return _ ("Name is not a syntactically valid DNS label\n"); if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (zone_liststore), &iter)) { do { char *name; gtk_tree_model_get (GTK_TREE_MODEL (zone_liststore), &iter, ZONE_LS_NAME, &name, -1); if (0 == strcasecmp (name, label)) return _ ("Name already exists in the list\n"); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (zone_liststore), &iter)); } for (tld = tld_head; NULL != tld; tld = tld->next) if (0 == strcasecmp (tld->tld, label)) return _ ( "Name is assigned in the configuration file (see [GNS] section)\n"); return NULL; } /** * Continuation after identity operation. Makes the * main window sensitive again. * * @param cls NULL * @param priv private key of the new identity * @param emsg error message, used in status bar */ static void iop_create_cont (void *cls, const struct GNUNET_CRYPTO_PrivateKey *priv, enum GNUNET_ErrorCode ec) { GtkEntry *entry; (void) cls; (void) priv; iop = NULL; entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry")); gtk_widget_set_sensitive (main_window, TRUE); gtk_label_set_markup (status_label, (GNUNET_EC_NONE == ec) ? NULL : GNUNET_ErrorCode_get_hint (ec)); gtk_entry_grab_focus_without_selecting (entry); } /** * Continuation after identity operation. Makes the * main window sensitive again. * * @param cls NULL * @param emsg error message, used in status bar */ static void iop_cont (void *cls, enum GNUNET_ErrorCode ec) { GtkEntry *entry; iop = NULL; entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry")); gtk_widget_set_sensitive (main_window, TRUE); gtk_label_set_markup (status_label, (GNUNET_EC_NONE == ec) ? NULL : GNUNET_ErrorCode_get_hint (ec)); gtk_entry_grab_focus_without_selecting (entry); } /** * Actual main to tear down the system. * * @param cls the main loop handle */ static void cleanup_task (void *cls) { struct OperationContext *oc; struct MoveOperationContext *moc; struct GNS_TopLevelDomain *tld; if (NULL == ml) { GNUNET_break (0); return; } GNUNET_GTK_main_loop_quit (ml); gtk_widget_show (GTK_WIDGET (status_label)); gtk_widget_hide ( GTK_WIDGET (get_object ("gnunet_namestore_gtk_scrolledwindow"))); while (NULL != (oc = oc_head)) { GNUNET_log ( GNUNET_ERROR_TYPE_ERROR, _ ( "A pending namestore operation was not transmitted to the namestore.\n")); GNUNET_CONTAINER_DLL_remove (oc_head, oc_tail, oc); GNUNET_NAMESTORE_cancel (oc->qe); GNUNET_free (oc); } while (NULL != (moc = moc_head)) { GNUNET_log ( GNUNET_ERROR_TYPE_ERROR, _ ( "A pending namestore operation was not transmitted to the namestore.\n")); GNUNET_CONTAINER_DLL_remove (moc_head, moc_tail, moc); if (NULL != moc->it) GNUNET_NAMESTORE_zone_iteration_stop (moc->it); free_edit_dialog_context (moc->edc); GNUNET_free (moc->data); GNUNET_free (moc); } gtk_tree_store_clear (ts); if (NULL != current_pseudonym) { GNUNET_free (current_pseudonym); current_pseudonym = NULL; } if (NULL != iop) { GNUNET_IDENTITY_cancel (iop); iop = NULL; } if (NULL != identity) { GNUNET_IDENTITY_disconnect (identity); identity = NULL; } if (NULL != zmon) { GNUNET_NAMESTORE_zone_monitor_stop (zmon); zmon = NULL; } if (NULL != namestore) { GNUNET_NAMESTORE_disconnect (namestore); namestore = NULL; } if (NULL != n2r) { GNUNET_CONTAINER_multihashmap_iterate (n2r, &release_ri, NULL); GNUNET_CONTAINER_multihashmap_destroy (n2r); n2r = NULL; } GNUNET_assert (NULL == zmon); while (NULL != (tld = tld_head)) { GNUNET_CONTAINER_DLL_remove (tld_head, tld_tail, tld); GNUNET_free (tld->tld); GNUNET_free (tld); } } /** * Callback invoked if the application is supposed to exit. */ void gnunet_namestore_gtk_quit_cb (GObject *object, gpointer user_data) { GNUNET_SCHEDULER_shutdown (); } /** * Change the NICK record of the current zone to @a new_name. * Gets the old records, append new one or updates the existing one. * * @param new_name the new name (and hence nick) of the zone */ static void update_nick_record (const char *new_name) { struct RecordInfo *ric; struct GNUNET_HashCode hc; unsigned int rd_count; struct OperationContext *oc; struct GNUNET_NAMESTORE_RecordInfo ri; unsigned int res; GNUNET_CRYPTO_hash ("+", 1, &hc); ric = GNUNET_CONTAINER_multihashmap_get (n2r, &hc); if (NULL == ric) rd_count = 0; else rd_count = ric->rd_count; { struct GNUNET_GNSRECORD_Data rd_old[rd_count]; struct GNUNET_GNSRECORD_Data rd_new[rd_count + 1]; unsigned int off; unsigned int total; total = rd_count; if (NULL != ric) GNUNET_break (GNUNET_OK == GNUNET_GNSRECORD_records_deserialize (ric->data_size, ric->data, rd_count, rd_old)); memcpy (rd_new, rd_old, sizeof (struct GNUNET_GNSRECORD_Data) * rd_count); for (off = 0; off < rd_count; off++) if (GNUNET_GNSRECORD_TYPE_NICK == rd_new[off].record_type) break; if (off == rd_count) total++; if ((NULL == new_name) || (0 == strlen (new_name))) { rd_new[off] = rd_new[rd_count - 1]; total--; } else { rd_new[off].record_type = GNUNET_GNSRECORD_TYPE_NICK; rd_new[off].expiration_time = nick_expiration_time.rel_value_us; rd_new[off].flags = GNUNET_GNSRECORD_RF_PRIVATE | GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; rd_new[off].data_size = strlen (new_name) + 1; rd_new[off].data = new_name; } oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = GNUNET_GNS_EMPTY_LABEL_AT; ri.a_rd_count = total; ri.a_rd = rd_new; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } } /** * The user has edited the zone's name. Check if the new name is * valid (if not, warn) and update the NICK and rename the zone. */ static void handle_zone_entry_edit () { /* user is editing label, or we removed the last entry in the list */ const char *new_name; GtkEntry *entry; const char *emsg; entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry")); if (NULL == current_pseudonym) { gtk_entry_set_text (entry, ""); return; } new_name = gtk_entry_get_text (entry); if (NULL != (emsg = fresh_label (new_name))) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "%s", emsg); gtk_widget_error_bell (GTK_WIDGET (entry)); gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, "error"); gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_PRIMARY, emsg); return; } gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL); update_nick_record (new_name); #if HAVE_QRENCODE_H setup_qrcode (); #endif gtk_list_store_set (GTK_LIST_STORE (zone_liststore), &zone_iter, ZONE_LS_NAME, new_name, -1); gtk_label_set_markup (status_label, _ ("Renaming zone")); gtk_widget_set_sensitive (main_window, FALSE); GNUNET_assert (NULL == iop); iop = GNUNET_IDENTITY_rename (identity, current_pseudonym, new_name, &iop_cont, NULL); GNUNET_free (current_pseudonym); current_pseudonym = GNUNET_strdup (new_name); } /** * The user selected another zone in the combobox. Load it. * * @param widget the combo box where the selection was changed * @param user_data the builder, unused */ void gnunet_namestore_gtk_zone_combobox_changed_cb (GtkComboBox *widget, gpointer user_data) { GtkTreeIter iter; struct GNUNET_IDENTITY_Ego *ego; char *name; GtkEntry *entry; (void) user_data; if (! gtk_combo_box_get_active_iter (zone_combo_box, &iter)) { handle_zone_entry_edit (); return; } zone_iter = iter; entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_entry")); gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_PRIMARY, NULL); /* clang-format off */ gtk_tree_model_get (GTK_TREE_MODEL (zone_liststore), &iter, ZONE_LS_NAME, &name, ZONE_LS_EGO, &ego, -1); /* clang-format on */ gtk_widget_set_sensitive (GTK_WIDGET (get_object ( "gnunet_namestore_gtk_zone_del_button")), TRUE); load_zone (name, ego); } /** * Method called to inform about the egos of this peer. Updates the * `zone_liststore`. * * When used with #GNUNET_IDENTITY_connect, this function is * initially called for all egos and then again whenever a * ego's name changes or if it is deleted. At the end of * the initial pass over all egos, the function is once called * with 'NULL' for @a ego. That does NOT mean that the callback won't * be invoked in the future or that there was an error. * * If @a ego is non-NULL and if '*ctx' is set in those callbacks, the * value WILL be passed to a subsequent call to the identity callback * of #GNUNET_IDENTITY_connect (if that one was not NULL). * * When an identity is renamed, this function is called with the * (known) @a ego but the NEW @a name. * * When an identity is deleted, this function is called with the * (known) ego and "NULL" for the @a name. In this case, * the @a ego is henceforth invalid (and the @a ctx should also be * cleaned up). * * @param cls closure * @param ego ego handle * @param ctx context for application to store data for this ego * (during the lifetime of this process, initially NULL) * @param name name assigned by the user for this ego, * NULL if the user just deleted the ego and it * must thus no longer be used */ static void identity_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego, void **ctx, const char *name) { GtkTreeRowReference *rr; GtkTreeIter iter; GtkTreePath *path; if ((NULL == ego) && (NULL == name) && (NULL == ctx)) { /* end of initial iteration, trigger loading selected zone */ gnunet_namestore_gtk_zone_combobox_changed_cb (zone_combo_box, ml); return; } rr = *ctx; if (NULL == rr) { /* new identity, add to list */ GNUNET_assert (NULL != name); /* clang-format off */ gtk_list_store_insert_with_values (zone_liststore, &iter, 0, ZONE_LS_NAME, name, ZONE_LS_EGO, ego, -1); /* clang-format on */ gtk_combo_box_set_active_iter (zone_combo_box, &iter); gtk_widget_set_sensitive (GTK_WIDGET (zone_combo_box), TRUE); gtk_widget_set_sensitive (GTK_WIDGET ( get_object ("gnunet_namestore_gtk_zone_entry")), TRUE); path = gtk_tree_model_get_path (GTK_TREE_MODEL (zone_liststore), &iter); rr = gtk_tree_row_reference_new (GTK_TREE_MODEL (zone_liststore), path); *ctx = rr; gtk_tree_path_free (path); return; } path = gtk_tree_row_reference_get_path (rr); GNUNET_break ( gtk_tree_model_get_iter (GTK_TREE_MODEL (zone_liststore), &iter, path)); gtk_tree_path_free (path); if (NULL == name) { GtkTreeIter act_iter; /* identity was removed, remove from list */ GNUNET_free (current_pseudonym); current_pseudonym = NULL; gtk_list_store_remove (zone_liststore, &iter); if (! gtk_combo_box_get_active_iter (zone_combo_box, &act_iter)) { if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (zone_liststore), &act_iter)) { /* make sure combo box remains selected if possible */ gtk_combo_box_set_active (zone_combo_box, 0); } else { /* make combo box insensitive if nothing can be selected */ gtk_widget_set_sensitive (GTK_WIDGET (zone_combo_box), FALSE); gtk_widget_set_sensitive (GTK_WIDGET (get_object ( "gnunet_namestore_gtk_zone_entry")), FALSE); clear_zone_view (); } } return; } /* identity was renamed, rename in model */ gtk_list_store_set (zone_liststore, &iter, ZONE_LS_NAME, name, -1); } /** * The user clicked the "add" button for a new zone. * Obtain the label, create the new zone and reset * the entry to empty. * * @param button the "add" button * @param user_data the builder, unused */ void gnunet_namestore_gtk_zone_add_button_clicked_cb (GtkButton *button, gpointer user_data) { const char *label; GtkEntry *entry; (void) user_data; entry = GTK_ENTRY (get_object ("gnunet_namestore_gtk_zone_add_entry")); label = gtk_entry_get_text (entry); gtk_label_set_markup (status_label, _ ("Creating zone")); gtk_widget_set_sensitive (main_window, FALSE); iop = GNUNET_IDENTITY_create (identity, label, NULL, GNUNET_PUBLIC_KEY_TYPE_ECDSA, &iop_create_cont, NULL); gtk_entry_set_text (entry, ""); } /** * Delete all records of the current zone. * * @param cls NULL * @param key key of a record in the map to delete * @param value a `struct RecordInfo` to delete * @return #GNUNET_OK (continue to iterate) */ static int delete_records (void *cls, const struct GNUNET_HashCode *key, void *value) { struct RecordInfo *ric = value; struct OperationContext *oc; struct GNUNET_NAMESTORE_RecordInfo ri; unsigned int res; if (0 != ric->rd_count) { oc = GNUNET_new (struct OperationContext); GNUNET_CONTAINER_DLL_insert (oc_head, oc_tail, oc); ri.a_label = ric->name; ri.a_rd_count = 0; ri.a_rd = NULL; oc->qe = GNUNET_NAMESTORE_records_store (namestore, pkey, 1, &ri, &res, &operation_done_cont, oc); } free_ri (ric); return GNUNET_OK; } /** * The user clicked the "del" button for an existing zone. * Obtain the label/ego and delete the zone. * * @param button the "del" button * @param user_data the builder, unused */ void gnunet_namestore_gtk_zone_del_button_clicked_cb (GtkButton *button, gpointer user_data) { char *pseu; (void) user_data; pseu = current_pseudonym; current_pseudonym = NULL; GNUNET_CONTAINER_multihashmap_iterate (n2r, &delete_records, NULL); gtk_tree_store_clear (ts); gtk_widget_set_sensitive (GTK_WIDGET (get_object ( "gnunet_namestore_gtk_zone_del_button")), FALSE); gtk_widget_set_sensitive (main_window, FALSE); iop = GNUNET_IDENTITY_delete (identity, pseu, &iop_cont, NULL); GNUNET_free (pseu); } /** * The user edited the zone label for 'adding' a new zone. * Check if the label is unique and well-formed and update * the sensitivity of the "add" button accordingly. * * @param editable the zone label that was changed * @param user_data the builder, unused */ void gnunet_namestore_gtk_zone_add_entry_changed_cb (GtkEditable *editable, gpointer user_data) { const char *label; (void) user_data; label = gtk_entry_get_text (GTK_ENTRY (editable)); gtk_widget_set_sensitive (GTK_WIDGET (get_object ( "gnunet_namestore_gtk_zone_add_button")), NULL == fresh_label (label)); } /** * Reads the configuration and populates TLDs * * @param cls unused * @param section name of section in config, always "gns" * @param option name of the option, TLDs start with "." * @param value value for the option, public key for TLDs */ static void read_service_conf (void *cls, const char *section, const char *option, const char *value) { struct GNUNET_CRYPTO_EddsaPublicKey pk; struct GNS_TopLevelDomain *tld; if (option[0] != '.') return; if (GNUNET_OK != GNUNET_STRINGS_string_to_data (value, strlen (value), &pk, sizeof (pk))) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, option, _ ( "Properly base32-encoded public key required")); return; } tld = GNUNET_new (struct GNS_TopLevelDomain); tld->tld = GNUNET_strdup (&option[1]); tld->pkey = pk; GNUNET_CONTAINER_DLL_insert (tld_head, tld_tail, tld); } /** * Actual main method that sets up the configuration window. * * @param cls the main loop handle */ static void run (void *cls) { gchar *label; ml = cls; cfg = GNUNET_GTK_main_loop_get_configuration (ml); GNUNET_CONFIGURATION_iterate_section_values (cfg, "gns", &read_service_conf, NULL); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, "gnunet-namestore-gtk", "NICK_EXPIRATION", &nick_expiration_time)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "gnunet-namestore-gtk", "NICK_EXPIRATION"); GNUNET_GTK_main_loop_quit (ml); return; } if (GNUNET_OK != GNUNET_GTK_main_loop_build_window (ml, NULL)) { GNUNET_GTK_main_loop_quit (ml); return; } GNUNET_GTK_set_icon_search_path (); GNUNET_GTK_setup_nls (); main_window = GTK_WIDGET (get_object ("gnunet_namestore_gtk_dialog")); status_label = GTK_LABEL (get_object ("gnunet_namestore_gtk_status_label")); zone_combo_box = GTK_COMBO_BOX (get_object ("gnunet_namestore_gtk_zone_combobox")); namestore = GNUNET_NAMESTORE_connect (cfg); if (NULL == namestore) { label = g_markup_printf_escaped ( _ ("Failed to connect to namestore")); gtk_label_set_markup (status_label, label); g_free (label); GNUNET_SCHEDULER_add_shutdown (&cleanup_task, NULL); return; } ts = GTK_TREE_STORE (get_object ("gnunet_namestore_gtk_treestore")); tv = GTK_TREE_VIEW (get_object ("gnunet_namestore_gtk_treeview")); zone_liststore = GTK_LIST_STORE (get_object ("zone_liststore")); tm = GTK_TREE_MODEL (ts); n2r = GNUNET_CONTAINER_multihashmap_create (128, GNUNET_NO); identity = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL); gtk_widget_show (main_window); gtk_window_present (GTK_WINDOW (main_window)); GNUNET_SCHEDULER_add_shutdown (&cleanup_task, NULL); } /** * Main function for gnunet-namestore-gtk. * * @param argc number of arguments * @param argv arguments * @return 0 on success */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = {GNUNET_GETOPT_OPTION_END}; int ret; if (GNUNET_OK == GNUNET_GTK_main_loop_start ("gnunet-namestore-gtk", "gnunet-namestore-gtk", argc, argv, options, "gnunet_namestore_gtk_main_window.glade", &run)) ret = gret; else ret = 1; return ret; } /* end of gnunet-namestore-gtk.c */