Commit 8357bf5c authored by Daniel Salzman's avatar Daniel Salzman

modules: remove rosedb module (hard to maintain, no known user)

parent 563a2f5a
......@@ -245,8 +245,6 @@ src/knot/modules/noudp/noudp.c
src/knot/modules/onlinesign/nsec_next.c
src/knot/modules/onlinesign/nsec_next.h
src/knot/modules/onlinesign/onlinesign.c
src/knot/modules/rosedb/rosedb.c
src/knot/modules/rosedb/rosedb_tool.c
src/knot/modules/rrl/functions.c
src/knot/modules/rrl/functions.h
src/knot/modules/rrl/rrl.c
......
......@@ -303,7 +303,6 @@ KNOT_MODULE([dnsproxy], "yes", "non-shareable")
KNOT_MODULE([dnstap], "no")
KNOT_MODULE([noudp], "yes")
KNOT_MODULE([onlinesign], "yes", "non-shareable")
KNOT_MODULE([rosedb], "no")
KNOT_MODULE([rrl], "yes")
KNOT_MODULE([stats], "yes")
KNOT_MODULE([synthrecord], "yes")
......
......@@ -540,7 +540,6 @@ include $(srcdir)/knot/modules/dnsproxy/Makefile.inc
include $(srcdir)/knot/modules/dnstap/Makefile.inc
include $(srcdir)/knot/modules/noudp/Makefile.inc
include $(srcdir)/knot/modules/onlinesign/Makefile.inc
include $(srcdir)/knot/modules/rosedb/Makefile.inc
include $(srcdir)/knot/modules/rrl/Makefile.inc
include $(srcdir)/knot/modules/stats/Makefile.inc
include $(srcdir)/knot/modules/synthrecord/Makefile.inc
......
knot_modules_rosedb_la_SOURCES = knot/modules/rosedb/rosedb.c
EXTRA_DIST += knot/modules/rosedb/rosedb.rst
rosedb_tool_SOURCES = knot/modules/rosedb/rosedb_tool.c
rosedb_tool_LDADD = libknotd.la libcontrib.la
rosedb_tool_LDFLAGS = $(AM_LDFLAGS) $(lmdb_LIBS)
rosedb_tool_CPPFLAGS = $(AM_CPPFLAGS) -DKNOTD_MOD_STATIC
rosedb_tool_CFLAGS = $(AM_CFLAGS) $(lmdb_CFLAGS)
if STATIC_MODULE_rosedb
libknotd_la_SOURCES += $(knot_modules_rosedb_la_SOURCES)
if HAVE_UTILS
bin_PROGRAMS += rosedb_tool
endif
endif
if SHARED_MODULE_rosedb
knot_modules_rosedb_la_LDFLAGS = $(KNOTD_MOD_LDFLAGS)
knot_modules_rosedb_la_CPPFLAGS = $(KNOTD_MOD_CPPFLAGS) $(lmdb_CFLAGS)
knot_modules_rosedb_la_LIBADD = libcontrib.la
pkglib_LTLIBRARIES += knot/modules/rosedb.la
if HAVE_UTILS
bin_PROGRAMS += rosedb_tool
endif
endif
This diff is collapsed.
.. _mod-rosedb:
``rosedb`` – Static resource records
====================================
The module provides a mean to override responses for certain queries before
the record is searched in the available zones. The module comes with the
``rosedb_tool`` tool used to manipulate the database of static records.
For example, let's suppose we have a database of following records:
.. code-block:: none
myrecord.com. 3600 IN A 127.0.0.1
www.myrecord.com. 3600 IN A 127.0.0.2
ipv6.myrecord.com. 3600 IN AAAA ::1
And we query the nameserver with the following:
.. code-block:: console
$ kdig IN A myrecord.com
... returns NOERROR, 127.0.0.1
$ kdig IN A www.myrecord.com
... returns NOERROR, 127.0.0.2
$ kdig IN A stuff.myrecord.com
... returns NOERROR, 127.0.0.1
$ kdig IN AAAA myrecord.com
... returns NOERROR, NODATA
$ kdig IN AAAA ipv6.myrecord.com
... returns NOERROR, ::1
An entry in the database matches anything at the same or a lower domain
level, i.e. 'myrecord.com' matches 'a.a.myrecord.com' as well.
This can be utilized to create catch-all entries.
You can also add authority information for the entries, provided you create
SOA + NS records for a name, like so:
.. code-block:: none
myrecord.com. 3600 IN SOA master host 1 3600 60 3600 3600
myrecord.com. 3600 IN NS ns1.myrecord.com.
myrecord.com. 3600 IN NS ns2.myrecord.com.
ns1.myrecord.com. 3600 IN A 127.0.0.1
ns2.myrecord.com. 3600 IN A 127.0.0.2
In this case, the responses will:
1. Be authoritative (AA flag set)
2. Provide an authority section (SOA + NS)
3. Be NXDOMAIN if the name is found *(i.e. the 'IN AAAA myrecord.com' from
the example)*, but not the RR type *(this is to allow the synthesis of
negative responses)*
The SOA record applies only to the 'myrecord.com.', not to any other
record (not even those of its subdomains). From this point of view, all records
in the database are unrelated and not hierarchical. The idea is to provide
subtree isolation for each entry.
In addition, the module is able to log matching queries via remote syslog if
you specify a syslog address endpoint and an optional string code.
Example
-------
* Create the entries in the database:
.. code-block:: console
$ mkdir /tmp/static_rrdb
$ # No logging
$ rosedb_tool /tmp/static_rrdb add myrecord.com. A 3600 "127.0.0.1" "-" "-"
$ # Logging as 'www_query' to Syslog at 10.0.0.1
$ rosedb_tool /tmp/static_rrdb add www.myrecord.com. A 3600 "127.0.0.1" \
"www_query" "10.0.0.1"
$ # Logging as 'ipv6_query' to Syslog at 10.0.0.1
$ rosedb_tool /tmp/static_rrdb add ipv6.myrecord.com. AAAA 3600 "::1" \
"ipv6_query" "10.0.0.1"
$ # Verify settings
$ rosedb_tool /tmp/static_rrdb list
www.myrecord.com. A RDATA=10B www_query 10.0.0.1
ipv6.myrecord.com. AAAA RDATA=22B ipv6_query 10.0.0.1
myrecord.com. A RDATA=10B - -
.. NOTE::
The database may be modified later on while the server is running.
* Configure the query module::
mod-rosedb:
- id: default
dbdir: /tmp/static_rrdb
template:
- id: default
global-module: mod-rosedb/default
The module accepts just one parameter – the path to the directory where
the database will be stored.
* Start the server:
.. code-block:: console
$ knotd -c knot.conf
* Verify the running instance:
.. code-block:: console
$ kdig @127.0.0.1#6667 A myrecord.com
Module reference
----------------
::
mod-rosedb:
- id: STR
dbdir: STR
.. _mod-rosedb_id:
id
..
A module identifier.
.. _mod-rosedb_dbdir:
dbdir
.....
A path to the directory where the database is stored.
*Required*
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include "contrib/getline.h"
#include "contrib/string.h"
#include "zscanner/scanner.h"
#include "knot/modules/rosedb/rosedb.c"
static int rosedb_add(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
static int rosedb_del(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
static int rosedb_get(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
static int rosedb_list(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
static int rosedb_import(struct cache *cache, MDB_txn *txn, int argc, char *argv[]);
struct tool_action {
const char *name;
int (*func)(struct cache *, MDB_txn *, int, char *[]);
int min_args;
const char *info;
};
#define TOOL_ACTION_MAXARG 7
#define TOOL_ACTION_COUNT 5
static struct tool_action TOOL_ACTION[TOOL_ACTION_COUNT] = {
{ "add", rosedb_add, 6, "<zone> <rrtype> <ttl> <rdata> <threat_code> <syslog_ip>" },
{ "del", rosedb_del, 1, "<zone> [rrtype]" },
{ "get", rosedb_get, 1, "<zone> [rrtype]" },
{ "import", rosedb_import, 1, "<file>" },
{ "list", rosedb_list, 0, "" }
};
static void help(FILE *stream)
{
fprintf(stream, "Usage: rosedb_tool <dbdir> <action> [params]\n");
fprintf(stream, "Actions:\n");
for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
struct tool_action *ta = &TOOL_ACTION[i];
fprintf(stream, "\t%s %s\n", ta->name, ta->info);
}
}
/* Global instance of RR scanner. */
static void parse_err(zs_scanner_t *s) {
fprintf(stderr, "failed to parse RDATA: %s\n", zs_strerror(s->error.code));
}
static zs_scanner_t *g_scanner = NULL;
int main(int argc, char *argv[])
{
static const struct option options[] = {
{ "version", no_argument, 0, 'V' },
{ "help", no_argument, 0, 'h' },
{ NULL }
};
int opt = 0;
int index = 0;
while ((opt = getopt_long(argc, argv, "Vh", options, &index)) != -1) {
switch (opt) {
case 'V':
printf("rosedb_tool (Knot DNS), version %s\n", PACKAGE_VERSION);
return EXIT_SUCCESS;
case 'h':
help(stdout);
return EXIT_SUCCESS;
default:
help(stderr);
return EXIT_FAILURE;
}
}
if (argc < 3) {
help(stderr);
return EXIT_FAILURE;
}
/* Get mandatory parameters. */
int ret = EXIT_SUCCESS;
char *dbdir = argv[1];
char *action = argv[2];
argv += 3;
argc -= 3;
g_scanner = malloc(sizeof(zs_scanner_t));
if (g_scanner == NULL) {
return EXIT_FAILURE;
}
if (zs_init(g_scanner, ".", KNOT_CLASS_IN, 0) != 0 ||
zs_set_processing(g_scanner, NULL, parse_err, NULL) != 0) {
zs_deinit(g_scanner);
free(g_scanner);
return EXIT_FAILURE;
}
/* Open cache for operations. */
struct cache *cache = cache_open(dbdir, 0, NULL);
if (cache == NULL) {
fprintf(stderr, "failed to open db '%s'\n", dbdir);
zs_deinit(g_scanner);
free(g_scanner);
return EXIT_FAILURE;
}
/* Execute action. */
bool found = false;
for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
struct tool_action *ta = &TOOL_ACTION[i];
if (strcmp(ta->name, action) == 0) {
/* Check param count. */
if (argc < ta->min_args) {
break;
}
/* Now set as found. */
found = true;
MDB_txn *txn = NULL;
int ret = mdb_txn_begin(cache->env, NULL, 0, &txn);
if (ret != MDB_SUCCESS) {
fprintf(stderr, "failed to open transaction, aborting\n");
break;
}
/* Execute operation handler. */
ret = ta->func(cache, txn, argc, argv);
if (ret != 0) {
fprintf(stderr, "'%s' failed, aborting transaction\n", action);
mdb_txn_abort(txn);
} else {
mdb_txn_commit(txn);
}
break;
}
}
cache_close(cache);
zs_deinit(g_scanner);
free(g_scanner);
if (!found) {
help(stderr);
return EXIT_FAILURE;
}
return ret;
}
static int parse_rdata(struct entry *entry, const char *owner, const char *rrtype, const char *rdata,
int ttl, knot_mm_t *mm)
{
knot_rdataset_init(&entry->data.rrs);
int ret = knot_rrtype_from_string(rrtype, &entry->data.type);
if (ret != KNOT_EOK) {
return ret;
}
/* Synthetize RR line */
char *rr_line = sprintf_alloc("%s %u IN %s %s\n", owner, ttl, rrtype, rdata);
if (zs_set_input_string(g_scanner, rr_line, strlen(rr_line)) != 0 ||
zs_parse_all(g_scanner) != 0) {
free(rr_line);
return KNOT_EPARSEFAIL;
}
free(rr_line);
/* Write parsed RDATA. */
knot_rdata_t rr[knot_rdata_array_size(g_scanner->r_data_length)];
knot_rdata_init(rr, g_scanner->r_data_length, g_scanner->r_data, ttl);
return knot_rdataset_add(&entry->data.rrs, rr, mm);
}
static int rosedb_add(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
{
printf("ADD %s\t%s\t%s\t%s\t%s\t%s\n", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
knot_dname_from_str(key, argv[0], sizeof(key));
knot_dname_to_lower(key);
struct entry entry;
int ret = parse_rdata(&entry, argv[0], argv[1], argv[3], atoi(argv[2]), cache->pool);
entry.threat_code = argv[4];
entry.syslog_ip = argv[5];
if (ret != 0) {
fprintf(stderr, "PARSE: %s\n", knot_strerror(ret));
return ret;
}
ret = cache_insert(txn, cache->dbi, key, &entry);
knot_rdataset_clear(&entry.data.rrs, cache->pool);
if (ret != 0) {
fprintf(stderr, "%s\n", mdb_strerror(ret));
}
return ret;
}
static int rosedb_del(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
{
printf("DEL %s\n", argv[0]);
knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
knot_dname_from_str(key, argv[0], sizeof(key));
knot_dname_to_lower(key);
int ret = cache_remove(txn, cache->dbi, key);
if (ret != 0) {
fprintf(stderr, "%s\n", mdb_strerror(ret));
}
return ret;
}
static int rosedb_get(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
{
knot_dname_t key[KNOT_DNAME_MAXLEN] = { '\0' };
knot_dname_from_str(key, argv[0], sizeof(key));
knot_dname_to_lower(key);
char type_str[16] = { '\0' };
struct iter it;
int ret = cache_query_fetch(txn, cache->dbi, &it, key);
while (ret == 0) {
struct entry entry;
cache_iter_val(&it, &entry);
knot_rdata_t *rd = knot_rdataset_at(&entry.data.rrs, 0);
knot_rrtype_to_string(entry.data.type, type_str, sizeof(type_str));
printf("%s\t%s\tTTL=%u\tRDLEN=%u\t%s\t%s\n", argv[0], type_str,
knot_rdata_ttl(rd), knot_rdata_rdlen(rd), entry.threat_code, entry.syslog_ip);
if (cache_iter_next(&it) != 0) {
break;
}
}
cache_iter_free(&it);
return ret;
}
static int rosedb_list(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
{
MDB_cursor *cursor = cursor_acquire(txn, cache->dbi);
MDB_val key, data;
char dname_str[KNOT_DNAME_MAXLEN] = {'\0'};
char type_str[16] = { '\0' };
int ret = mdb_cursor_get(cursor, &key, &data, MDB_FIRST);
while (ret == 0) {
struct entry entry;
unpack_entry(&data, &entry);
knot_dname_to_str(dname_str, key.mv_data, sizeof(dname_str));
knot_rrtype_to_string(entry.data.type, type_str, sizeof(type_str));
printf("%s\t%s RDATA=%zuB\t%s\t%s\n", dname_str, type_str,
knot_rdataset_size(&entry.data.rrs), entry.threat_code, entry.syslog_ip);
ret = mdb_cursor_get(cursor, &key, &data, MDB_NEXT);
}
cursor_release(cursor);
return KNOT_EOK;
}
static char *trim(char *line)
{
int last = strlen(line) - 1;
if (line[last] == '\n') {
line[last] = '\0';
last -= 1;
}
if (*line == '"') {
line[last] = '\0';
line += 1;
}
return line;
}
static int rosedb_import_line(struct cache *cache, MDB_txn *txn, char *line, const char *file, int lineno)
{
int ret = 0;
int argc = 0;
char *argv[TOOL_ACTION_MAXARG];
/* Tokenize */
char *saveptr = line;
char *token = NULL;
while ((token = strtok_r(saveptr, ";\t", &saveptr)) != NULL) {
token = trim(token);
if (*token == '\0') {
continue;
}
if (argc <= TOOL_ACTION_MAXARG) {
argv[argc] = token;
argc += 1;
} else {
fprintf(stderr, "%s#%d command '%s' - too much parameters (%d)\n",
file, lineno, line, argc);
return KNOT_EPARSEFAIL;
}
}
if (argc < 1) {
fprintf(stderr, "%s#%d command '%s' - command not recognized\n", file, lineno, line);
return KNOT_EOK; /* Ignore NOOP */
}
/* Execute action. */
bool found = false;
for (unsigned i = 0; i < TOOL_ACTION_COUNT; ++i) {
struct tool_action *ta = &TOOL_ACTION[i];
if (strcmp(ta->name, argv[0]) == 0) {
if (argc < ta->min_args) {
help(stderr);
return EXIT_FAILURE;
}
found = true;
ret = ta->func(cache, txn, argc - 1, argv + 1);
break;
}
}
if (!found) {
fprintf(stderr, "%s#%d command '%s' - command not recognized\n", file, lineno, line);
return KNOT_EPARSEFAIL;
}
return ret;
}
static int rosedb_import(struct cache *cache, MDB_txn *txn, int argc, char *argv[])
{
printf("IMPORT %s\n", argv[0]);
int ret = 0;
char *line = NULL;
int lineno = 0;
size_t line_len = 0;
FILE *fp = fopen(argv[0], "r");
if (fp == NULL) {
return KNOT_ENOENT;
}
while (knot_getline(&line, &line_len, fp) != -1) {
lineno += 1;
ret = rosedb_import_line(cache, txn, line, argv[0], lineno);
if (ret != 0) {
break;
}
}
free(line);
fclose(fp);
return ret;
}
#!/usr/bin/env python3
''' Check 'rosedb' query module functionality. '''
import os.path
from dnstest.test import Test
from dnstest.utils import *
from dnstest.module import ModRosedb
t = Test()
ModRosedb.check()
# Initialize server configuration.
zone = t.zone("example.com.")
knot = t.server("knot")
t.link(zone, knot)
# Attach rosedb.
module = ModRosedb(os.path.join(knot.dir, "rosedb"))
knot.add_module(None, module)
t.start()
# Check before rosedb applied.
resp = knot.dig("mail.example.com", "A")
resp.check(rcode="NOERROR", rdata="192.0.2.3", ttl=3600, flags="AA")
# Set rosedb records.
module.add_record("mail.example.com", "A", "1000", "127.0.0.1")
module.add_record("mail6.example.com", "AAAA", "1000", "::1")
knot.reload()
# Check if zone record is overridden with rosedb.
resp = knot.dig("mail.example.com", "A")
resp.check(rcode="NOERROR", rdata="127.0.0.1", ttl=1000, noflags="AA")
# Check for subdomain match.
resp = knot.dig("sub.sub.mail.example.com", "A")
resp.check(rcode="NOERROR", rdata="127.0.0.1", ttl=1000, noflags="AA")
# Check for new record.
resp = knot.dig("mail6.example.com", "AAAA")
resp.check(rcode="NOERROR", rdata="::1", ttl=1000, noflags="AA")
# Check for new record with bad type (NODATA).
resp = knot.dig("mail6.example.com", "A")
resp.check(rcode="NOERROR", noflags="AA")
compare(resp.count(), 0, "A count")
# Add authority information.
module.add_record("example.net", "SOA", "1", "ns1 host 1 3600 60 3600 3600")
module.add_record("example.net", "NS", "2", "ns1.example.net")
module.add_record("ns1.example.net", "A", "3", "127.0.0.2")
knot.reload()
# Check for authoritative answer.
resp = knot.dig("example.net", "NS")
resp.check(rcode="NOERROR", rdata="ns1.example.net.", ttl=2, flags="AA")
resp.check_count(1, rtype="SOA", section="authority")
# Check for NXDOMAIN.
resp = knot.dig("example.net", "MX")
resp.check(rcode="NXDOMAIN", flags="AA")
resp.check_count(1, rtype="SOA", section="authority")
t.end()
......@@ -211,38 +211,6 @@ class ModOnlineSign(KnotModule):
return conf
class ModRosedb(KnotModule):
'''Rosedb module'''
mod_name = "rosedb"
def __init__(self, dbdir):
super().__init__()
self.dbdir = dbdir
def get_conf(self, conf=None):
if not conf:
conf = dnstest.config.KnotConf()
conf.begin(self.conf_name)
conf.id_item("id", self.conf_id)
conf.item_str("dbdir", "%s" % (self.dbdir))
conf.end()
return conf
def add_record(self, owner, rtype, ttl, rdata, code="-", target="-"):
prepare_dir(self.dbdir)
try:
check_call([params.rosedb_tool, self.dbdir, 'add', owner, rtype,
ttl, rdata, code, target],
stdout=open(os.path.join(params.out_dir, "rosedb-tool.out"), mode="a"),
stderr=open(os.path.join(params.out_dir, "rosedb-tool.err"), mode="a"))
except:
set_err("ROSEDB_TOOL")
detail_log("!Failed to add a record into rosedb '%s'" % self.dbdir)
detail_log(SEP)
class ModStats(KnotModule):
'''Stats module'''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment