/*
This file is part of GNUnet.
Copyright (C) 2012, 2013, 2014 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 .
*/
/**
* @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;
/**
* 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;
/**
* Expirationstring converted to relative time.
*/
static struct GNUNET_TIME_Relative etime_rel;
/**
* Expirationstring converted to absolute time.
*/
static struct GNUNET_TIME_Absolute etime_abs;
/**
* 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 != 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 *typestring;
char *s;
const char *ets;
struct GNUNET_TIME_Absolute at;
struct GNUNET_TIME_Relative rt;
if ( (NULL != name) &&
(0 != strcmp (name, rname)) )
{
GNUNET_NAMESTORE_zone_iterator_next (list_it,
1);
return;
}
FPRINTF (stdout,
"%s:\n",
rname);
for (unsigned int i=0;idata = 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;
if (GNUNET_YES == etime_is_rel)
{
rde->expiration_time = etime_rel.rel_value_us;
rde->flags |= GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
}
else if (GNUNET_NO == etime_is_rel)
rde->expiration_time = etime_abs.abs_value_us;
else
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;inext = *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',
"record",
"RECORDLINE",
gettext_noop ("complete record on one line to add/delete/display; 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
};
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 !=
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 1;
}
GNUNET_free ((void*) argv);
GNUNET_CRYPTO_ecdsa_key_clear (&zone_pkey);
return ret;
}
/* end of gnunet-namestore.c */