/*
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
/**
* 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;
};
/**
* Handle to the namestore.
*/
static struct GNUNET_NAMESTORE_Handle *ns;
/**
* Private key for the our zone.
*/
static struct GNUNET_CRYPTO_EcdsaPrivateKey zone_pkey;
/**
* Handle to identity lookup.
*/
static struct GNUNET_IDENTITY_EgoLookup *el;
/**
* Identity service handle
*/
static struct GNUNET_IDENTITY_Handle *idh;
/**
* Obtain default ego
*/
struct GNUNET_IDENTITY_Operation *get_default;
/**
* Name of the ego controlling the zone.
*/
static char *ego_name;
/**
* Desired action is to add a record.
*/
static int add;
/**
* 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;
/**
* Desired action is to list records.
*/
static int list;
/**
* List iterator for the 'list' operation.
*/
static struct GNUNET_NAMESTORE_ZoneIterator *list_it;
/**
* 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_RECORD)
*/
static int is_shadow;
/**
* 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;
/**
* 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;
/**
* Task run on shutdown. Cleans up everything.
*
* @param cls unused
*/
static void
do_shutdown (void *cls)
{
(void) cls;
if (NULL != get_default)
{
GNUNET_IDENTITY_cancel (get_default);
get_default = NULL;
}
if (NULL != idh)
{
GNUNET_IDENTITY_disconnect (idh);
idh = NULL;
}
if (NULL != el)
{
GNUNET_IDENTITY_ego_lookup_cancel (el);
el = NULL;
}
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 != ns)
{
GNUNET_NAMESTORE_disconnect (ns);
ns = NULL;
}
memset (&zone_pkey, 0, sizeof(zone_pkey));
if (NULL != uri)
{
GNUNET_free (uri);
uri = NULL;
}
if (NULL != zm)
{
GNUNET_NAMESTORE_zone_monitor_stop (zm);
zm = NULL;
}
if (NULL != data)
{
GNUNET_free (data);
data = NULL;
}
}
/**
* Check if we are finished, and if so, perform shutdown.
*/
static void
test_finished ()
{
if ((NULL == add_qe) && (NULL == add_qe_uri) && (NULL == get_qe) &&
(NULL == del_qe) && (NULL == reverse_qe) && (NULL == list_it))
GNUNET_SCHEDULER_shutdown ();
}
/**
* Continuation called to notify client about result of the
* operation.
*
* @param cls closure, location of the QueueEntry pointer to NULL out
* @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
* #GNUNET_NO if content was already there
* #GNUNET_YES (or other positive value) on success
* @param emsg NULL on success, otherwise an error message
*/
static void
add_continuation (void *cls, int32_t success, const char *emsg)
{
struct GNUNET_NAMESTORE_QueueEntry **qe = cls;
*qe = NULL;
if (GNUNET_YES != success)
{
fprintf (stderr,
_ ("Adding record failed: %s\n"),
(GNUNET_NO == success) ? "record exists" : emsg);
if (GNUNET_NO != success)
ret = 1;
}
ret = 0;
test_finished ();
}
/**
* Continuation called to notify client about result of the
* operation.
*
* @param cls closure, unused
* @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
* #GNUNET_NO if content was already there
* #GNUNET_YES (or other positive value) on success
* @param emsg NULL on success, otherwise an error message
*/
static void
del_continuation (void *cls, int32_t success, const char *emsg)
{
(void) cls;
del_qe = NULL;
if (GNUNET_NO == success)
{
fprintf (stderr,
_ ("Deleting record failed, record does not exist%s%s\n"),
(NULL != emsg) ? ": " : "",
(NULL != emsg) ? emsg : "");
}
if (GNUNET_SYSERR == success)
{
fprintf (stderr,
_ ("Deleting record failed%s%s\n"),
(NULL != emsg) ? ": " : "",
(NULL != emsg) ? emsg : "");
}
test_finished ();
}
/**
* Function called when we are done with a zone iteration.
*/
static void
zone_iteration_finished (void *cls)
{
(void) cls;
list_it = NULL;
test_finished ();
}
/**
* 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;
test_finished ();
}
/**
* 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 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;
int have_record;
if ((NULL != name) && (0 != strcmp (name, rname)))
{
GNUNET_NAMESTORE_zone_iterator_next (list_it, 1);
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;
fprintf (stdout, "%s:\n", rname);
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);
}
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_RECORD)) ? "SHADOW"
: "");
GNUNET_free (s);
}
fprintf (stdout, "%s", "\n");
}
/**
* 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_CRYPTO_EcdsaPrivateKey *zone_key,
const char *rname,
unsigned int rd_len,
const struct GNUNET_GNSRECORD_Data *rd)
{
(void) cls;
(void) zone_key;
display_record (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_CRYPTO_EcdsaPrivateKey *zone_key,
const char *rname,
unsigned int rd_len,
const struct GNUNET_GNSRECORD_Data *rd)
{
(void) cls;
(void) zone_key;
display_record (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_CRYPTO_EcdsaPrivateKey *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 (rname, rd_len, rd);
test_finished ();
}
/**
* 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");
test_finished ();
}
/**
* Function called if lookup fails.
*/
static void
add_error_cb (void *cls)
{
(void) cls;
add_qe = NULL;
GNUNET_break (0);
ret = 1;
test_finished ();
}
/**
* 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_CRYPTO_EcdsaPrivateKey *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;
test_finished ();
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_CNAME:
fprintf (
stderr,
_ (
"A %s record exists already under `%s', no other records can be added.\n"),
"CNAME",
rec_name);
ret = 1;
test_finished ();
return;
case GNUNET_GNSRECORD_TYPE_PKEY:
fprintf (
stderr,
_ (
"A %s record exists already under `%s', no other records can be added.\n"),
"PKEY",
rec_name);
ret = 1;
test_finished ();
return;
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;
test_finished ();
return;
}
break;
}
}
switch (type)
{
case GNUNET_DNSPARSER_TYPE_CNAME:
if (0 != rd_count)
{
fprintf (stderr,
_ (
"Records already exist under `%s', cannot add `%s' record.\n"),
rec_name,
"CNAME");
ret = 1;
test_finished ();
return;
}
break;
case GNUNET_GNSRECORD_TYPE_PKEY:
if (0 != rd_count)
{
fprintf (stderr,
_ (
"Records already exist under `%s', cannot add `%s' record.\n"),
rec_name,
"PKEY");
ret = 1;
test_finished ();
return;
}
break;
case GNUNET_GNSRECORD_TYPE_GNS2DNS:
for (unsigned int i = 0; i < rd_count; i++)
if (GNUNET_GNSRECORD_TYPE_GNS2DNS != rd[i].record_type)
{
fprintf (
stderr,
_ (
"Non-GNS2DNS records already exist under `%s', cannot add GNS2DNS record.\n"),
rec_name);
ret = 1;
test_finished ();
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_RECORD;
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_CRYPTO_EcdsaPrivateKey *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);
test_finished ();
}
/**
* 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;
test_finished ();
}
/**
* 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_CRYPTO_EcdsaPrivateKey *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;
test_finished ();
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_non_null (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);
test_finished ();
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);
}
/**
* Parse expiration time.
*
* @param expirationstring text to parse
* @param etime_is_rel[out] set to #GNUNET_YES if time is relative
* @param etime[out] 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;
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;
}
/**
* Function called when namestore is done with the replace
* operation.
*
* @param cls NULL
* @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
* #GNUNET_NO if content was already there or not found
* #GNUNET_YES (or other positive value) on success
* @param emsg NULL on success, otherwise an error message
*/
static void
replace_cont (void *cls, int success, const char *emsg)
{
(void) cls;
set_qe = NULL;
if (GNUNET_OK != success)
{
GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
_ ("Failed to replace records: %s\n"),
emsg);
ret = 1; /* fail from 'main' */
}
GNUNET_SCHEDULER_shutdown ();
}
/**
* 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;
if (! (add | del | list | (NULL != nickstring) | (NULL != uri)
| (NULL != reverse_pkey) | (NULL != recordset)))
{
/* nothing more to be done */
fprintf (stderr, _ ("No options given\n"));
GNUNET_SCHEDULER_shutdown ();
return;
}
ns = GNUNET_NAMESTORE_connect (cfg);
if (NULL == ns)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Failed to connect to namestore\n"));
return;
}
if (NULL != recordset)
{
/* replace entire record set */
unsigned int rd_count;
struct GNUNET_GNSRECORD_Data *rd;
if (NULL == name)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-R",
_ ("replace"));
GNUNET_SCHEDULER_shutdown ();
ret = 1;
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 (add)
{
if (NULL == name)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-n",
_ ("add"));
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
if (NULL == typestring)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-t",
_ ("add"));
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
type = GNUNET_GNSRECORD_typename_to_number (typestring);
if (UINT32_MAX == type)
{
fprintf (stderr, _ ("Unsupported type `%s'\n"), typestring);
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
if (NULL == value)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-V",
_ ("add"));
ret = 1;
GNUNET_SCHEDULER_shutdown ();
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);
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
if (NULL == expirationstring)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-e",
_ ("add"));
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
if (GNUNET_OK != parse_expiration (expirationstring, &etime_is_rel, &etime))
{
fprintf (stderr, _ ("Invalid time format `%s'\n"), expirationstring);
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
add_qe = GNUNET_NAMESTORE_records_lookup (ns,
&zone_pkey,
name,
&add_error_cb,
NULL,
&get_existing_record,
NULL);
}
if (del)
{
if (NULL == name)
{
fprintf (stderr,
_ ("Missing option `%s' for operation `%s'\n"),
"-n",
_ ("del"));
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
del_qe = GNUNET_NAMESTORE_records_lookup (ns,
&zone_pkey,
name,
&del_lookup_error_cb,
NULL,
&del_monitor,
NULL);
}
if (list)
{
if (NULL != name)
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_start (ns,
&zone_pkey,
&zone_iteration_error_cb,
NULL,
&display_record_iterator,
NULL,
&zone_iteration_finished,
NULL);
}
if (NULL != reverse_pkey)
{
struct GNUNET_CRYPTO_EcdsaPublicKey pubkey;
if (GNUNET_OK !=
GNUNET_CRYPTO_ecdsa_public_key_from_string (reverse_pkey,
strlen (reverse_pkey),
&pubkey))
{
fprintf (stderr,
_ ("Invalid public key for reverse lookup `%s'\n"),
reverse_pkey);
GNUNET_SCHEDULER_shutdown ();
}
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_CRYPTO_EcdsaPublicKey pkey;
GNUNET_STRINGS_utf8_tolower (uri, uri);
if ((2 != (sscanf (uri, "gnunet://gns/%52s/%63s", sh, sname))) ||
(GNUNET_OK !=
GNUNET_CRYPTO_ecdsa_public_key_from_string (sh, strlen (sh), &pkey)))
{
fprintf (stderr, _ ("Invalid URI `%s'\n"), uri);
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
memset (&rd, 0, sizeof(rd));
rd.data = &pkey;
rd.data_size = sizeof(struct GNUNET_CRYPTO_EcdsaPublicKey);
rd.record_type = GNUNET_GNSRECORD_TYPE_PKEY;
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_RECORD;
add_qe_uri = GNUNET_NAMESTORE_records_store (ns,
&zone_pkey,
sname,
1,
&rd,
&add_continuation,
&add_qe_uri);
}
if (NULL != nickstring)
{
if (0 == strlen (nickstring))
{
fprintf (stderr, _ ("Invalid nick `%s'\n"), nickstring);
GNUNET_SCHEDULER_shutdown ();
ret = 1;
return;
}
add_qe_uri = GNUNET_NAMESTORE_set_nick (ns,
&zone_pkey,
nickstring,
&add_continuation,
&add_qe_uri);
}
if (monitor)
{
zm = GNUNET_NAMESTORE_zone_monitor_start (cfg,
&zone_pkey,
GNUNET_YES,
&monitor_error_cb,
NULL,
&display_record_monitor,
NULL,
&sync_cb,
NULL);
}
}
/**
* Callback invoked from identity service with ego information.
* An @a ego of NULL means the ego was not found.
*
* @param cls closure with the configuration
* @param ego an ego known to identity service, or NULL
*/
static void
identity_cb (void *cls, const struct GNUNET_IDENTITY_Ego *ego)
{
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
el = NULL;
if ((NULL != name) && (0 != strchr (name, '.')))
{
fprintf (stderr,
_ ("Label `%s' contains `.' which is not allowed\n"),
name);
GNUNET_SCHEDULER_shutdown ();
ret = -1;
return;
}
if (NULL == ego)
{
if (NULL != ego_name)
{
fprintf (stderr,
_ ("Ego `%s' not known to identity service\n"),
ego_name);
}
GNUNET_SCHEDULER_shutdown ();
ret = -1;
return;
}
zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
GNUNET_free_non_null (ego_name);
ego_name = NULL;
run_with_zone_pkey (cfg);
}
/**
* Function called with the default ego to be used for GNS
* operations. Used if the user did not specify a zone via
* command-line or environment variables.
*
* @param cls NULL
* @param ego default ego, NULL for none
* @param ctx NULL
* @param name unused
*/
static void
default_ego_cb (void *cls,
struct GNUNET_IDENTITY_Ego *ego,
void **ctx,
const char *name)
{
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
(void) ctx;
(void) name;
get_default = NULL;
if (NULL == ego)
{
fprintf (stderr,
_ ("No default identity configured for `namestore' subsystem\n"
"Run gnunet-identity -s namestore -e $NAME to set the default to $NAME\n"
"Run gnunet-identity -d to get a list of choices for $NAME\n"));
GNUNET_SCHEDULER_shutdown ();
ret = -1;
return;
}
else
{
identity_cb ((void *) cfg, ego);
}
}
/**
* 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)
{
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
(void) ctx;
(void) name;
if (NULL != ego)
return;
get_default =
GNUNET_IDENTITY_get (idh, "namestore", &default_ego_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)
{
const char *pkey_str;
(void) cls;
(void) args;
(void) cfgfile;
if (NULL != args[0])
GNUNET_log (
GNUNET_ERROR_TYPE_WARNING,
_ ("Superfluous command line arguments (starting with `%s') ignored\n"),
args[0]);
if ((NULL != args[0]) && (NULL == uri))
uri = GNUNET_strdup (args[0]);
GNUNET_SCHEDULER_add_shutdown (&do_shutdown, (void *) cfg);
pkey_str = getenv ("GNUNET_NAMESTORE_EGO_PRIVATE_KEY");
if (NULL != pkey_str)
{
if (GNUNET_OK != GNUNET_STRINGS_string_to_data (pkey_str,
strlen (pkey_str),
&zone_pkey,
sizeof(zone_pkey)))
{
fprintf (stderr,
"Malformed private key `%s' in $%s\n",
pkey_str,
"GNUNET_NAMESTORE_EGO_PRIVATE_KEY");
ret = 1;
GNUNET_SCHEDULER_shutdown ();
return;
}
run_with_zone_pkey (cfg);
return;
}
if (NULL == ego_name)
{
idh = GNUNET_IDENTITY_connect (cfg, &id_connect_cb, (void *) cfg);
if (NULL == idh)
fprintf (stderr, _ ("Cannot connect to identity service\n"));
ret = -1;
return;
}
el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name, &identity_cb, (void *) cfg);
}
/**
* Command-line option parser function that allows the user to specify
* a complete record as one argument for adding/removing. A pointer
* to the head of the list of record sets must be passed as the "scls"
* argument.
*
* @param ctx command line processor context
* @param scls must be of type "struct GNUNET_FS_Uri **"
* @param option name of the option (typically 'R')
* @param value command line argument given; format is
* "TTL TYPE FLAGS VALUE" where TTL is an expiration time (rel or abs),
* always given in seconds (without the unit),
* TYPE is a DNS/GNS record type, FLAGS is either "n" for no flags or
* a combination of 's' (shadow) and 'p' (public) and VALUE is the
* value (in human-readable format)
* @return #GNUNET_OK on success
*/
static int
multirecord_process (struct GNUNET_GETOPT_CommandLineProcessorContext *ctx,
void *scls,
const char *option,
const char *value)
{
struct RecordSetEntry **head = scls;
struct RecordSetEntry *r;
struct GNUNET_GNSRECORD_Data record;
char *cp;
char *tok;
char *saveptr;
int etime_is_rel;
void *raw_data;
(void) ctx;
(void) option;
cp = GNUNET_strdup (value);
tok = strtok_r (cp, " ", &saveptr);
if (NULL == tok)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Empty record line argument is not allowed.\n"));
GNUNET_free (cp);
return GNUNET_SYSERR;
}
{
char *etime_in_s;
GNUNET_asprintf (&etime_in_s, "%s s", tok);
if (GNUNET_OK !=
parse_expiration (etime_in_s, &etime_is_rel, &record.expiration_time))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Invalid expiration time `%s' (must be without unit)\n"),
tok);
GNUNET_free (cp);
GNUNET_free (etime_in_s);
return GNUNET_SYSERR;
}
GNUNET_free (etime_in_s);
}
tok = strtok_r (NULL, " ", &saveptr);
if (NULL == tok)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Missing entries in record line `%s'.\n"),
value);
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,
_ ("Missing entries in record line `%s'.\n"),
value);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
record.flags = GNUNET_GNSRECORD_RF_NONE;
if (etime_is_rel)
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_SHADOW_RECORD;
/* find beginning of record value */
tok = strchr (&value[tok - cp], (unsigned char) ' ');
if (NULL == tok)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
_ ("Missing entries in record line `%s'.\n"),
value);
GNUNET_free (cp);
return GNUNET_SYSERR;
}
GNUNET_free (cp);
tok++; /* skip space */
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;
}
/**
* Allow user to specify keywords.
*
* @param shortName short name of the option
* @param name long name of the option
* @param argumentHelp help text for the option argument
* @param description long help text for the option
* @param[out] topKeywords set to the desired value
*/
struct GNUNET_GETOPT_CommandLineOption
multirecord_option (char shortName,
const char *name,
const char *argumentHelp,
const char *description,
struct RecordSetEntry **rs)
{
struct GNUNET_GETOPT_CommandLineOption clo = { .shortName = shortName,
.name = name,
.argumentHelp = argumentHelp,
.description = description,
.require_argument = 1,
.processor =
&multirecord_process,
.scls = (void *) rs };
return clo;
}
/**
* 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)
{
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_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_string ('r',
"reverse",
"PKEY",
gettext_noop (
"determine our name for the given PKEY"),
&reverse_pkey),
multirecord_option (
'R',
"replace",
"RECORDLINE",
gettext_noop (
"set record set to values given by (possibly multiple) RECORDLINES; can be specified multiple times"),
&recordset),
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 (
'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 };
int lret;
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 ((void *) argv);
GNUNET_CRYPTO_ecdsa_key_clear (&zone_pkey);
return lret;
}
GNUNET_free ((void *) argv);
GNUNET_CRYPTO_ecdsa_key_clear (&zone_pkey);
return ret;
}
/* end of gnunet-namestore.c */