/* This file is part of GNUnet. Copyright (C) 2012, 2013, 2014, 2019 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file gnunet-namestore.c * @brief command line tool to manipulate the local zone * @author Christian Grothoff * * TODO: * - test */ #include "platform.h" #include #include #include #include #include #include #include /** * The upper bound for the zone iteration interval * (per record). */ #define WARN_RELATIVE_EXPIRATION_LIMIT GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, 15) /** * Entry in record set for bulk processing. */ struct RecordSetEntry { /** * Kept in a linked list. */ struct RecordSetEntry *next; /** * The record to add/remove. */ struct GNUNET_GNSRECORD_Data record; }; /** * The record marked for deletion */ struct MarkedRecord { /** * DLL */ struct MarkedRecord *next; /** * DLL */ struct MarkedRecord *prev; /** * Ego Identifier */ char *name; /** * The zone key */ struct GNUNET_IDENTITY_PrivateKey key; }; /** * The default namestore ego */ struct EgoEntry { /** * DLL */ struct EgoEntry *next; /** * DLL */ struct EgoEntry *prev; /** * Ego Identifier */ char *identifier; /** * The Ego */ struct GNUNET_IDENTITY_Ego *ego; }; /** * Handle to the namestore. */ static struct GNUNET_NAMESTORE_Handle *ns; /** * Private key for the our zone. */ static struct GNUNET_IDENTITY_PrivateKey zone_pkey; /** * Identity service handle */ static struct GNUNET_IDENTITY_Handle *idh; /** * Name of the ego controlling the zone. */ static char *ego_name; /** * Queue entry for the 'add-uri' operation. */ static struct GNUNET_NAMESTORE_QueueEntry *add_qe_uri; /** * Queue entry for the 'add' operation. */ static struct GNUNET_NAMESTORE_QueueEntry *add_qe; /** * Queue entry for the 'lookup' operation. */ static struct GNUNET_NAMESTORE_QueueEntry *get_qe; /** * Queue entry for the 'reverse lookup' operation (in combination with a name). */ static struct GNUNET_NAMESTORE_QueueEntry *reverse_qe; /** * Marked record list */ static struct MarkedRecord *marked_head; /** * Marked record list */ static struct MarkedRecord *marked_tail; /** * Configuration handle */ const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Ego list */ static struct EgoEntry *ego_head; /** * Ego list */ static struct EgoEntry *ego_tail; /** * List iterator for the 'list' operation. */ static struct GNUNET_NAMESTORE_ZoneIterator *list_it; /** * Run in read from stdin mode. */ static int read_from_stdin; /** * Desired action is to list records. */ static int list; /** * Desired action is to add a record. */ static int add; /** * Desired action is to remove a record. */ static int del; /** * Is record public (opposite of #GNUNET_GNSRECORD_RF_PRIVATE) */ static int is_public; /** * Is record a shadow record (#GNUNET_GNSRECORD_RF_SHADOW) */ static int is_shadow; /** * Filter private records */ static int omit_private; /** * Output in recordline format */ static int output_recordline; /** * Purge zone contents */ static int purge_zone; /** * Do not filter maintenance records */ static int include_maintenance; /** * Purge orphaned records */ static int purge_orphaned; /** * List records and zone keys of orphaned records */ static int list_orphaned; /** * Queue entry for the 'del' operation. */ static struct GNUNET_NAMESTORE_QueueEntry *del_qe; /** * Queue entry for the 'set/replace' operation. */ static struct GNUNET_NAMESTORE_QueueEntry *set_qe; /** * Queue entry for begin/commit */ static struct GNUNET_NAMESTORE_QueueEntry *ns_qe; /** * Name of the records to add/list/remove. */ static char *name; /** * Value of the record to add/remove. */ static char *value; /** * URI to import. */ static char *uri; /** * Reverse lookup to perform. */ static char *reverse_pkey; /** * Type of the record to add/remove, NULL to remove all. */ static char *typestring; /** * Desired expiration time. */ static char *expirationstring; /** * Desired nick name. */ static char *nickstring; /** * Global return value */ static int ret; /** * Type string converted to DNS type value. */ static uint32_t type; /** * Value in binary format. */ static void *data; /** * Number of bytes in #data. */ static size_t data_size; /** * Expiration string converted to numeric value. */ static uint64_t etime; /** * Is expiration time relative or absolute time? */ static int etime_is_rel = GNUNET_SYSERR; /** * Monitor handle. */ static struct GNUNET_NAMESTORE_ZoneMonitor *zm; /** * Enables monitor mode. */ static int monitor; /** * Entry in record set for processing records in bulk. */ static struct RecordSetEntry *recordset; /** * Purge task */ static struct GNUNET_SCHEDULER_Task *purge_task; /** * Parse expiration time. * * @param expirationstring text to parse * @param[out] etime_is_rel set to #GNUNET_YES if time is relative * @param[out] etime set to expiration time (abs or rel) * @return #GNUNET_OK on success */ static int parse_expiration (const char *expirationstring, int *etime_is_rel, uint64_t *etime) { struct GNUNET_TIME_Relative etime_rel; struct GNUNET_TIME_Absolute etime_abs; if (0 == strcmp (expirationstring, "never")) { *etime = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us; *etime_is_rel = GNUNET_NO; return GNUNET_OK; } if (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_relative (expirationstring, &etime_rel)) { *etime_is_rel = GNUNET_YES; *etime = etime_rel.rel_value_us; if (GNUNET_TIME_relative_cmp (etime_rel, <, WARN_RELATIVE_EXPIRATION_LIMIT)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Relative expiration times of less than %s are not recommended. To improve availability, consider increasing this value.\n", GNUNET_STRINGS_relative_time_to_string ( WARN_RELATIVE_EXPIRATION_LIMIT, GNUNET_NO)); } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing record with relative expiration time of %s\n", GNUNET_STRINGS_relative_time_to_string (etime_rel, GNUNET_NO)); return GNUNET_OK; } if (GNUNET_OK == GNUNET_STRINGS_fancy_time_to_absolute (expirationstring, &etime_abs)) { *etime_is_rel = GNUNET_NO; *etime = etime_abs.abs_value_us; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Storing record with absolute expiration time of %s\n", GNUNET_STRINGS_absolute_time_to_string (etime_abs)); return GNUNET_OK; } return GNUNET_SYSERR; } static int parse_recordline (const char *line) { struct RecordSetEntry **head = &recordset; struct RecordSetEntry *r; struct GNUNET_GNSRECORD_Data record; char *cp; char *tok; char *saveptr; void *raw_data; cp = GNUNET_strdup (line); tok = strtok_r (cp, " ", &saveptr); if (NULL == tok) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Missing entries in record line `%s'.\n"), line); GNUNET_free (cp); return GNUNET_SYSERR; } record.record_type = GNUNET_GNSRECORD_typename_to_number (tok); if (UINT32_MAX == record.record_type) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unknown record type `%s'\n"), tok); GNUNET_free (cp); return GNUNET_SYSERR; } tok = strtok_r (NULL, " ", &saveptr); if (NULL == tok) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Empty record line argument is not allowed.\n")); GNUNET_free (cp); return GNUNET_SYSERR; } if (1 != sscanf (tok, "%" SCNu64, &record.expiration_time)) { fprintf (stderr, _ ("Error parsing expiration time %s.\n"), tok); GNUNET_free (cp); return GNUNET_SYSERR; } tok = strtok_r (NULL, " ", &saveptr); if (NULL == tok) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Empty record line argument is not allowed.\n")); GNUNET_free (cp); return GNUNET_SYSERR; } record.flags = GNUNET_GNSRECORD_RF_NONE; if (NULL != strchr (tok, (unsigned char) 'r')) record.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; if (NULL == strchr (tok, (unsigned char) 'p')) /* p = public */ record.flags |= GNUNET_GNSRECORD_RF_PRIVATE; if (NULL != strchr (tok, (unsigned char) 'S')) record.flags |= GNUNET_GNSRECORD_RF_SUPPLEMENTAL; if (NULL != strchr (tok, (unsigned char) 's')) record.flags |= GNUNET_GNSRECORD_RF_SHADOW; if (NULL != strchr (tok, (unsigned char) 'C')) record.flags |= GNUNET_GNSRECORD_RF_CRITICAL; tok += strlen (tok) + 1; if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (record.record_type, tok, &raw_data, &record.data_size)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Invalid record data for type %s: `%s'.\n"), GNUNET_GNSRECORD_number_to_typename (record.record_type), tok); return GNUNET_SYSERR; } r = GNUNET_malloc (sizeof(struct RecordSetEntry) + record.data_size); r->next = *head; record.data = &r[1]; memcpy (&r[1], raw_data, record.data_size); GNUNET_free (raw_data); r->record = record; *head = r; return GNUNET_OK; } static void reset_handles (void) { struct MarkedRecord *mrec; struct MarkedRecord *mrec_tmp; struct RecordSetEntry *rs_entry; rs_entry = recordset; while (NULL != (rs_entry = recordset)) { recordset = recordset->next; GNUNET_free (rs_entry); } recordset = NULL; if (NULL != ego_name) { GNUNET_free (ego_name); ego_name = NULL; } if (NULL != name) { GNUNET_free (name); name = NULL; } if (NULL != value) { GNUNET_free (value); value = NULL; } if (NULL != uri) { GNUNET_free (uri); uri = NULL; } if (NULL != expirationstring) { GNUNET_free (expirationstring); expirationstring = NULL; } if (NULL != purge_task) { GNUNET_SCHEDULER_cancel (purge_task); purge_task = NULL; } for (mrec = marked_head; NULL != mrec;) { mrec_tmp = mrec; mrec = mrec->next; GNUNET_free (mrec_tmp->name); GNUNET_free (mrec_tmp); } if (NULL != list_it) { GNUNET_NAMESTORE_zone_iteration_stop (list_it); list_it = NULL; } if (NULL != add_qe) { GNUNET_NAMESTORE_cancel (add_qe); add_qe = NULL; } if (NULL != set_qe) { GNUNET_NAMESTORE_cancel (set_qe); set_qe = NULL; } if (NULL != add_qe_uri) { GNUNET_NAMESTORE_cancel (add_qe_uri); add_qe_uri = NULL; } if (NULL != get_qe) { GNUNET_NAMESTORE_cancel (get_qe); get_qe = NULL; } if (NULL != del_qe) { GNUNET_NAMESTORE_cancel (del_qe); del_qe = NULL; } if (NULL != reverse_qe) { GNUNET_NAMESTORE_cancel (reverse_qe); reverse_qe = NULL; } memset (&zone_pkey, 0, sizeof(zone_pkey)); if (NULL != zm) { GNUNET_NAMESTORE_zone_monitor_stop (zm); zm = NULL; } if (NULL != data) { GNUNET_free (data); data = NULL; } if (NULL != typestring) { GNUNET_free (typestring); typestring = NULL; } list = 0; is_public = 0; is_shadow = 0; purge_zone = 0; } /** * Task run on shutdown. Cleans up everything. * * @param cls unused */ static void do_shutdown (void *cls) { struct EgoEntry *ego_entry; struct EgoEntry *ego_tmp; (void) cls; reset_handles (); if (NULL != ns_qe) { GNUNET_NAMESTORE_cancel (ns_qe); ns_qe = NULL; } if (NULL != ns) { GNUNET_NAMESTORE_disconnect (ns); ns = NULL; } if (NULL != idh) { GNUNET_IDENTITY_disconnect (idh); idh = NULL; } for (ego_entry = ego_head; NULL != ego_entry;) { ego_tmp = ego_entry; ego_entry = ego_entry->next; GNUNET_free (ego_tmp->identifier); GNUNET_free (ego_tmp); } } static void commit_cb (void *cls, enum GNUNET_ErrorCode ec) { ns_qe = NULL; if (GNUNET_EC_NONE != ec) { fprintf (stderr, "Failed to commit to namestore: `%s'\n", GNUNET_ErrorCode_get_hint (ec)); ret = 1; } GNUNET_SCHEDULER_shutdown (); } static void process_command_stdin (); static void finish_command (void) { reset_handles (); if (read_from_stdin) { process_command_stdin (); return; } ns_qe = GNUNET_NAMESTORE_transaction_commit (ns, &commit_cb, NULL); } static void add_continuation (void *cls, enum GNUNET_ErrorCode ec) { struct GNUNET_NAMESTORE_QueueEntry **qe = cls; *qe = NULL; if (GNUNET_EC_NONE != ec) { fprintf (stderr, _ ("Adding record failed: %s\n"), GNUNET_ErrorCode_get_hint (ec)); if (GNUNET_EC_NAMESTORE_RECORD_EXISTS != ec) ret = 1; } ret = 0; finish_command (); } static void del_continuation (void *cls, enum GNUNET_ErrorCode ec) { (void) cls; del_qe = NULL; if (GNUNET_EC_NAMESTORE_RECORD_NOT_FOUND == ec) { fprintf (stderr, _ ("Deleting record failed: %s\n"), GNUNET_ErrorCode_get_hint ( ec)); } finish_command (); } static void purge_next_record (void *cls); static void marked_deleted (void *cls, enum GNUNET_ErrorCode ec) { del_qe = NULL; if (GNUNET_EC_NONE != ec) { fprintf (stderr, _ ("Deleting record failed: %s\n"), GNUNET_ErrorCode_get_hint (ec)); } purge_task = GNUNET_SCHEDULER_add_now (&purge_next_record, NULL); } static void purge_next_record (void *cls) { struct MarkedRecord *mrec; purge_task = NULL; if (NULL == marked_head) { ret = 0; finish_command (); return; } mrec = marked_head; GNUNET_CONTAINER_DLL_remove (marked_head, marked_tail, mrec); del_qe = GNUNET_NAMESTORE_records_store (ns, &mrec->key, mrec->name, 0, NULL, &marked_deleted, NULL); GNUNET_free (mrec->name); GNUNET_free (mrec); } /** * Function called when we are done with a zone iteration. */ static void zone_iteration_finished (void *cls) { (void) cls; list_it = NULL; if (purge_orphaned || purge_zone) { purge_task = GNUNET_SCHEDULER_add_now (&purge_next_record, NULL); return; } ret = 0; finish_command (); } /** * Function called when we encountered an error in a zone iteration. */ static void zone_iteration_error_cb (void *cls) { (void) cls; list_it = NULL; fprintf (stderr, "Error iterating over zone\n"); ret = 1; finish_command (); } static void collect_zone_records_to_purge (const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd) { struct MarkedRecord *mrec; mrec = GNUNET_new (struct MarkedRecord); mrec->key = *zone_key; mrec->name = GNUNET_strdup (rname); GNUNET_CONTAINER_DLL_insert (marked_head, marked_tail, mrec); } static void collect_orphans (const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd) { struct EgoEntry *ego; struct MarkedRecord *orphan; int is_orphaned = 1; for (ego = ego_head; NULL != ego; ego = ego->next) { if (0 == memcmp (GNUNET_IDENTITY_ego_get_private_key (ego->ego), zone_key, sizeof (*zone_key))) { is_orphaned = 0; break; } } if (is_orphaned) { orphan = GNUNET_new (struct MarkedRecord); orphan->key = *zone_key; orphan->name = GNUNET_strdup (rname); GNUNET_CONTAINER_DLL_insert (marked_head, marked_tail, orphan); } } /** * Process a record that was stored in the namestore. * * @param rname name that is being mapped (at most 255 characters long) * @param rd_len number of entries in @a rd array * @param rd array of records with data to store */ static void display_record (const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd) { const char *typestr; char *s; const char *ets; struct GNUNET_TIME_Absolute at; struct GNUNET_TIME_Relative rt; struct EgoEntry *ego; int have_record; int is_orphaned = 1; char *orphaned_str; if ((NULL != name) && (0 != strcmp (name, rname))) return; have_record = GNUNET_NO; for (unsigned int i = 0; i < rd_len; i++) { if ((GNUNET_GNSRECORD_TYPE_NICK == rd[i].record_type) && (0 != strcmp (rname, GNUNET_GNS_EMPTY_LABEL_AT))) continue; if ((type != rd[i].record_type) && (GNUNET_GNSRECORD_TYPE_ANY != type)) continue; have_record = GNUNET_YES; break; } if (GNUNET_NO == have_record) return; for (ego = ego_head; NULL != ego; ego = ego->next) { if (0 == memcmp (GNUNET_IDENTITY_ego_get_private_key (ego->ego), zone_key, sizeof (*zone_key))) { is_orphaned = 0; break; } } if (list_orphaned && ! is_orphaned) return; if (! list_orphaned && is_orphaned) return; orphaned_str = GNUNET_IDENTITY_private_key_to_string (zone_key); fprintf (stdout, "%s.%s:\n", rname, is_orphaned ? orphaned_str : ego->identifier); GNUNET_free (orphaned_str); if (NULL != typestring) type = GNUNET_GNSRECORD_typename_to_number (typestring); else type = GNUNET_GNSRECORD_TYPE_ANY; for (unsigned int i = 0; i < rd_len; i++) { if ((GNUNET_GNSRECORD_TYPE_NICK == rd[i].record_type) && (0 != strcmp (rname, GNUNET_GNS_EMPTY_LABEL_AT))) continue; if ((type != rd[i].record_type) && (GNUNET_GNSRECORD_TYPE_ANY != type)) continue; typestr = GNUNET_GNSRECORD_number_to_typename (rd[i].record_type); s = GNUNET_GNSRECORD_value_to_string (rd[i].record_type, rd[i].data, rd[i].data_size); if (NULL == s) { fprintf (stdout, _ ("\tCorrupt or unsupported record of type %u\n"), (unsigned int) rd[i].record_type); continue; } if (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION)) { rt.rel_value_us = rd[i].expiration_time; ets = GNUNET_STRINGS_relative_time_to_string (rt, GNUNET_YES); } else { at.abs_value_us = rd[i].expiration_time; ets = GNUNET_STRINGS_absolute_time_to_string (at); } char flgstr[16]; sprintf (flgstr, "[%s%s%s%s%s]", (rd[i].flags & GNUNET_GNSRECORD_RF_PRIVATE) ? "" : "p", (rd[i].flags & GNUNET_GNSRECORD_RF_SUPPLEMENTAL) ? "S" : "", (rd[i].flags & GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION) ? "r" : "", (rd[i].flags & GNUNET_GNSRECORD_RF_SHADOW) ? "S" : "", (rd[i].flags & GNUNET_GNSRECORD_RF_CRITICAL) ? "C" : ""); if (output_recordline) fprintf (stdout, " %s %" PRIu64 " %s %s\n", typestr, rd[i].expiration_time, flgstr, s); else fprintf (stdout, "\t%s: %s (%s)\t%s\t%s\n", typestr, s, ets, (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_PRIVATE)) ? "PRIVATE" : "PUBLIC", (0 != (rd[i].flags & GNUNET_GNSRECORD_RF_SHADOW)) ? "SHADOW" : ""); GNUNET_free (s); } // fprintf (stdout, "%s", "\n"); } static void purge_zone_iterator (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd, struct GNUNET_TIME_Absolute expiry) { (void) cls; (void) zone_key; (void) expiry; collect_zone_records_to_purge (zone_key, rname, rd_len, rd); GNUNET_NAMESTORE_zone_iterator_next (list_it, 1); } static void purge_orphans_iterator (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd, struct GNUNET_TIME_Absolute expiry) { (void) cls; (void) zone_key; (void) expiry; collect_orphans (zone_key, rname, rd_len, rd); GNUNET_NAMESTORE_zone_iterator_next (list_it, 1); } /** * Process a record that was stored in the namestore. * * @param cls closure * @param zone_key private key of the zone * @param rname name that is being mapped (at most 255 characters long) * @param rd_len number of entries in @a rd array * @param rd array of records with data to store */ static void display_record_iterator (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd, struct GNUNET_TIME_Absolute expiry) { (void) cls; (void) zone_key; (void) expiry; display_record (zone_key, rname, rd_len, rd); GNUNET_NAMESTORE_zone_iterator_next (list_it, 1); } /** * Process a record that was stored in the namestore. * * @param cls closure * @param zone_key private key of the zone * @param rname name that is being mapped (at most 255 characters long) * @param rd_len number of entries in @a rd array * @param rd array of records with data to store */ static void display_record_monitor (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd, struct GNUNET_TIME_Absolute expiry) { (void) cls; (void) zone_key; (void) expiry; display_record (zone_key, rname, rd_len, rd); GNUNET_NAMESTORE_zone_monitor_next (zm, 1); } /** * Process a record that was stored in the namestore. * * @param cls closure * @param zone_key private key of the zone * @param rname name that is being mapped (at most 255 characters long) * @param rd_len number of entries in @a rd array * @param rd array of records with data to store */ static void display_record_lookup (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rname, unsigned int rd_len, const struct GNUNET_GNSRECORD_Data *rd) { (void) cls; (void) zone_key; get_qe = NULL; display_record (zone_key, rname, rd_len, rd); finish_command (); } /** * Function called once we are in sync in monitor mode. * * @param cls NULL */ static void sync_cb (void *cls) { (void) cls; fprintf (stdout, "%s", "Monitor is now in sync.\n"); } /** * Function called on errors while monitoring. * * @param cls NULL */ static void monitor_error_cb (void *cls) { (void) cls; fprintf (stderr, "%s", "Monitor disconnected and out of sync.\n"); } /** * Function called on errors while monitoring. * * @param cls NULL */ static void lookup_error_cb (void *cls) { (void) cls; get_qe = NULL; fprintf (stderr, "%s", "Failed to lookup record.\n"); finish_command (); } /** * Function called if lookup fails. */ static void add_error_cb (void *cls) { (void) cls; add_qe = NULL; GNUNET_break (0); ret = 1; finish_command (); } /** * We're storing a record; this function is given the existing record * so that we can merge the information. * * @param cls closure, unused * @param zone_key private key of the zone * @param rec_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 get_existing_record (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone_key, const char *rec_name, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct GNUNET_GNSRECORD_Data rdn[rd_count + 1]; struct GNUNET_GNSRECORD_Data *rde; (void) cls; (void) zone_key; add_qe = NULL; if (0 != strcmp (rec_name, name)) { GNUNET_break (0); ret = 1; finish_command (); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received %u records for name `%s'\n", rd_count, rec_name); for (unsigned int i = 0; i < rd_count; i++) { switch (rd[i].record_type) { case GNUNET_DNSPARSER_TYPE_SOA: if (GNUNET_DNSPARSER_TYPE_SOA == type) { fprintf ( stderr, _ ( "A SOA record exists already under `%s', cannot add a second SOA to the same zone.\n"), rec_name); ret = 1; finish_command (); return; } break; } } memset (rdn, 0, sizeof(struct GNUNET_GNSRECORD_Data)); GNUNET_memcpy (&rdn[1], rd, rd_count * sizeof(struct GNUNET_GNSRECORD_Data)); rde = &rdn[0]; rde->data = data; rde->data_size = data_size; rde->record_type = type; if (1 == is_shadow) rde->flags |= GNUNET_GNSRECORD_RF_SHADOW; if (1 != is_public) rde->flags |= GNUNET_GNSRECORD_RF_PRIVATE; rde->expiration_time = etime; if (GNUNET_YES == etime_is_rel) rde->flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; else if (GNUNET_NO != etime_is_rel) rde->expiration_time = GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us; GNUNET_assert (NULL != name); add_qe = GNUNET_NAMESTORE_records_store (ns, &zone_pkey, name, rd_count + 1, rde, &add_continuation, &add_qe); } /** * Function called if we encountered an error in zone-to-name. */ static void reverse_error_cb (void *cls) { (void) cls; reverse_qe = NULL; fprintf (stdout, "%s.zkey\n", reverse_pkey); } /** * Function called with the result of our attempt to obtain a name for a given * public key. * * @param cls NULL * @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_reverse_lookup (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { (void) cls; (void) zone; (void) rd_count; (void) rd; reverse_qe = NULL; if (NULL == label) fprintf (stdout, "%s\n", reverse_pkey); else fprintf (stdout, "%s.%s\n", label, ego_name); finish_command (); } /** * Function called if lookup for deletion fails. */ static void del_lookup_error_cb (void *cls) { (void) cls; del_qe = NULL; GNUNET_break (0); ret = 1; finish_command (); } /** * We were asked to delete something; this function is called with * the existing records. Now we should determine what should be * deleted and then issue the deletion operation. * * @param cls NULL * @param zone private key of the zone we are deleting from * @param label name of the records we are editing * @param rd_count size of the @a rd array * @param rd existing records */ static void del_monitor (void *cls, const struct GNUNET_IDENTITY_PrivateKey *zone, const char *label, unsigned int rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct GNUNET_GNSRECORD_Data rdx[rd_count]; unsigned int rd_left; uint32_t type; char *vs; (void) cls; (void) zone; del_qe = NULL; if (0 == rd_count) { fprintf (stderr, _ ( "There are no records under label `%s' that could be deleted.\n"), label); ret = 1; finish_command (); return; } if ((NULL == value) && (NULL == typestring)) { /* delete everything */ del_qe = GNUNET_NAMESTORE_records_store (ns, &zone_pkey, name, 0, NULL, &del_continuation, NULL); return; } rd_left = 0; if (NULL != typestring) type = GNUNET_GNSRECORD_typename_to_number (typestring); else type = GNUNET_GNSRECORD_TYPE_ANY; for (unsigned int i = 0; i < rd_count; i++) { vs = NULL; if (! (((GNUNET_GNSRECORD_TYPE_ANY == type) || (rd[i].record_type == type)) && ((NULL == value) || (NULL == (vs = (GNUNET_GNSRECORD_value_to_string (rd[i].record_type, rd[i].data, rd[i].data_size)))) || (0 == strcmp (vs, value))))) rdx[rd_left++] = rd[i]; GNUNET_free (vs); } if (rd_count == rd_left) { /* nothing got deleted */ fprintf ( stderr, _ ( "There are no records under label `%s' that match the request for deletion.\n"), label); finish_command (); return; } /* delete everything but what we copied to 'rdx' */ del_qe = GNUNET_NAMESTORE_records_store (ns, &zone_pkey, name, rd_left, rdx, &del_continuation, NULL); } static void replace_cont (void *cls, enum GNUNET_ErrorCode ec) { (void) cls; set_qe = NULL; if (GNUNET_EC_NONE != ec) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, _ ("%s\n"), GNUNET_ErrorCode_get_hint (ec)); ret = 1; /* fail from 'main' */ } finish_command (); } /** * We have obtained the zone's private key, so now process * the main commands using it. * * @param cfg configuration to use */ static void run_with_zone_pkey (const struct GNUNET_CONFIGURATION_Handle *cfg) { struct GNUNET_GNSRECORD_Data rd; enum GNUNET_GNSRECORD_Filter filter_flags = GNUNET_GNSRECORD_FILTER_NONE; if (omit_private) filter_flags |= GNUNET_GNSRECORD_FILTER_OMIT_PRIVATE; if (include_maintenance) filter_flags |= GNUNET_GNSRECORD_FILTER_INCLUDE_MAINTENANCE; if (! (add | del | list | (NULL != nickstring) | (NULL != uri) | (NULL != reverse_pkey) | (NULL != recordset) | (monitor) | (purge_orphaned) | (list_orphaned) | (purge_zone)) ) { /* nothing more to be done */ fprintf (stderr, _ ("No options given\n")); finish_command (); return; } if (NULL != recordset) { /* replace entire record set */ unsigned int rd_count; struct GNUNET_GNSRECORD_Data *rd; /* FIXME: We could easily support append and delete with this as well */ if (! add) { fprintf (stderr, _ ("Recordlines only work with option `%s'\n"), "-a"); ret = 1; finish_command (); return; } if (NULL == name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-n", _ ("name")); ret = 1; finish_command (); return; } rd_count = 0; for (struct RecordSetEntry *e = recordset; NULL != e; e = e->next) rd_count++; rd = GNUNET_new_array (rd_count, struct GNUNET_GNSRECORD_Data); rd_count = 0; for (struct RecordSetEntry *e = recordset; NULL != e; e = e->next) { rd[rd_count] = e->record; rd_count++; } set_qe = GNUNET_NAMESTORE_records_store (ns, &zone_pkey, name, rd_count, rd, &replace_cont, NULL); GNUNET_free (rd); return; } if (NULL != nickstring) { if (0 == strlen (nickstring)) { fprintf (stderr, _ ("Invalid nick `%s'\n"), nickstring); ret = 1; finish_command (); return; } add = 1; typestring = GNUNET_strdup (GNUNET_GNSRECORD_number_to_typename ( GNUNET_GNSRECORD_TYPE_NICK)); name = GNUNET_strdup (GNUNET_GNS_EMPTY_LABEL_AT); value = GNUNET_strdup (nickstring); is_public = 0; expirationstring = GNUNET_strdup ("never"); GNUNET_free (nickstring); nickstring = NULL; } if (add) { if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("add")); ret = 1; finish_command (); return; } if (NULL == name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-n", _ ("add")); ret = 1; finish_command (); return; } if (NULL == typestring) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-t", _ ("add")); ret = 1; finish_command (); return; } type = GNUNET_GNSRECORD_typename_to_number (typestring); if (UINT32_MAX == type) { fprintf (stderr, _ ("Unsupported type `%s'\n"), typestring); ret = 1; finish_command (); return; } if ((GNUNET_DNSPARSER_TYPE_SRV == type) || (GNUNET_DNSPARSER_TYPE_TLSA == type) || (GNUNET_DNSPARSER_TYPE_OPENPGPKEY == type)) { fprintf (stderr, _ ("For DNS record types `SRV', `TLSA' and `OPENPGPKEY'")); fprintf (stderr, ", please use a `BOX' record instead\n"); ret = 1; finish_command (); return; } if (NULL == value) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-V", _ ("add")); ret = 1; finish_command (); return; } if (GNUNET_OK != GNUNET_GNSRECORD_string_to_value (type, value, &data, &data_size)) { fprintf (stderr, _ ("Value `%s' invalid for record type `%s'\n"), value, typestring); ret = 1; finish_command (); return; } if (NULL == expirationstring) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-e", _ ("add")); ret = 1; finish_command (); return; } if (GNUNET_OK != parse_expiration (expirationstring, &etime_is_rel, &etime)) { fprintf (stderr, _ ("Invalid time format `%s'\n"), expirationstring); ret = 1; finish_command (); return; } add_qe = GNUNET_NAMESTORE_records_lookup (ns, &zone_pkey, name, &add_error_cb, NULL, &get_existing_record, NULL); } if (del) { if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("del")); ret = 1; finish_command (); return; } if (NULL == name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-n", _ ("del")); ret = 1; finish_command (); return; } del_qe = GNUNET_NAMESTORE_records_lookup (ns, &zone_pkey, name, &del_lookup_error_cb, NULL, &del_monitor, NULL); } if (purge_orphaned) { list_it = GNUNET_NAMESTORE_zone_iteration_start2 (ns, NULL, &zone_iteration_error_cb, NULL, &purge_orphans_iterator, NULL, &zone_iteration_finished, NULL, filter_flags); } else if (purge_zone) { if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("purge-zone")); ret = 1; finish_command (); return; } list_it = GNUNET_NAMESTORE_zone_iteration_start2 (ns, &zone_pkey, &zone_iteration_error_cb, NULL, &purge_zone_iterator, NULL, &zone_iteration_finished, NULL, filter_flags); } else if (list || list_orphaned) { if (NULL != name) { if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("list")); ret = 1; finish_command (); return; } get_qe = GNUNET_NAMESTORE_records_lookup (ns, &zone_pkey, name, &lookup_error_cb, NULL, &display_record_lookup, NULL); } else list_it = GNUNET_NAMESTORE_zone_iteration_start2 (ns, (NULL == ego_name) ? NULL : &zone_pkey, &zone_iteration_error_cb, NULL, &display_record_iterator, NULL, &zone_iteration_finished, NULL, filter_flags); } if (NULL != reverse_pkey) { struct GNUNET_IDENTITY_PublicKey pubkey; if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("reverse-pkey")); ret = 1; finish_command (); return; } if (GNUNET_OK != GNUNET_IDENTITY_public_key_from_string (reverse_pkey, &pubkey)) { fprintf (stderr, _ ("Invalid public key for reverse lookup `%s'\n"), reverse_pkey); ret = 1; finish_command (); return; } reverse_qe = GNUNET_NAMESTORE_zone_to_name (ns, &zone_pkey, &pubkey, &reverse_error_cb, NULL, &handle_reverse_lookup, NULL); } if (NULL != uri) { char sh[105]; char sname[64]; struct GNUNET_IDENTITY_PublicKey pkey; if (NULL == ego_name) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-z", _ ("uri")); ret = 1; finish_command (); return; } memset (sh, 0, 105); memset (sname, 0, 64); if ((2 != (sscanf (uri, "gnunet://gns/%58s/%63s", sh, sname))) || (GNUNET_OK != GNUNET_IDENTITY_public_key_from_string (sh, &pkey))) { fprintf (stderr, _ ("Invalid URI `%s'\n"), uri); ret = 1; finish_command (); return; } if (NULL == expirationstring) { fprintf (stderr, _ ("Missing option `%s' for operation `%s'\n"), "-e", _ ("add")); ret = 1; finish_command (); return; } if (GNUNET_OK != parse_expiration (expirationstring, &etime_is_rel, &etime)) { fprintf (stderr, _ ("Invalid time format `%s'\n"), expirationstring); ret = 1; finish_command (); return; } memset (&rd, 0, sizeof(rd)); rd.data = &pkey; rd.data_size = GNUNET_IDENTITY_public_key_get_length (&pkey); rd.record_type = ntohl (pkey.type); rd.expiration_time = etime; if (GNUNET_YES == etime_is_rel) rd.flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; if (1 == is_shadow) rd.flags |= GNUNET_GNSRECORD_RF_SHADOW; add_qe_uri = GNUNET_NAMESTORE_records_store (ns, &zone_pkey, sname, 1, &rd, &add_continuation, &add_qe_uri); } if (monitor) { zm = GNUNET_NAMESTORE_zone_monitor_start2 (cfg, (NULL != ego_name) ? &zone_pkey : NULL, GNUNET_YES, &monitor_error_cb, NULL, &display_record_monitor, NULL, &sync_cb, NULL, filter_flags); } } #define MAX_LINE_LEN 4086 #define MAX_ARGS 20 static int get_identity_for_string (const char *str, struct GNUNET_IDENTITY_PrivateKey *zk) { const struct GNUNET_IDENTITY_PrivateKey *privkey; struct GNUNET_IDENTITY_PublicKey pubkey; struct GNUNET_IDENTITY_PublicKey ego_pubkey; struct EgoEntry *ego_entry; if (GNUNET_OK == GNUNET_IDENTITY_public_key_from_string (str, &pubkey)) { for (ego_entry = ego_head; NULL != ego_entry; ego_entry = ego_entry->next) { privkey = GNUNET_IDENTITY_ego_get_private_key (ego_entry->ego); GNUNET_IDENTITY_ego_get_public_key (ego_entry->ego, &ego_pubkey); if (0 == memcmp (&ego_pubkey, &pubkey, sizeof (pubkey))) { *zk = *privkey; return GNUNET_OK; } } } else { for (ego_entry = ego_head; NULL != ego_entry; ego_entry = ego_entry->next) { /** FIXME: Check for zTLD? **/ if (0 != strcmp (str, ego_entry->identifier)) continue; *zk = *GNUNET_IDENTITY_ego_get_private_key (ego_entry->ego); return GNUNET_OK; } } return GNUNET_NO; } static void process_command_stdin () { char buf[MAX_LINE_LEN]; static struct GNUNET_IDENTITY_PrivateKey next_zone_key; static char next_name[GNUNET_DNSPARSER_MAX_NAME_LENGTH]; static int finished = GNUNET_NO; static int have_next_zonekey = GNUNET_NO; int zonekey_set = GNUNET_NO; char *tmp; if (GNUNET_YES == have_next_zonekey) { zone_pkey = next_zone_key; if (NULL != name) GNUNET_free (name); name = GNUNET_strdup (next_name); zonekey_set = GNUNET_YES; } while (NULL != fgets (buf, sizeof (buf), stdin)) { if (1 >= strlen (buf)) continue; if (buf[strlen (buf) - 1] == '\n') buf[strlen (buf) - 1] = '\0'; /** * Check if this is a new name. If yes, and we have records, store them. */ if (buf[strlen (buf) - 1] == ':') { memset (next_name, 0, sizeof (next_name)); strncpy (next_name, buf, strlen (buf) - 1); tmp = strchr (next_name, '.'); if (NULL == tmp) { fprintf (stderr, "Error parsing name `%s'\n", next_name); ns_qe = GNUNET_NAMESTORE_transaction_commit (ns, &commit_cb, NULL); ret = 1; return; } if (GNUNET_OK != get_identity_for_string (tmp + 1, &next_zone_key)) { fprintf (stderr, "Error parsing zone name `%s'\n", tmp + 1); ret = 1; GNUNET_SCHEDULER_shutdown (); return; } *tmp = '\0'; have_next_zonekey = GNUNET_YES; /* Run a command for the previous record set */ if (NULL != recordset) { run_with_zone_pkey (cfg); return; } zone_pkey = next_zone_key; if (NULL != name) GNUNET_free (name); name = GNUNET_strdup (next_name); zonekey_set = GNUNET_YES; continue; } if (GNUNET_NO == zonekey_set) { fprintf (stderr, "Warning, encountered recordline without zone\n"); continue; } parse_recordline (buf); } if (GNUNET_NO == finished) { if (NULL != recordset) { if (GNUNET_YES == zonekey_set) { run_with_zone_pkey (cfg); /** one last time **/ finished = GNUNET_YES; return; } fprintf (stderr, "Warning, encountered recordline without zone\n"); } } ns_qe = GNUNET_NAMESTORE_transaction_commit (ns, &commit_cb, NULL); return; } static void begin_cb (void *cls, enum GNUNET_ErrorCode ec) { ns_qe = NULL; if (GNUNET_EC_NONE != ec) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to start transaction: %s\n", GNUNET_ErrorCode_get_hint (ec)); GNUNET_SCHEDULER_shutdown (); return; } if (read_from_stdin) { process_command_stdin (); return; } run_with_zone_pkey (cfg); } /** * Function called with ALL of the egos known to the * identity service, used on startup if the user did * not specify a zone on the command-line. * Once the iteration is done (@a ego is NULL), we * ask for the default ego for "namestore". * * @param cls a `struct GNUNET_CONFIGURATION_Handle` * @param ego an ego, NULL for end of iteration * @param ctx NULL * @param name name associated with @a ego */ static void id_connect_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego, void **ctx, const char *name) { struct GNUNET_IDENTITY_PublicKey pk; struct EgoEntry *ego_entry; (void) ctx; (void) name; if ((NULL != name) && (NULL != ego)) { ego_entry = GNUNET_new (struct EgoEntry); GNUNET_IDENTITY_ego_get_public_key (ego, &pk); ego_entry->ego = ego; ego_entry->identifier = GNUNET_strdup (name); GNUNET_CONTAINER_DLL_insert_tail (ego_head, ego_tail, ego_entry); if ((NULL != ego_name) && (0 == strcmp (name, ego_name))) zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego); return; } if (NULL != ego) return; ns_qe = GNUNET_NAMESTORE_transaction_begin (ns, &begin_cb, (void *) cfg); } /** * Main function that will be run. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param cfg configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *_cfg) { (void) cls; (void) args; (void) cfgfile; cfg = _cfg; if (NULL != args[0]) GNUNET_log ( GNUNET_ERROR_TYPE_WARNING, _ ("Superfluous command line arguments (starting with `%s') ignored\n"), args[0]); GNUNET_SCHEDULER_add_shutdown (&do_shutdown, (void *) cfg); ns = GNUNET_NAMESTORE_connect (cfg); if (NULL == ns) { fprintf (stderr, _ ("Failed to connect to namestore\n")); GNUNET_SCHEDULER_shutdown (); return; } idh = GNUNET_IDENTITY_connect (cfg, &id_connect_cb, (void *) cfg); if (NULL == idh) { ret = -1; fprintf (stderr, _ ("Cannot connect to identity service\n")); GNUNET_SCHEDULER_shutdown (); } } /** * The main function for gnunet-namestore. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { int lret; struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_flag ('a', "add", gettext_noop ("add record"), &add), GNUNET_GETOPT_option_flag ('d', "delete", gettext_noop ("delete record"), &del), GNUNET_GETOPT_option_flag ('D', "display", gettext_noop ("display records"), &list), GNUNET_GETOPT_option_flag ('S', "from-stdin", gettext_noop ("read commands from stdin"), &read_from_stdin), GNUNET_GETOPT_option_string ( 'e', "expiration", "TIME", gettext_noop ( "expiration time for record to use (for adding only), \"never\" is possible"), &expirationstring), GNUNET_GETOPT_option_string ('i', "nick", "NICKNAME", gettext_noop ( "set the desired nick name for the zone"), &nickstring), GNUNET_GETOPT_option_flag ('m', "monitor", gettext_noop ( "monitor changes in the namestore"), &monitor), GNUNET_GETOPT_option_string ('n', "name", "NAME", gettext_noop ( "name of the record to add/delete/display"), &name), GNUNET_GETOPT_option_flag ('r', "recordline", gettext_noop ("Output in recordline format"), &output_recordline), GNUNET_GETOPT_option_string ('Z', "zone-to-name", "KEY", gettext_noop ( "determine our name for the given KEY"), &reverse_pkey), GNUNET_GETOPT_option_string ('t', "type", "TYPE", gettext_noop ( "type of the record to add/delete/display"), &typestring), GNUNET_GETOPT_option_string ('u', "uri", "URI", gettext_noop ("URI to import into our zone"), &uri), GNUNET_GETOPT_option_string ('V', "value", "VALUE", gettext_noop ( "value of the record to add/delete"), &value), GNUNET_GETOPT_option_flag ('p', "public", gettext_noop ("create or list public record"), &is_public), GNUNET_GETOPT_option_flag ('o', "omit-private", gettext_noop ("omit private records"), &omit_private), GNUNET_GETOPT_option_flag ('T', "include-maintenance", gettext_noop ( "do not filter maintenance records"), &include_maintenance), GNUNET_GETOPT_option_flag ('P', "purge-orphans", gettext_noop ( "purge namestore of all orphans"), &purge_orphaned), GNUNET_GETOPT_option_flag ('O', "list-orphans", gettext_noop ( "show private key for orphaned records for recovery using `gnunet-identity -C -P '. Use in combination with --display"), &list_orphaned), GNUNET_GETOPT_option_flag ('X', "purge-zone-records", gettext_noop ( "delete all records in specified zone"), &purge_zone), GNUNET_GETOPT_option_flag ( 's', "shadow", gettext_noop ( "create shadow record (only valid if all other records of the same type have expired"), &is_shadow), GNUNET_GETOPT_option_string ('z', "zone", "EGO", gettext_noop ( "name of the ego controlling the zone"), &ego_name), GNUNET_GETOPT_OPTION_END }; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; is_public = -1; is_shadow = -1; GNUNET_log_setup ("gnunet-namestore", "WARNING", NULL); if (GNUNET_OK != (lret = GNUNET_PROGRAM_run (argc, argv, "gnunet-namestore", _ ("GNUnet zone manipulation tool"), options, &run, NULL))) { GNUNET_free_nz ((void *) argv); // FIXME // GNUNET_CRYPTO_ecdsa_key_clear (&zone_pkey); return lret; } GNUNET_free_nz ((void *) argv); // FIXME // GNUNET_CRYPTO_ecdsa_key_clear (&zone_pkey); return ret; } /* end of gnunet-namestore.c */