aboutsummaryrefslogtreecommitdiff
path: root/src/cli/namestore/gnunet-namestore-zonefile.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/cli/namestore/gnunet-namestore-zonefile.c')
-rw-r--r--src/cli/namestore/gnunet-namestore-zonefile.c729
1 files changed, 729 insertions, 0 deletions
diff --git a/src/cli/namestore/gnunet-namestore-zonefile.c b/src/cli/namestore/gnunet-namestore-zonefile.c
new file mode 100644
index 000000000..d43e88006
--- /dev/null
+++ b/src/cli/namestore/gnunet-namestore-zonefile.c
@@ -0,0 +1,729 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2012, 2013, 2014, 2019, 2022 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20/**
21 * @file gnunet-namestore-dbtool.c
22 * @brief command line tool to manipulate the database backends for the namestore
23 * @author Martin Schanzenbach
24 *
25 */
26#include "platform.h"
27#include <gnunet_util_lib.h>
28#include <gnunet_namestore_plugin.h>
29
30#define MAX_RECORDS_PER_NAME 50
31
32/**
33 * Maximum length of a zonefile line
34 */
35#define MAX_ZONEFILE_LINE_LEN 4096
36
37/**
38 * FIXME: Soft limit this?
39 */
40#define MAX_ZONEFILE_RECORD_DATA_LEN 2048
41
42/**
43 * The record data under a single label. Reused.
44 * Hard limit.
45 */
46static struct GNUNET_GNSRECORD_Data rd[MAX_RECORDS_PER_NAME];
47
48/**
49 * Current record $TTL to use
50 */
51static struct GNUNET_TIME_Relative ttl;
52
53/**
54 * Current origin
55 */
56static char origin[GNUNET_DNSPARSER_MAX_NAME_LENGTH];
57
58/**
59 * Number of records for currently parsed set
60 */
61static unsigned int rd_count = 0;
62
63/**
64 * Return code
65 */
66static int ret = 0;
67
68/**
69 * Name of the ego
70 */
71static char *ego_name = NULL;
72
73/**
74 * Currently read line or NULL on EOF
75 */
76static char *res;
77
78/**
79 * Statistics, how many published record sets
80 */
81static unsigned int published_sets = 0;
82
83/**
84 * Statistics, how many records published in aggregate
85 */
86static unsigned int published_records = 0;
87
88
89/**
90 * Handle to identity lookup.
91 */
92static struct GNUNET_IDENTITY_EgoLookup *el;
93
94/**
95 * Private key for the our zone.
96 */
97static struct GNUNET_CRYPTO_PrivateKey zone_pkey;
98
99/**
100 * Queue entry for the 'add' operation.
101 */
102static struct GNUNET_NAMESTORE_QueueEntry *ns_qe;
103
104/**
105 * Handle to the namestore.
106 */
107static struct GNUNET_NAMESTORE_Handle *ns;
108
109/**
110 * Origin create operations
111 */
112static struct GNUNET_IDENTITY_Operation *id_op;
113
114/**
115 * Handle to IDENTITY
116 */
117static struct GNUNET_IDENTITY_Handle *id;
118
119/**
120 * Current configurataion
121 */
122static const struct GNUNET_CONFIGURATION_Handle *cfg;
123
124/**
125 * Scheduled parse task
126 */
127static struct GNUNET_SCHEDULER_Task *parse_task;
128
129/**
130 * The current state of the parser
131 */
132static int state;
133
134enum ZonefileImportState
135{
136
137 /* Uninitialized */
138 ZS_READY,
139
140 /* The initial state */
141 ZS_ORIGIN_SET,
142
143 /* The $ORIGIN has changed */
144 ZS_ORIGIN_CHANGED,
145
146 /* The record name/label has changed */
147 ZS_NAME_CHANGED
148
149};
150
151
152
153/**
154 * Task run on shutdown. Cleans up everything.
155 *
156 * @param cls unused
157 */
158static void
159do_shutdown (void *cls)
160{
161 (void) cls;
162 if (NULL != ego_name)
163 GNUNET_free (ego_name);
164 if (NULL != el)
165 {
166 GNUNET_IDENTITY_ego_lookup_cancel (el);
167 el = NULL;
168 }
169 if (NULL != ns_qe)
170 GNUNET_NAMESTORE_cancel (ns_qe);
171 if (NULL != id_op)
172 GNUNET_IDENTITY_cancel (id_op);
173 if (NULL != ns)
174 GNUNET_NAMESTORE_disconnect (ns);
175 if (NULL != id)
176 GNUNET_IDENTITY_disconnect (id);
177 for (int i = 0; i < rd_count; i++)
178 {
179 void *rd_ptr = (void*) rd[i].data;
180 GNUNET_free (rd_ptr);
181 }
182 if (NULL != parse_task)
183 GNUNET_SCHEDULER_cancel (parse_task);
184}
185
186static void
187parse (void *cls);
188
189static char*
190trim (char *line)
191{
192 char *ltrimmed = line;
193 int ltrimmed_len;
194 int quoted = 0;
195
196 // Trim all whitespace to the left
197 while (*ltrimmed == ' ')
198 ltrimmed++;
199 ltrimmed_len = strlen (ltrimmed);
200 // Find the first occurence of an unqoted ';', which is our comment
201 for (int i = 0; i < ltrimmed_len; i++)
202 {
203 if (ltrimmed[i] == '"')
204 quoted = ! quoted;
205 if ((ltrimmed[i] != ';') || quoted)
206 continue;
207 ltrimmed[i] = '\0';
208 }
209 ltrimmed_len = strlen (ltrimmed);
210 // Remove trailing whitespace
211 for (int i = ltrimmed_len; i > 0; i--)
212 {
213 if (ltrimmed[i - 1] != ' ')
214 break;
215 ltrimmed[i - 1] = '\0';
216 }
217 ltrimmed_len = strlen (ltrimmed);
218 if (ltrimmed[ltrimmed_len - 1] == '\n')
219 ltrimmed[ltrimmed_len - 1] = ' ';
220 return ltrimmed;
221}
222
223static char*
224next_token (char *token)
225{
226 char *next = token;
227 while (*next == ' ')
228 next++;
229 return next;
230}
231
232static int
233parse_ttl (char *token, struct GNUNET_TIME_Relative *ttl)
234{
235 char *next;
236 unsigned int ttl_tmp;
237
238 next = strchr (token, ';');
239 if (NULL != next)
240 next[0] = '\0';
241 next = strchr (token, ' ');
242 if (NULL != next)
243 next[0] = '\0';
244 if (1 != sscanf (token, "%u", &ttl_tmp))
245 {
246 fprintf (stderr, "Unable to parse TTL `%s'\n", token);
247 return GNUNET_SYSERR;
248 }
249 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TTL is: %u\n", ttl_tmp);
250 ttl->rel_value_us = ttl_tmp * 1000 * 1000;
251 return GNUNET_OK;
252}
253
254static int
255parse_origin (char *token, char *origin)
256{
257 char *next;
258 next = strchr (token, ';');
259 if (NULL != next)
260 next[0] = '\0';
261 next = strchr (token, ' ');
262 if (NULL != next)
263 next[0] = '\0';
264 strcpy (origin, token);
265 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Origin is: %s\n", origin);
266 return GNUNET_OK;
267}
268
269static void
270origin_create_cb (void *cls, const struct GNUNET_CRYPTO_PrivateKey *pk,
271 enum GNUNET_ErrorCode ec)
272{
273 id_op = NULL;
274 if (GNUNET_EC_NONE != ec)
275 {
276 fprintf (stderr, "Error: %s\n", GNUNET_ErrorCode_get_hint (ec));
277 ret = 1;
278 GNUNET_SCHEDULER_shutdown ();
279 return;
280 }
281 state = ZS_ORIGIN_SET;
282 zone_pkey = *pk;
283 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
284}
285
286static void
287origin_lookup_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego)
288{
289
290 el = NULL;
291
292 if (NULL == ego)
293 {
294 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
295 "$ORIGIN %s does not exist, creating...\n", ego_name);
296 id_op = GNUNET_IDENTITY_create (id, ego_name, NULL,
297 GNUNET_PUBLIC_KEY_TYPE_ECDSA, // FIXME make configurable
298 origin_create_cb,
299 NULL);
300 return;
301 }
302 state = ZS_ORIGIN_SET;
303 zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
304 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
305}
306
307static void
308add_continuation (void *cls, enum GNUNET_ErrorCode ec)
309{
310 ns_qe = NULL;
311 if (GNUNET_EC_NONE != ec)
312 {
313 fprintf (stderr,
314 _ ("Failed to store records...\n"));
315 GNUNET_SCHEDULER_shutdown ();
316 ret = -1;
317 }
318 if (ZS_ORIGIN_CHANGED == state)
319 {
320 if (NULL != ego_name)
321 GNUNET_free (ego_name);
322 ego_name = GNUNET_strdup (origin);
323 if (ego_name[strlen (ego_name) - 1] == '.')
324 ego_name[strlen (ego_name) - 1] = '\0';
325 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
326 "Changing origin to %s\n", ego_name);
327 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name,
328 &origin_lookup_cb, NULL);
329 return;
330 }
331 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
332}
333
334
335
336/**
337 * Main function that will be run.
338 *
339 * TODO:
340 * - We must assume that names are not repeated later in the zonefile because
341 * our _store APIs are replacing. No sure if that is common in zonefiles.
342 * - We must only actually store a record set when the name to store changes or
343 * the end of the file is reached.
344 * that way we can group them and add (see above).
345 * - We need to hope our string formats are compatible, but seems ok.
346 *
347 * @param cls closure
348 * @param args remaining command-line arguments
349 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
350 * @param cfg configuration
351 */
352static void
353parse (void *cls)
354{
355 char buf[MAX_ZONEFILE_LINE_LEN];
356 char payload[MAX_ZONEFILE_RECORD_DATA_LEN];
357 char *next;
358 char *token;
359 char *payload_pos;
360 static char lastname[GNUNET_DNSPARSER_MAX_LABEL_LENGTH];
361 char newname[GNUNET_DNSPARSER_MAX_LABEL_LENGTH];
362 void *data;
363 size_t data_size;
364 int ttl_line = 0;
365 int type;
366 int bracket_unclosed = 0;
367 int quoted = 0;
368
369 parse_task = NULL;
370 /* use filename provided as 1st argument (stdin by default) */
371 int ln = 0;
372 while ((res = fgets (buf, sizeof(buf), stdin))) /* read each line of input */
373 {
374 ln++;
375 ttl_line = 0;
376 token = trim (buf);
377 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
378 "Trimmed line (bracket %s): `%s'\n",
379 (bracket_unclosed > 0) ? "unclosed" : "closed",
380 token);
381 if ((0 == strlen (token)) ||
382 ((1 == strlen (token)) && (' ' == *token)))
383 continue; // I guess we can safely ignore blank lines
384 if (bracket_unclosed == 0)
385 {
386 /* Payload is already parsed */
387 payload_pos = payload;
388 /* Find space */
389 next = strchr (token, ' ');
390 if (NULL == next)
391 {
392 fprintf (stderr, "Error at line %u: %s\n", ln, token);
393 ret = 1;
394 GNUNET_SCHEDULER_shutdown ();
395 return;
396 }
397 next[0] = '\0';
398 next++;
399 if (0 == (strcmp (token, "$ORIGIN")))
400 {
401 state = ZS_ORIGIN_CHANGED;
402 token = next_token (next);
403 }
404 else if (0 == (strcmp (token, "$TTL")))
405 {
406 ttl_line = 1;
407 token = next_token (next);
408 }
409 else
410 {
411 if (0 == strcmp (token, "IN")) // Inherit name from before
412 {
413 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
414 "Old name: %s\n", lastname);
415 strcpy (newname, lastname);
416 token[strlen (token)] = ' ';
417 }
418 else if (token[strlen (token) - 1] != '.') // no fqdn
419 {
420 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: %s\n", token);
421 if (GNUNET_DNSPARSER_MAX_LABEL_LENGTH < strlen (token))
422 {
423 fprintf (stderr,
424 _ ("Name `%s' is too long\n"),
425 token);
426 ret = 1;
427 GNUNET_SCHEDULER_shutdown ();
428 return;
429 }
430 strcpy (newname, token);
431 token = next_token (next);
432 }
433 else if (0 == strcmp (token, origin))
434 {
435 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: @\n");
436 strcpy (newname, "@");
437 token = next_token (next);
438 }
439 else
440 {
441 if (strlen (token) < strlen (origin))
442 {
443 fprintf (stderr, "Wrong origin: %s (expected %s)\n", token, origin);
444 break; // FIXME error?
445 }
446 if (0 != strcmp (token + (strlen (token) - strlen (origin)), origin))
447 {
448 fprintf (stderr, "Wrong origin: %s (expected %s)\n", token, origin);
449 break;
450 }
451 token[strlen (token) - strlen (origin) - 1] = '\0';
452 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: %s\n", token);
453 if (GNUNET_DNSPARSER_MAX_LABEL_LENGTH < strlen (token))
454 {
455 fprintf (stderr,
456 _ ("Name `%s' is too long\n"),
457 token);
458 ret = 1;
459 GNUNET_SCHEDULER_shutdown ();
460 return;
461 }
462 strcpy (newname, token);
463 token = next_token (next);
464 }
465 if (0 != strcmp (newname, lastname) &&
466 (0 < rd_count))
467 {
468 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
469 "Name changed %s->%s, storing record set of %u elements\n",
470 lastname, newname,
471 rd_count);
472 state = ZS_NAME_CHANGED;
473 }
474 else {
475 strcpy (lastname, newname);
476 }
477 }
478
479 if (ttl_line)
480 {
481 if (GNUNET_SYSERR == parse_ttl (token, &ttl))
482 {
483 fprintf (stderr, _ ("Failed to parse $TTL\n"));
484 ret = 1;
485 GNUNET_SCHEDULER_shutdown ();
486 return;
487 }
488 continue;
489 }
490 if (ZS_ORIGIN_CHANGED == state)
491 {
492 if (GNUNET_SYSERR == parse_origin (token, origin))
493 {
494 fprintf (stderr, _ ("Failed to parse $ORIGIN from %s\n"), token);
495 ret = 1;
496 GNUNET_SCHEDULER_shutdown ();
497 return;
498 }
499 break;
500 }
501 if (ZS_READY == state)
502 {
503 fprintf (stderr,
504 _ (
505 "You must provide $ORIGIN in your zonefile or via arguments (--zone)!\n"));
506 ret = 1;
507 GNUNET_SCHEDULER_shutdown ();
508 return;
509 }
510 // This is a record, let's go
511 if (MAX_RECORDS_PER_NAME == rd_count)
512 {
513 fprintf (stderr,
514 _ ("Only %u records per unique name supported.\n"),
515 MAX_RECORDS_PER_NAME);
516 ret = 1;
517 GNUNET_SCHEDULER_shutdown ();
518 return;
519 }
520 rd[rd_count].flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
521 rd[rd_count].expiration_time = ttl.rel_value_us;
522 next = strchr (token, ' ');
523 if (NULL == next)
524 {
525 fprintf (stderr, "Error, last token: %s\n", token);
526 ret = 1;
527 GNUNET_SCHEDULER_shutdown ();
528 break;
529 }
530 next[0] = '\0';
531 next++;
532 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "class is: %s\n", token);
533 while (*next == ' ')
534 next++;
535 token = next;
536 next = strchr (token, ' ');
537 if (NULL == next)
538 {
539 fprintf (stderr, "Error\n");
540 break;
541 }
542 next[0] = '\0';
543 next++;
544 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "type is: %s\n", token);
545 type = GNUNET_GNSRECORD_typename_to_number (token);
546 rd[rd_count].record_type = type;
547 while (*next == ' ')
548 next++;
549 token = next;
550 }
551 for (int i = 0; i < strlen (token); i++)
552 {
553 if (token[i] == '"')
554 quoted = ! quoted;
555 if ((token[i] == '(') && ! quoted)
556 bracket_unclosed++;
557 if ((token[i] == ')') && ! quoted)
558 bracket_unclosed--;
559 }
560 memcpy (payload_pos, token, strlen (token));
561 payload_pos += strlen (token);
562 if (bracket_unclosed > 0)
563 {
564 *payload_pos = ' ';
565 payload_pos++;
566 continue;
567 }
568 *payload_pos = '\0';
569 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "data is: %s\n\n", payload);
570 if (GNUNET_OK !=
571 GNUNET_GNSRECORD_string_to_value (type, payload,
572 &data,
573 &data_size))
574 {
575 fprintf (stderr,
576 _ ("Data `%s' invalid\n"),
577 payload);
578 ret = 1;
579 GNUNET_SCHEDULER_shutdown ();
580 return;
581 }
582 rd[rd_count].data = data;
583 rd[rd_count].data_size = data_size;
584 if (ZS_NAME_CHANGED == state)
585 break;
586 rd_count++;
587 }
588 if (rd_count > 0)
589 {
590 ns_qe = GNUNET_NAMESTORE_record_set_store (ns,
591 &zone_pkey,
592 lastname,
593 rd_count,
594 rd,
595 &add_continuation,
596 NULL);
597 published_sets++;
598 published_records += rd_count;
599 for (int i = 0; i < rd_count; i++)
600 {
601 data = (void*) rd[i].data;
602 GNUNET_free (data);
603 }
604 if (ZS_NAME_CHANGED == state)
605 {
606 rd[0] = rd[rd_count]; // recover last rd parsed.
607 rd_count = 1;
608 strcpy (lastname, newname);
609 state = ZS_ORIGIN_SET;
610 }
611 else
612 rd_count = 0;
613 return;
614 }
615 if (ZS_ORIGIN_CHANGED == state)
616 {
617 if (NULL != ego_name)
618 GNUNET_free (ego_name);
619 ego_name = GNUNET_strdup (origin);
620 if (ego_name[strlen (ego_name) - 1] == '.')
621 ego_name[strlen (ego_name) - 1] = '\0';
622 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
623 "Changing origin to %s\n", ego_name);
624 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name,
625 &origin_lookup_cb, NULL);
626 return;
627 }
628 printf ("Published %u records sets with total %u records\n",
629 published_sets, published_records);
630 GNUNET_SCHEDULER_shutdown ();
631}
632
633
634static void
635identity_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego)
636{
637
638 el = NULL;
639 if (NULL == ego)
640 {
641 if (NULL != ego_name)
642 {
643 fprintf (stderr,
644 _ ("Ego `%s' not known to identity service\n"),
645 ego_name);
646
647 }
648 GNUNET_SCHEDULER_shutdown ();
649 ret = -1;
650 return;
651 }
652 zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
653 sprintf (origin, "%s.", ego_name);
654 state = ZS_ORIGIN_SET;
655 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
656}
657
658
659static void
660run (void *cls,
661 char *const *args,
662 const char *cfgfile,
663 const struct GNUNET_CONFIGURATION_Handle *_cfg)
664{
665 cfg = _cfg;
666 ns = GNUNET_NAMESTORE_connect (cfg);
667 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, (void *) cfg);
668 if (NULL == ns)
669 {
670 fprintf (stderr,
671 _ ("Failed to connect to NAMESTORE\n"));
672 return;
673 }
674 id = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
675 if (NULL == id)
676 {
677 fprintf (stderr,
678 _ ("Failed to connect to IDENTITY\n"));
679 return;
680 }
681 if (NULL != ego_name)
682 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name, &identity_cb, (void *) cfg);
683 else
684 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
685 state = ZS_READY;
686}
687
688
689/**
690 * The main function for gnunet-namestore-dbtool.
691 *
692 * @param argc number of arguments from the command line
693 * @param argv command line arguments
694 * @return 0 ok, 1 on error
695 */
696int
697main (int argc, char *const *argv)
698{
699 struct GNUNET_GETOPT_CommandLineOption options[] = {
700 GNUNET_GETOPT_option_string ('z',
701 "zone",
702 "EGO",
703 gettext_noop (
704 "name of the ego controlling the zone"),
705 &ego_name),
706 GNUNET_GETOPT_OPTION_END
707 };
708 int lret;
709
710 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
711 return 2;
712
713 GNUNET_log_setup ("gnunet-namestore-dbtool", "WARNING", NULL);
714 if (GNUNET_OK !=
715 (lret = GNUNET_PROGRAM_run (argc,
716 argv,
717 "gnunet-namestore-zonefile",
718 _ (
719 "GNUnet namestore database manipulation tool"),
720 options,
721 &run,
722 NULL)))
723 {
724 GNUNET_free_nz ((void *) argv);
725 return lret;
726 }
727 GNUNET_free_nz ((void *) argv);
728 return ret;
729}