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.c763
1 files changed, 763 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..dfd438e94
--- /dev/null
+++ b/src/cli/namestore/gnunet-namestore-zonefile.c
@@ -0,0 +1,763 @@
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
187tx_end (void *cls, enum GNUNET_ErrorCode ec)
188{
189 ns_qe = NULL;
190 if (GNUNET_EC_NONE != ec)
191 {
192 fprintf (stderr,
193 _ ("Ego `%s' not known to identity service\n"),
194 ego_name);
195 GNUNET_SCHEDULER_shutdown ();
196 ret = -1;
197 }
198 GNUNET_SCHEDULER_shutdown ();
199}
200
201static void
202parse (void *cls);
203
204static char*
205trim (char *line)
206{
207 char *ltrimmed = line;
208 int ltrimmed_len;
209 int quoted = 0;
210
211 // Trim all whitespace to the left
212 while (*ltrimmed == ' ')
213 ltrimmed++;
214 ltrimmed_len = strlen (ltrimmed);
215 // Find the first occurence of an unqoted ';', which is our comment
216 for (int i = 0; i < ltrimmed_len; i++)
217 {
218 if (ltrimmed[i] == '"')
219 quoted = ! quoted;
220 if ((ltrimmed[i] != ';') || quoted)
221 continue;
222 ltrimmed[i] = '\0';
223 }
224 ltrimmed_len = strlen (ltrimmed);
225 // Remove trailing whitespace
226 for (int i = ltrimmed_len; i > 0; i--)
227 {
228 if (ltrimmed[i - 1] != ' ')
229 break;
230 ltrimmed[i - 1] = '\0';
231 }
232 ltrimmed_len = strlen (ltrimmed);
233 if (ltrimmed[ltrimmed_len - 1] == '\n')
234 ltrimmed[ltrimmed_len - 1] = ' ';
235 return ltrimmed;
236}
237
238static char*
239next_token (char *token)
240{
241 char *next = token;
242 while (*next == ' ')
243 next++;
244 return next;
245}
246
247static int
248parse_ttl (char *token, struct GNUNET_TIME_Relative *ttl)
249{
250 char *next;
251 unsigned int ttl_tmp;
252
253 next = strchr (token, ';');
254 if (NULL != next)
255 next[0] = '\0';
256 next = strchr (token, ' ');
257 if (NULL != next)
258 next[0] = '\0';
259 if (1 != sscanf (token, "%u", &ttl_tmp))
260 {
261 fprintf (stderr, "Unable to parse TTL `%s'\n", token);
262 return GNUNET_SYSERR;
263 }
264 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "TTL is: %u\n", ttl_tmp);
265 ttl->rel_value_us = ttl_tmp * 1000 * 1000;
266 return GNUNET_OK;
267}
268
269static int
270parse_origin (char *token, char *origin)
271{
272 char *next;
273 next = strchr (token, ';');
274 if (NULL != next)
275 next[0] = '\0';
276 next = strchr (token, ' ');
277 if (NULL != next)
278 next[0] = '\0';
279 strcpy (origin, token);
280 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Origin is: %s\n", origin);
281 return GNUNET_OK;
282}
283
284static void
285origin_create_cb (void *cls, const struct GNUNET_CRYPTO_PrivateKey *pk,
286 enum GNUNET_ErrorCode ec)
287{
288 id_op = NULL;
289 if (GNUNET_EC_NONE != ec)
290 {
291 fprintf (stderr, "Error: %s\n", GNUNET_ErrorCode_get_hint (ec));
292 ret = 1;
293 GNUNET_SCHEDULER_shutdown ();
294 return;
295 }
296 state = ZS_ORIGIN_SET;
297 zone_pkey = *pk;
298 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
299}
300
301static void
302origin_lookup_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego)
303{
304
305 el = NULL;
306
307 if (NULL == ego)
308 {
309 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
310 "$ORIGIN %s does not exist, creating...\n", ego_name);
311 id_op = GNUNET_IDENTITY_create (id, ego_name, NULL,
312 GNUNET_PUBLIC_KEY_TYPE_ECDSA, // FIXME make configurable
313 origin_create_cb,
314 NULL);
315 return;
316 }
317 state = ZS_ORIGIN_SET;
318 zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
319 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
320}
321
322static void
323add_continuation (void *cls, enum GNUNET_ErrorCode ec)
324{
325 ns_qe = NULL;
326 if (GNUNET_EC_NONE != ec)
327 {
328 fprintf (stderr,
329 _ ("Failed to store records...\n"));
330 GNUNET_SCHEDULER_shutdown ();
331 ret = -1;
332 }
333 if (ZS_ORIGIN_CHANGED == state)
334 {
335 if (NULL != ego_name)
336 GNUNET_free (ego_name);
337 ego_name = GNUNET_strdup (origin);
338 if (ego_name[strlen (ego_name) - 1] == '.')
339 ego_name[strlen (ego_name) - 1] = '\0';
340 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
341 "Changing origin to %s\n", ego_name);
342 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name,
343 &origin_lookup_cb, NULL);
344 return;
345 }
346 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
347}
348
349
350
351/**
352 * Main function that will be run.
353 *
354 * TODO:
355 * - We must assume that names are not repeated later in the zonefile because
356 * our _store APIs are replacing. No sure if that is common in zonefiles.
357 * - We must only actually store a record set when the name to store changes or
358 * the end of the file is reached.
359 * that way we can group them and add (see above).
360 * - We need to hope our string formats are compatible, but seems ok.
361 *
362 * @param cls closure
363 * @param args remaining command-line arguments
364 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
365 * @param cfg configuration
366 */
367static void
368parse (void *cls)
369{
370 char buf[MAX_ZONEFILE_LINE_LEN];
371 char payload[MAX_ZONEFILE_RECORD_DATA_LEN];
372 char *next;
373 char *token;
374 char *payload_pos;
375 static char lastname[GNUNET_DNSPARSER_MAX_LABEL_LENGTH];
376 char newname[GNUNET_DNSPARSER_MAX_LABEL_LENGTH];
377 void *data;
378 size_t data_size;
379 int ttl_line = 0;
380 int type;
381 int bracket_unclosed = 0;
382 int quoted = 0;
383
384 parse_task = NULL;
385 /* use filename provided as 1st argument (stdin by default) */
386 int ln = 0;
387 while ((res = fgets (buf, sizeof(buf), stdin))) /* read each line of input */
388 {
389 ln++;
390 ttl_line = 0;
391 token = trim (buf);
392 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
393 "Trimmed line (bracket %s): `%s'\n",
394 (bracket_unclosed > 0) ? "unclosed" : "closed",
395 token);
396 if ((0 == strlen (token)) ||
397 ((1 == strlen (token)) && (' ' == *token)))
398 continue; // I guess we can safely ignore blank lines
399 if (bracket_unclosed == 0)
400 {
401 /* Payload is already parsed */
402 payload_pos = payload;
403 /* Find space */
404 next = strchr (token, ' ');
405 if (NULL == next)
406 {
407 fprintf (stderr, "Error at line %u: %s\n", ln, token);
408 ret = 1;
409 GNUNET_SCHEDULER_shutdown ();
410 return;
411 }
412 next[0] = '\0';
413 next++;
414 if (0 == (strcmp (token, "$ORIGIN")))
415 {
416 state = ZS_ORIGIN_CHANGED;
417 token = next_token (next);
418 }
419 else if (0 == (strcmp (token, "$TTL")))
420 {
421 ttl_line = 1;
422 token = next_token (next);
423 }
424 else
425 {
426 if (0 == strcmp (token, "IN")) // Inherit name from before
427 {
428 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
429 "Old name: %s\n", lastname);
430 strcpy (newname, lastname);
431 token[strlen (token)] = ' ';
432 }
433 else if (token[strlen (token) - 1] != '.') // no fqdn
434 {
435 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: %s\n", token);
436 if (GNUNET_DNSPARSER_MAX_LABEL_LENGTH < strlen (token))
437 {
438 fprintf (stderr,
439 _ ("Name `%s' is too long\n"),
440 token);
441 ret = 1;
442 GNUNET_SCHEDULER_shutdown ();
443 return;
444 }
445 strcpy (newname, token);
446 token = next_token (next);
447 }
448 else if (0 == strcmp (token, origin))
449 {
450 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: @\n");
451 strcpy (newname, "@");
452 token = next_token (next);
453 }
454 else
455 {
456 if (strlen (token) < strlen (origin))
457 {
458 fprintf (stderr, "Wrong origin: %s (expected %s)\n", token, origin);
459 break; // FIXME error?
460 }
461 if (0 != strcmp (token + (strlen (token) - strlen (origin)), origin))
462 {
463 fprintf (stderr, "Wrong origin: %s (expected %s)\n", token, origin);
464 break;
465 }
466 token[strlen (token) - strlen (origin) - 1] = '\0';
467 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New name: %s\n", token);
468 if (GNUNET_DNSPARSER_MAX_LABEL_LENGTH < strlen (token))
469 {
470 fprintf (stderr,
471 _ ("Name `%s' is too long\n"),
472 token);
473 ret = 1;
474 GNUNET_SCHEDULER_shutdown ();
475 return;
476 }
477 strcpy (newname, token);
478 token = next_token (next);
479 }
480 if (0 != strcmp (newname, lastname) &&
481 (0 < rd_count))
482 {
483 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
484 "Name changed %s->%s, storing record set of %u elements\n",
485 lastname, newname,
486 rd_count);
487 state = ZS_NAME_CHANGED;
488 }
489 else {
490 strcpy (lastname, newname);
491 }
492 }
493
494 if (ttl_line)
495 {
496 if (GNUNET_SYSERR == parse_ttl (token, &ttl))
497 {
498 fprintf (stderr, _ ("Failed to parse $TTL\n"));
499 ret = 1;
500 GNUNET_SCHEDULER_shutdown ();
501 return;
502 }
503 continue;
504 }
505 if (ZS_ORIGIN_CHANGED == state)
506 {
507 if (GNUNET_SYSERR == parse_origin (token, origin))
508 {
509 fprintf (stderr, _ ("Failed to parse $ORIGIN from %s\n"), token);
510 ret = 1;
511 GNUNET_SCHEDULER_shutdown ();
512 return;
513 }
514 break;
515 }
516 if (ZS_READY == state)
517 {
518 fprintf (stderr,
519 _ (
520 "You must provide $ORIGIN in your zonefile or via arguments (--zone)!\n"));
521 ret = 1;
522 GNUNET_SCHEDULER_shutdown ();
523 return;
524 }
525 // This is a record, let's go
526 if (MAX_RECORDS_PER_NAME == rd_count)
527 {
528 fprintf (stderr,
529 _ ("Only %u records per unique name supported.\n"),
530 MAX_RECORDS_PER_NAME);
531 ret = 1;
532 GNUNET_SCHEDULER_shutdown ();
533 return;
534 }
535 rd[rd_count].flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
536 rd[rd_count].expiration_time = ttl.rel_value_us;
537 next = strchr (token, ' ');
538 if (NULL == next)
539 {
540 fprintf (stderr, "Error, last token: %s\n", token);
541 ret = 1;
542 GNUNET_SCHEDULER_shutdown ();
543 break;
544 }
545 next[0] = '\0';
546 next++;
547 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "class is: %s\n", token);
548 while (*next == ' ')
549 next++;
550 token = next;
551 next = strchr (token, ' ');
552 if (NULL == next)
553 {
554 fprintf (stderr, "Error\n");
555 break;
556 }
557 next[0] = '\0';
558 next++;
559 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "type is: %s\n", token);
560 type = GNUNET_GNSRECORD_typename_to_number (token);
561 rd[rd_count].record_type = type;
562 while (*next == ' ')
563 next++;
564 token = next;
565 }
566 for (int i = 0; i < strlen (token); i++)
567 {
568 if (token[i] == '"')
569 quoted = ! quoted;
570 if ((token[i] == '(') && ! quoted)
571 bracket_unclosed++;
572 if ((token[i] == ')') && ! quoted)
573 bracket_unclosed--;
574 }
575 memcpy (payload_pos, token, strlen (token));
576 payload_pos += strlen (token);
577 if (bracket_unclosed > 0)
578 {
579 *payload_pos = ' ';
580 payload_pos++;
581 continue;
582 }
583 *payload_pos = '\0';
584 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "data is: %s\n\n", payload);
585 if (GNUNET_OK !=
586 GNUNET_GNSRECORD_string_to_value (type, payload,
587 &data,
588 &data_size))
589 {
590 fprintf (stderr,
591 _ ("Data `%s' invalid\n"),
592 payload);
593 ret = 1;
594 GNUNET_SCHEDULER_shutdown ();
595 return;
596 }
597 rd[rd_count].data = data;
598 rd[rd_count].data_size = data_size;
599 if (ZS_NAME_CHANGED == state)
600 break;
601 rd_count++;
602 }
603 if (rd_count > 0)
604 {
605 ns_qe = GNUNET_NAMESTORE_records_store (ns,
606 &zone_pkey,
607 lastname,
608 rd_count,
609 rd,
610 &add_continuation,
611 NULL);
612 published_sets++;
613 published_records += rd_count;
614 for (int i = 0; i < rd_count; i++)
615 {
616 data = (void*) rd[i].data;
617 GNUNET_free (data);
618 }
619 if (ZS_NAME_CHANGED == state)
620 {
621 rd[0] = rd[rd_count]; // recover last rd parsed.
622 rd_count = 1;
623 strcpy (lastname, newname);
624 state = ZS_ORIGIN_SET;
625 }
626 else
627 rd_count = 0;
628 return;
629 }
630 if (ZS_ORIGIN_CHANGED == state)
631 {
632 if (NULL != ego_name)
633 GNUNET_free (ego_name);
634 ego_name = GNUNET_strdup (origin);
635 if (ego_name[strlen (ego_name) - 1] == '.')
636 ego_name[strlen (ego_name) - 1] = '\0';
637 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
638 "Changing origin to %s\n", ego_name);
639 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name,
640 &origin_lookup_cb, NULL);
641 return;
642 }
643 printf ("Published %u records sets with total %u records\n",
644 published_sets, published_records);
645 ns_qe = GNUNET_NAMESTORE_transaction_commit (ns,
646 &tx_end,
647 NULL);
648}
649
650static void
651tx_start (void *cls, enum GNUNET_ErrorCode ec)
652{
653 ns_qe = NULL;
654 if (GNUNET_EC_NONE != ec)
655 {
656 fprintf (stderr,
657 _ ("Ego `%s' not known to identity service\n"),
658 ego_name);
659 GNUNET_SCHEDULER_shutdown ();
660 ret = -1;
661 return;
662 }
663 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
664}
665
666static void
667identity_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego)
668{
669
670 el = NULL;
671 if (NULL == ego)
672 {
673 if (NULL != ego_name)
674 {
675 fprintf (stderr,
676 _ ("Ego `%s' not known to identity service\n"),
677 ego_name);
678
679 }
680 GNUNET_SCHEDULER_shutdown ();
681 ret = -1;
682 return;
683 }
684 zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego);
685 sprintf (origin, "%s.", ego_name);
686 state = ZS_ORIGIN_SET;
687 ns_qe = GNUNET_NAMESTORE_transaction_begin (ns,
688 &tx_start,
689 NULL);
690}
691
692
693static void
694run (void *cls,
695 char *const *args,
696 const char *cfgfile,
697 const struct GNUNET_CONFIGURATION_Handle *_cfg)
698{
699 cfg = _cfg;
700 ns = GNUNET_NAMESTORE_connect (cfg);
701 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, (void *) cfg);
702 if (NULL == ns)
703 {
704 fprintf (stderr,
705 _ ("Failed to connect to NAMESTORE\n"));
706 return;
707 }
708 id = GNUNET_IDENTITY_connect (cfg, NULL, NULL);
709 if (NULL == id)
710 {
711 fprintf (stderr,
712 _ ("Failed to connect to IDENTITY\n"));
713 return;
714 }
715 if (NULL != ego_name)
716 el = GNUNET_IDENTITY_ego_lookup (cfg, ego_name, &identity_cb, (void *) cfg);
717 else
718 parse_task = GNUNET_SCHEDULER_add_now (&parse, NULL);
719 state = ZS_READY;
720}
721
722
723/**
724 * The main function for gnunet-namestore-dbtool.
725 *
726 * @param argc number of arguments from the command line
727 * @param argv command line arguments
728 * @return 0 ok, 1 on error
729 */
730int
731main (int argc, char *const *argv)
732{
733 struct GNUNET_GETOPT_CommandLineOption options[] = {
734 GNUNET_GETOPT_option_string ('z',
735 "zone",
736 "EGO",
737 gettext_noop (
738 "name of the ego controlling the zone"),
739 &ego_name),
740 GNUNET_GETOPT_OPTION_END
741 };
742 int lret;
743
744 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
745 return 2;
746
747 GNUNET_log_setup ("gnunet-namestore-dbtool", "WARNING", NULL);
748 if (GNUNET_OK !=
749 (lret = GNUNET_PROGRAM_run (argc,
750 argv,
751 "gnunet-namestore-zonefile",
752 _ (
753 "GNUnet namestore database manipulation tool"),
754 options,
755 &run,
756 NULL)))
757 {
758 GNUNET_free_nz ((void *) argv);
759 return lret;
760 }
761 GNUNET_free_nz ((void *) argv);
762 return ret;
763}