summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2013-02-18 12:38:54 +0100
committerOndřej Surý <ondrej@sury.org>2013-02-18 12:38:54 +0100
commit57f6772329cadf9c491cad62096138b85accd8b7 (patch)
tree5c0c1ab917a45ddb992d58758494a8ee844a2051
parent9fd746d685910496beff321536ed55ed857740e7 (diff)
parent2729227efaac366293e70770b6e82e7e775a2605 (diff)
downloadknot-57f6772329cadf9c491cad62096138b85accd8b7.tar.gz
Merge tag 'upstream/1.2_rc2' into debian-sid
Upstream version 1.2~rc2
-rw-r--r--AUTHORS1
-rw-r--r--CodingStyle2
-rw-r--r--KNOWN_ISSUES5
-rw-r--r--Knot.files2
-rw-r--r--RELNOTES24
-rw-r--r--astylerc12
-rwxr-xr-xconfigure22
-rw-r--r--configure.ac4
-rw-r--r--doc/configuration.texi64
-rw-r--r--doc/reference.texi88
-rw-r--r--samples/knot.full.conf43
-rw-r--r--src/Makefile.am8
-rw-r--r--src/Makefile.in61
-rw-r--r--src/common/base32.c539
-rw-r--r--src/common/base32.h121
-rw-r--r--src/common/base32hex.c893
-rw-r--r--src/common/base32hex.h181
-rw-r--r--src/common/base64.c797
-rw-r--r--src/common/base64.h174
-rw-r--r--src/common/errcode.c1
-rw-r--r--src/common/errcode.h3
-rw-r--r--src/common/evqueue.c81
-rw-r--r--src/common/evsched.c26
-rw-r--r--src/common/evsched.h2
-rw-r--r--src/common/fdset.c80
-rw-r--r--src/common/fdset.h17
-rw-r--r--src/common/general-tree.c1
-rw-r--r--src/common/mempattern.c39
-rw-r--r--src/common/mempattern.h10
-rw-r--r--src/config.h.in6
-rw-r--r--src/knot.conf.5180
-rw-r--r--src/knot/conf/cf-lex.l10
-rw-r--r--src/knot/conf/cf-parse.y77
-rw-r--r--src/knot/conf/conf.c83
-rw-r--r--src/knot/conf/conf.h25
-rw-r--r--src/knot/ctl/knotc_main.c1288
-rw-r--r--src/knot/ctl/process.c61
-rw-r--r--src/knot/ctl/process.h33
-rw-r--r--src/knot/ctl/remote.c704
-rw-r--r--src/knot/ctl/remote.h204
-rw-r--r--src/knot/main.c54
-rw-r--r--src/knot/server/journal.c15
-rw-r--r--src/knot/server/journal.h17
-rw-r--r--src/knot/server/notify.c2
-rw-r--r--src/knot/server/server.c32
-rw-r--r--src/knot/server/server.h9
-rw-r--r--src/knot/server/socket.c2
-rw-r--r--src/knot/server/socket.h1
-rw-r--r--src/knot/server/tcp-handler.c34
-rw-r--r--src/knot/server/tcp-handler.h11
-rw-r--r--src/knot/server/udp-handler.c11
-rw-r--r--src/knot/server/xfr-handler.c240
-rw-r--r--src/knot/server/xfr-handler.h10
-rw-r--r--src/knot/server/zones.c984
-rw-r--r--src/knot/server/zones.h50
-rw-r--r--src/knot/zone/semantic-check.c12
-rw-r--r--src/knot/zone/zone-dump-text.c34
-rw-r--r--src/knot/zone/zone-dump.c8
-rw-r--r--src/knot/zone/zone-load.c2
-rw-r--r--src/knotc.837
-rw-r--r--src/knotd.82
-rw-r--r--src/libknot/consts.h3
-rw-r--r--src/libknot/dname.c41
-rw-r--r--src/libknot/dname.h17
-rw-r--r--src/libknot/hash/cuckoo-hash-table.c9
-rw-r--r--src/libknot/nameserver/name-server.c395
-rw-r--r--src/libknot/nameserver/name-server.h32
-rw-r--r--src/libknot/packet/packet.c41
-rw-r--r--src/libknot/packet/packet.h4
-rw-r--r--src/libknot/packet/query.h2
-rw-r--r--src/libknot/packet/response.c37
-rw-r--r--src/libknot/packet/response.h3
-rw-r--r--src/libknot/rdata.c56
-rw-r--r--src/libknot/rdata.h5
-rw-r--r--src/libknot/rrset.c17
-rw-r--r--src/libknot/rrset.h6
-rw-r--r--src/libknot/tsig-op.c27
-rw-r--r--src/libknot/updates/changesets.c356
-rw-r--r--src/libknot/updates/changesets.h59
-rw-r--r--src/libknot/updates/ddns.c1916
-rw-r--r--src/libknot/updates/ddns.h23
-rw-r--r--src/libknot/updates/xfr-in.c961
-rw-r--r--src/libknot/updates/xfr-in.h20
-rw-r--r--src/libknot/util/debug.h10
-rw-r--r--src/libknot/util/descriptor.c25
-rw-r--r--src/libknot/util/descriptor.h37
-rw-r--r--src/libknot/zone/zone-contents.c169
-rw-r--r--src/libknot/zone/zone-diff.c124
-rw-r--r--src/tests/common/base32hex_tests.c166
-rw-r--r--src/tests/common/base32hex_tests.h25
-rw-r--r--src/tests/common/base64_tests.c154
-rw-r--r--src/tests/common/base64_tests.h25
-rw-r--r--src/tests/libknot/libknot/response_tests.c6
-rw-r--r--src/tests/libknot/realdata/libknot/response_tests_realdata.c2
-rw-r--r--src/tests/unittests_main.c22
-rw-r--r--src/zcompile/parser-util.c8
-rw-r--r--src/zcompile/zcompile.c2
-rw-r--r--tests/Makefile16
-rw-r--r--tests/chkjournal.c541
99 files changed, 9084 insertions, 3817 deletions
diff --git a/AUTHORS b/AUTHORS
index c032e47..82bb742 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,5 +1,6 @@
Ľuboš Slovák <lubos.slovak@nic.cz>
Marek Vavruša <marek.vavrusa@nic.cz>
Jan Kadlec <jan.kadlec@nic.cz>
+Daniel Salzman <daniel.salzman@nic.cz>
Ondřej Surý <ondrej.sury@nic.cz>
Ondřej Filip <ondrej.filip@nic.cz>
diff --git a/CodingStyle b/CodingStyle
index 986417c..13dc272 100644
--- a/CodingStyle
+++ b/CodingStyle
@@ -5,7 +5,7 @@ Coding style
* Max line width: 80 chars
* Pointer asterisk attached to the name of the variable
* Own structures/types: _t suffix (f.e. nameserver_t)
-* Header guard format: _KNOTD__HEADER_H_
+* Header guard format: _KNOTD_HEADER_H_
* Spaces around binary operators
* Space between keyword and bracket (f.e. "if (predicate)")
* No space between variable and typecast (f.e. "return (int)val;")
diff --git a/KNOWN_ISSUES b/KNOWN_ISSUES
index fff55a8..4db58a7 100644
--- a/KNOWN_ISSUES
+++ b/KNOWN_ISSUES
@@ -5,7 +5,10 @@ Here is a list of the most notable features that are not supported in the
current version of Knot.
* Other DNS classes than IN (CH, CS, HS)
-* Dynamic updates
+* Dynamic updates with signed zones have following limitations:
+ - Knot DNS doesn't automatically sign incoming RRs
+ - Removing of RRSIG without covered type specification
+ - Deleting a last RR also removes its RRSIG
Known bugs
==========
diff --git a/Knot.files b/Knot.files
index 903cf79..37dc52a 100644
--- a/Knot.files
+++ b/Knot.files
@@ -134,6 +134,8 @@ src/knot/main.c
src/knot/ctl/knotc_main.c
src/knot/ctl/process.c
src/knot/ctl/process.h
+src/knot/ctl/remote.c
+src/knot/ctl/remote.h
src/knot/other/debug.h
src/knot/stat/gatherer.c
src/knot/stat/gatherer.h
diff --git a/RELNOTES b/RELNOTES
index 3a9a1cc..449a849 100644
--- a/RELNOTES
+++ b/RELNOTES
@@ -1,13 +1,31 @@
+v1.2-rc2 - Feb 15, 2013
+-------------------
+
+Bugfixes:
+ * Fixed processing of some non-standard dnames.
+ * Correct checking of label length bounds in some cases.
+ * More compliant rcodes in case of DDNS/TSIG failures.
+ * Correct processing of malformed DDNS prereq section.
+
+v1.2-rc1 - Jan 4, 2013
+------------------
+
+Features:
+ * Dynamic updates, including forwarding (limited on signed zones)
+ * Updated remote control utility
+ * Configurable TCP timeouts
+ * LOC RR support
+
v1.1.3 - Dec 19, 2012
---------------------
-Bugfixes
+Bugfixes:
* Updated manpage.
v1.1.3-rc1 - Dec 6, 2012
------------------------
-Bugfixes
+Bugfixes:
* Fixed answering DS queries (RRSIGs not together with DS, AA bit
missing).
* Fixed setting ARCOUNT in some error responses with EDNS enabled.
@@ -18,7 +36,7 @@ Bugfixes
v1.1.2 - Nov 21, 2012
---------------------
-Bugfixes
+Bugfixes:
* Fixed debug message.
diff --git a/astylerc b/astylerc
new file mode 100644
index 0000000..95703db
--- /dev/null
+++ b/astylerc
@@ -0,0 +1,12 @@
+--style=1tbs
+--indent=tab=8
+--indent-preprocessor
+--pad-oper
+--pad-header
+--unpad-paren
+--add-brackets
+--convert-tabs
+--align-pointer=name
+--mode=c
+--lineend=linux
+
diff --git a/configure b/configure
index 7d6043f..2549253 100755
--- a/configure
+++ b/configure
@@ -1,6 +1,6 @@
#! /bin/sh
# Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for knot 1.1.3.
+# Generated by GNU Autoconf 2.69 for knot 1.2-rc2.
#
# Report bugs to <knot-dns@labs.nic.cz>.
#
@@ -590,8 +590,8 @@ MAKEFLAGS=
# Identity of this package.
PACKAGE_NAME='knot'
PACKAGE_TARNAME='knot'
-PACKAGE_VERSION='1.1.3'
-PACKAGE_STRING='knot 1.1.3'
+PACKAGE_VERSION='1.2-rc2'
+PACKAGE_STRING='knot 1.2-rc2'
PACKAGE_BUGREPORT='knot-dns@labs.nic.cz'
PACKAGE_URL=''
@@ -1319,7 +1319,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF
-\`configure' configures knot 1.1.3 to adapt to many kinds of systems.
+\`configure' configures knot 1.2-rc2 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1389,7 +1389,7 @@ fi
if test -n "$ac_init_help"; then
case $ac_init_help in
- short | recursive ) echo "Configuration of knot 1.1.3:";;
+ short | recursive ) echo "Configuration of knot 1.2-rc2:";;
esac
cat <<\_ACEOF
@@ -1509,7 +1509,7 @@ fi
test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then
cat <<\_ACEOF
-knot configure 1.1.3
+knot configure 1.2-rc2
generated by GNU Autoconf 2.69
Copyright (C) 2012 Free Software Foundation, Inc.
@@ -2062,7 +2062,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake.
-It was created by knot $as_me 1.1.3, which was
+It was created by knot $as_me 1.2-rc2, which was
generated by GNU Autoconf 2.69. Invocation command line was
$ $0 $@
@@ -2889,7 +2889,7 @@ fi
# Define the identity of the package.
PACKAGE='knot'
- VERSION='1.1.3'
+ VERSION='1.2-rc2'
cat >>confdefs.h <<_ACEOF
@@ -14068,7 +14068,7 @@ fi
done
-for ac_header in arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h
+for ac_header in arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h
do :
as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default"
@@ -15342,7 +15342,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their
# values after options handling.
ac_log="
-This file was extended by knot $as_me 1.1.3, which was
+This file was extended by knot $as_me 1.2-rc2, which was
generated by GNU Autoconf 2.69. Invocation command line was
CONFIG_FILES = $CONFIG_FILES
@@ -15408,7 +15408,7 @@ _ACEOF
cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
ac_cs_version="\\
-knot config.status 1.1.3
+knot config.status 1.2-rc2
configured by $0, generated by GNU Autoconf 2.69,
with options \\"\$ac_cs_config\\"
diff --git a/configure.ac b/configure.ac
index 260f379..7a723e5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,7 +1,7 @@
# -*- Autoconf -*-
AC_PREREQ([2.60])
-AC_INIT([knot], [1.1.3], [knot-dns@labs.nic.cz])
+AC_INIT([knot], [1.2-rc2], [knot-dns@labs.nic.cz])
AM_INIT_AUTOMAKE([gnu -Wall -Werror])
AC_CONFIG_SRCDIR([src/knot/main.c])
AC_CONFIG_HEADERS([src/config.h])
@@ -151,7 +151,7 @@ AC_SEARCH_LIBS([adler32], [z])
# Checks for header files.
AC_HEADER_RESOLV
-AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h])
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h inttypes.h limits.h malloc.h netdb.h netinet/in_systm.h netinet/in.h stdint.h stdlib.h string.h strings.h sys/socket.h sys/time.h sys/select.h sys/wait.h sys/stat.h cap-ng.h syslog.h unistd.h urcu.h ev.h pthread_np.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
diff --git a/doc/configuration.texi b/doc/configuration.texi
index 8b7a1ef..ae5a4a1 100644
--- a/doc/configuration.texi
+++ b/doc/configuration.texi
@@ -8,6 +8,8 @@ In this chapter we provide suggested configurations and explain the meaning of i
* Slave configuration::
* Master configuration::
* Configuring multiple interfaces::
+* Using DNS UPDATE::
+* Remote control interface::
* Enabling zone semantic checks::
* Creating IXFR differences from zone file changes::
@end menu
@@ -86,7 +88,9 @@ form you can define a zone by its name and zone file.
Knot DNS doesn't strictly differ between master and slave zones.
The only requirement is to have @code{xfr-in} @code{zones} statement set for given zone,
thus allowing both incoming XFR from that remote and using it as the
-zone master. Note that you need to explicitly allow incoming NOTIFY, otherwise
+zone master. If @code{update-in} is set and zone has a master,
+any accepted DNS UPDATE will be forwarded to master.
+Also note that you need to explicitly allow incoming NOTIFY, otherwise
the daemon would reject them.
Also, you can specify paths, relative to the storage directory.
See @ref{zones} and @ref{storage}.
@@ -95,12 +99,14 @@ If the zone file doesn't exist and @code{xfr-in} is set, it will be bootstrapped
@example
remotes @{
master @{ address 127.0.0.1@@53; @}
+ subnet1 @{ address 192.168.1.0/24; @}
@}
zones @{
example.com @{
file "example.com"; # relative to 'storage'
xfr-in master; # define 'master' for this zone
notify-in master; # also allow NOTIFY from 'master'
+ update-in subnet1; # accept UPDATE msgs from subnet1 and forward to master
@}
@}
@end example
@@ -148,6 +154,7 @@ zones @{
file "/var/zones/example.com";
xfr-out subnet1, subnet2; # allow outgoing transfers
notify-out slave;
+ update-in subnet1; # only allow DNS UPDATE from subnet1
@}
@}
@end example
@@ -182,6 +189,61 @@ interfaces @{
@}
@end example
+@node Using DNS UPDATE
+@section Using DNS UPDATE
+
+As noted in examples for master and slave, it is possible to accept DNS UPDATE messages.
+When the zone is configured as a slave and DNS UPDATE messages is accepted, server forwards the
+message to its primary master specified by @code{xfr-in} directive. When it receives
+the response from primary master, it forwards it back to the originator. This finishes the transaction.
+
+However, if the zone is configured as master (i.e. not having any @code{xfr-in} directive), it accepts
+such an UPDATE and processes it. As of 1.2, there are a few limitations with DNSSEC signed zones described below. Other than that, UPDATE of unsigned zones works as expected without any limitations.
+
+@itemize @bullet
+@item
+Knot DNS doesn't automatically sign incoming RR if the zone is signed.
+As a workaround, it accepts DNSSEC-related records. However, it may prove challenging
+to create such an UPDATE that it correctly adds/replaces signed RRs, so this
+feature should be treated as experimental until correct signing is implemented.
+
+@item
+As for the reason in a previous point, removing RRSIG with no specified rdata makes it impossible
+to determine whether the user meant a RRSIG for an NSEC3 record or other one.
+Since they are stored separately, it is advisable to always specify RRSIG along with at least the types it covers.
+
+@item
+Deleting a last RR also removes its RRSIG.
+
+@end itemize
+
+@node Remote control interface
+@section Remote control interface
+
+It is possible to control Knot DNS remotely. In order to enable remote control,
+you have to define control interface and an appropriate key. Use of key for
+authentication is strongly recommended. The control protocol is not encrypted,
+and susceptible to replay attack in a short timeframe until message digest expires,
+for that reason, it is recommended to enable remote control only on private networks
+or loopback.
+
+@example
+keys @{
+ knotc-key hmac-md5 "Wg==";
+@}
+remotes @{
+ ctl @{ address 127.0.0.1; key knotc-key; @}
+@}
+control @{
+ listen-on @{ address 127.0.0.1; @}
+ allow ctl;
+@}
+@end example
+
+The @code{allow} keyword accepts an ACL list, similar to @code{xfr-in} or @code{xfr-out},
+see that for syntax reference. The @code{listen-on} has syntax equal to an interface specification,
+but the default port for remote control protocol is @code{5553}.
+
@node Enabling zone semantic checks
@section Enabling zone semantic checks
You can turn on more detailed semantic
diff --git a/doc/reference.texi b/doc/reference.texi
index 9dffd00..f28608d 100644
--- a/doc/reference.texi
+++ b/doc/reference.texi
@@ -8,6 +8,7 @@ This reference describes every configuration option in Knot DNS server.
* keys::
* interfaces::
* remotes::
+* control::
* zones::
* log::
@end menu
@@ -38,6 +39,9 @@ else.
[ @code{pidfile} @code{"}@kbd{string}@code{";} ]
[ @code{workers} @kbd{integer}@code{;} ]
[ @code{user} @kbd{string}[@code{.}@kbd{string}]@code{;} ]
+ [ @code{max-conn-idle} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ]
+ [ @code{max-conn-hs} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ]
+ [ @code{max-conn-reply} ( @kbd{integer} | @kbd{integer}(@code{s} | @code{m} | @code{h} | @code{d})@code{;} ) ]
@code{@}}
@end example
@@ -52,6 +56,9 @@ else.
* pidfile::
* workers::
* user::
+* max-conn-idle::
+* max-conn-hs::
+* max-conn-reply::
@end menu
@node identity
@@ -150,6 +157,27 @@ system @{
@}
@end example
+@node max-conn-idle
+@subsubsection max-conn-idle
+@vindex max-conn-idle
+
+Maximum idle time between requests on a TCP connection.
+This also limits receiving of a single query, each query must be received in this time limit.
+
+@node max-conn-hs
+@subsubsection max-conn-hs
+@vindex max-conn-hs
+
+Maximum time between newly accepted TCP connection and first query.
+This is useful to disconnect inactive connections faster, than connection
+that already made at least 1 meaningful query.
+
+@node max-conn-reply
+@subsubsection max-conn-reply
+@vindex max-conn-reply
+
+Maximum time to wait for a reply to an issued SOA query.
+
@node system Example
@subsection system Example
@@ -433,6 +461,57 @@ remotes @{
@end example
+@node control
+@section @code{control} Statement
+
+The @code{control} statement specifies on which interface to listen for remote control commands.
+Caution: The control protocol is not encrypted,
+and susceptible to replay attack in a short timeframe until message digest expires,
+for that reason, it is recommended to enable remote control only on private networks
+or loopback.
+
+@menu
+* control Syntax::
+* control Statement Definition and Grammar::
+* control Examples::
+@end menu
+
+@node control Syntax
+@subsection Syntax
+
+@example
+@code{control} @code{@{}
+ [ @kbd{listen-on} @code{@{}
+ ( @code{address} @kbd{ip_address}[@@@kbd{port_number}] |
+ @code{@{} @code{address} @kbd{ip_address}@code{;} [ @code{port} @kbd{port_number}@code{;} ] @code{@}} )
+ @code{@}} ]
+ [ @code{allow} @kbd{remote_id} [, @kbd{remote_id}, @dots{} ]@code{;} ]
+@code{@}}
+@end example
+
+@node control Statement Definition and Grammar
+@subsection Statement Definition and Grammar
+
+Control interface @code{listen-on} definition is equal to @code{interface} definition in @ref{interfaces}.
+Default port for control interface is @code{5553}.
+
+@node control Examples
+@subsection Examples
+
+@example
+keys @{
+ knotc-key hmac-md5 "Wg==";
+@}
+remotes @{
+ ctl @{ address 127.0.0.1; key knotc-key; @}
+@}
+control @{
+ listen-on @{ address 127.0.0.1; @}
+ allow ctl;
+@}
+@end example
+
+
@node zones
@section @code{zones} Statement
@@ -457,6 +536,7 @@ The @code{zones} statement contains definition of zones served by Knot DNS.
[ @code{xfr-out} @kbd{remote_id} [, @kbd{remote_id}, @dots{} ]@code{;} ]
[ @code{notify-in} @kbd{remote_id} [, @kbd{remote_id}, @dots{} ]@code{;} ]
[ @code{notify-out} @kbd{remote_id} [, @kbd{remote_id}, @dots{} ]@code{;} ]
+ [ @code{update-in} @kbd{remote_id} [, @kbd{remote_id}, @dots{} ]@code{;} ]
[ @kbd{zone_options} ]
@code{@}}
@code{@}}
@@ -482,6 +562,7 @@ The @code{zones} statement contains definition of zones served by Knot DNS.
* xfr-out::
* notify-in::
* notify-out::
+* update-in::
* semantic-checks::
* ixfr-from-differences::
* disable-any::
@@ -533,6 +614,13 @@ Remotes are defined in @code{remotes} section of configuration file (@pxref{remo
@code{notify-out} defines to which remotes will your server send NOTIFYs about this particular zone.
+@node update-in
+@subsubsection update-in
+@vindex update-in
+
+In @code{update-in} statement user specifies which remotes will be permitted to perform a DNS UPDATE.
+Remotes are defined in @code{remotes} section of configuration file (@pxref{remotes}).
+
@node semantic-checks
@subsubsection semantic-checks
@vindex semantic-checks
diff --git a/samples/knot.full.conf b/samples/knot.full.conf
index 3943b21..8b4571e 100644
--- a/samples/knot.full.conf
+++ b/samples/knot.full.conf
@@ -7,8 +7,8 @@
# This is a comment.
#
-# There are 4 main sections of this config file:
-# system, zones, interfaces and log
+# There are 5 main sections of this config file:
+# system, zones, interfaces, control and log
#
# Section 'system' contains general options for the server
@@ -18,7 +18,7 @@ system {
identity "I have no mouth and must scream";
# Version of the server (see RFC 4892). Not used yet.
- version "1.1.0";
+ version "1.2;
# Server identifier
# Use string format "text"
@@ -41,6 +41,25 @@ system {
# User for running server
# May also specify user.group (e.g. knot.users)
# user knot.users;
+
+ # Maximum idle time between requests on a TCP connection
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 60s
+ max-conn-idle 60s;
+
+ # Maximum time between newly accepted TCP connection and first query
+ # This is useful to disconnect inactive connections faster
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 10s
+ max-conn-handshake 10s;
+
+ # Maximum time to wait for a reply to SOA query
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 10s
+ max-conn-reply 10s;
}
# Section 'keys' contains list of TSIG keys
@@ -113,6 +132,21 @@ remotes {
}
}
+# Section 'control' specifies on which interface to listen for RC commands
+control {
+
+ # Specifies interface, syntax is exactly the same as in 'interfaces' section
+ # Note: as of now, it is possible replay commands in a short time frame
+ # with MitM type attacks, so you should keep the interface on localnet.
+ # Default port is: 5553
+ listen-on { address 127.0.0.1@5553; }
+
+ # Specifies ACL list for remote control
+ # Same syntax as for ACLs in zones
+ # List of remotes delimited by comma
+ allow server0;
+}
+
# Section 'zones' contains information about zones to be served.
zones {
@@ -210,6 +244,9 @@ zones {
# List of servers to send NOTIFY to
notify-out server0, server1;
+
+ # List of servers to allow UPDATE queries
+ update-in server0;
}
}
diff --git a/src/Makefile.am b/src/Makefile.am
index 1bd26f5..4458615 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -56,6 +56,10 @@ knot_zcompile_SOURCES = \
unittests_SOURCES = \
tests/common/acl_tests.c \
tests/common/acl_tests.h \
+ tests/common/base32hex_tests.c \
+ tests/common/base32hex_tests.h \
+ tests/common/base64_tests.c \
+ tests/common/base64_tests.h \
tests/common/events_tests.c \
tests/common/events_tests.h \
tests/common/skiplist_tests.c \
@@ -219,13 +223,11 @@ libknots_la_SOURCES = \
common/mempattern.h \
common/mempattern.c \
common/lists.c \
- common/base32.c \
common/base64.c \
common/base64.h \
common/lists.h \
common/heap.h \
common/heap.c \
- common/base32.h \
common/print.c \
common/print.h \
common/skip-list.c \
@@ -282,6 +284,8 @@ libknotd_la_SOURCES = \
knot/conf/conf.h \
knot/ctl/process.c \
knot/ctl/process.h \
+ knot/ctl/remote.c \
+ knot/ctl/remote.h \
knot/server/dthreads.c \
knot/server/journal.c \
knot/server/socket.c \
diff --git a/src/Makefile.in b/src/Makefile.in
index 4db92fc..7c00c90 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -86,14 +86,14 @@ am_libknot_la_OBJECTS = utils.lo debug.lo descriptor.lo tolower.lo \
libknot_la_OBJECTS = $(am_libknot_la_OBJECTS)
libknotd_la_DEPENDENCIES = libknot.la libknots.la @LIBOBJS@
am_libknotd_la_OBJECTS = gatherer.lo stat.lo libknotd_la-cf-parse.lo \
- libknotd_la-cf-lex.lo conf.lo logconf.lo process.lo \
+ libknotd_la-cf-lex.lo conf.lo logconf.lo process.lo remote.lo \
dthreads.lo journal.lo socket.lo server.lo udp-handler.lo \
tcp-handler.lo xfr-handler.lo zones.lo notify.lo zone-load.lo \
semantic-check.lo zone-dump.lo zone-dump-text.lo
libknotd_la_OBJECTS = $(am_libknotd_la_OBJECTS)
libknots_la_DEPENDENCIES = @LIBOBJS@
am_libknots_la_OBJECTS = slab.lo tap.lo mempattern.lo lists.lo \
- base32.lo base64.lo heap.lo print.lo skip-list.lo base32hex.lo \
+ base64.lo heap.lo print.lo skip-list.lo base32hex.lo \
general-tree.lo evqueue.lo evsched.lo acl.lo sockaddr.lo \
ref.lo errors.lo errcode.lo dSFMT.lo prng.lo fdset.lo \
fdset_poll.lo fdset_kqueue.lo fdset_epoll.lo log.lo
@@ -114,7 +114,8 @@ knotc_DEPENDENCIES = libknotd.la libknot.la libknots.la @LIBOBJS@
am_knotd_OBJECTS = main.$(OBJEXT)
knotd_OBJECTS = $(am_knotd_OBJECTS)
knotd_DEPENDENCIES = libknotd.la libknot.la libknots.la @LIBOBJS@
-am_unittests_OBJECTS = acl_tests.$(OBJEXT) events_tests.$(OBJEXT) \
+am_unittests_OBJECTS = acl_tests.$(OBJEXT) base32hex_tests.$(OBJEXT) \
+ base64_tests.$(OBJEXT) events_tests.$(OBJEXT) \
skiplist_tests.$(OBJEXT) slab_tests.$(OBJEXT) \
fdset_tests.$(OBJEXT) conf_tests.$(OBJEXT) \
dthreads_tests.$(OBJEXT) journal_tests.$(OBJEXT) \
@@ -405,6 +406,10 @@ knot_zcompile_SOURCES = \
unittests_SOURCES = \
tests/common/acl_tests.c \
tests/common/acl_tests.h \
+ tests/common/base32hex_tests.c \
+ tests/common/base32hex_tests.h \
+ tests/common/base64_tests.c \
+ tests/common/base64_tests.h \
tests/common/events_tests.c \
tests/common/events_tests.h \
tests/common/skiplist_tests.c \
@@ -567,13 +572,11 @@ libknots_la_SOURCES = \
common/mempattern.h \
common/mempattern.c \
common/lists.c \
- common/base32.c \
common/base64.c \
common/base64.h \
common/lists.h \
common/heap.h \
common/heap.c \
- common/base32.h \
common/print.c \
common/print.h \
common/skip-list.c \
@@ -630,6 +633,8 @@ libknotd_la_SOURCES = \
knot/conf/conf.h \
knot/ctl/process.c \
knot/ctl/process.h \
+ knot/ctl/remote.c \
+ knot/ctl/remote.h \
knot/server/dthreads.c \
knot/server/journal.c \
knot/server/socket.c \
@@ -866,9 +871,10 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/acl_tests.Po@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32hex.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base32hex_tests.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/base64_tests.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/changesets.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/conf_tests.Po@am__quote@
@@ -934,6 +940,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rdata_tests.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rdata_tests_realdata.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ref.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/remote.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/response_tests_realdata.Po@am__quote@
@@ -1238,6 +1245,13 @@ process.lo: knot/ctl/process.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o process.lo `test -f 'knot/ctl/process.c' || echo '$(srcdir)/'`knot/ctl/process.c
+remote.lo: knot/ctl/remote.c
+@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT remote.lo -MD -MP -MF $(DEPDIR)/remote.Tpo -c -o remote.lo `test -f 'knot/ctl/remote.c' || echo '$(srcdir)/'`knot/ctl/remote.c
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/remote.Tpo $(DEPDIR)/remote.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='knot/ctl/remote.c' object='remote.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o remote.lo `test -f 'knot/ctl/remote.c' || echo '$(srcdir)/'`knot/ctl/remote.c
+
dthreads.lo: knot/server/dthreads.c
@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dthreads.lo -MD -MP -MF $(DEPDIR)/dthreads.Tpo -c -o dthreads.lo `test -f 'knot/server/dthreads.c' || echo '$(srcdir)/'`knot/server/dthreads.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/dthreads.Tpo $(DEPDIR)/dthreads.Plo
@@ -1357,13 +1371,6 @@ lists.lo: common/lists.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lists.lo `test -f 'common/lists.c' || echo '$(srcdir)/'`common/lists.c
-base32.lo: common/base32.c
-@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base32.lo -MD -MP -MF $(DEPDIR)/base32.Tpo -c -o base32.lo `test -f 'common/base32.c' || echo '$(srcdir)/'`common/base32.c
-@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base32.Tpo $(DEPDIR)/base32.Plo
-@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='common/base32.c' object='base32.lo' libtool=yes @AMDEPBACKSLASH@
-@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
-@am__fastdepCC_FALSE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base32.lo `test -f 'common/base32.c' || echo '$(srcdir)/'`common/base32.c
-
base64.lo: common/base64.c
@am__fastdepCC_TRUE@ $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base64.lo -MD -MP -MF $(DEPDIR)/base64.Tpo -c -o base64.lo `test -f 'common/base64.c' || echo '$(srcdir)/'`common/base64.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base64.Tpo $(DEPDIR)/base64.Plo
@@ -1616,6 +1623,34 @@ acl_tests.obj: tests/common/acl_tests.c
@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o acl_tests.obj `if test -f 'tests/common/acl_tests.c'; then $(CYGPATH_W) 'tests/common/acl_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/acl_tests.c'; fi`
+base32hex_tests.o: tests/common/base32hex_tests.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base32hex_tests.o -MD -MP -MF $(DEPDIR)/base32hex_tests.Tpo -c -o base32hex_tests.o `test -f 'tests/common/base32hex_tests.c' || echo '$(srcdir)/'`tests/common/base32hex_tests.c
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base32hex_tests.Tpo $(DEPDIR)/base32hex_tests.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/base32hex_tests.c' object='base32hex_tests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base32hex_tests.o `test -f 'tests/common/base32hex_tests.c' || echo '$(srcdir)/'`tests/common/base32hex_tests.c
+
+base32hex_tests.obj: tests/common/base32hex_tests.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base32hex_tests.obj -MD -MP -MF $(DEPDIR)/base32hex_tests.Tpo -c -o base32hex_tests.obj `if test -f 'tests/common/base32hex_tests.c'; then $(CYGPATH_W) 'tests/common/base32hex_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/base32hex_tests.c'; fi`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base32hex_tests.Tpo $(DEPDIR)/base32hex_tests.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/base32hex_tests.c' object='base32hex_tests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base32hex_tests.obj `if test -f 'tests/common/base32hex_tests.c'; then $(CYGPATH_W) 'tests/common/base32hex_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/base32hex_tests.c'; fi`
+
+base64_tests.o: tests/common/base64_tests.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base64_tests.o -MD -MP -MF $(DEPDIR)/base64_tests.Tpo -c -o base64_tests.o `test -f 'tests/common/base64_tests.c' || echo '$(srcdir)/'`tests/common/base64_tests.c
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base64_tests.Tpo $(DEPDIR)/base64_tests.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/base64_tests.c' object='base64_tests.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base64_tests.o `test -f 'tests/common/base64_tests.c' || echo '$(srcdir)/'`tests/common/base64_tests.c
+
+base64_tests.obj: tests/common/base64_tests.c
+@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT base64_tests.obj -MD -MP -MF $(DEPDIR)/base64_tests.Tpo -c -o base64_tests.obj `if test -f 'tests/common/base64_tests.c'; then $(CYGPATH_W) 'tests/common/base64_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/base64_tests.c'; fi`
+@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/base64_tests.Tpo $(DEPDIR)/base64_tests.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='tests/common/base64_tests.c' object='base64_tests.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o base64_tests.obj `if test -f 'tests/common/base64_tests.c'; then $(CYGPATH_W) 'tests/common/base64_tests.c'; else $(CYGPATH_W) '$(srcdir)/tests/common/base64_tests.c'; fi`
+
events_tests.o: tests/common/events_tests.c
@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT events_tests.o -MD -MP -MF $(DEPDIR)/events_tests.Tpo -c -o events_tests.o `test -f 'tests/common/events_tests.c' || echo '$(srcdir)/'`tests/common/events_tests.c
@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/events_tests.Tpo $(DEPDIR)/events_tests.Po
diff --git a/src/common/base32.c b/src/common/base32.c
deleted file mode 100644
index 43b86c1..0000000
--- a/src/common/base32.c
+++ /dev/null
@@ -1,539 +0,0 @@
-/* base32.c -- Encode binary data using printable characters.
- Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006, 2010 Free Software
- Foundation, Inc.
-
- 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 2, 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, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-
-/* Adapted from base64.{h,c} by Ondřej Surý. base64.{h,c} was written
- * by Simon Josefsson. Partially adapted from GNU MailUtils
- * (mailbox/filter_trans.c, as of 2004-11-28). Improved by review
- * from Paul Eggert, Bruno Haible, and Stepan Kasal.
- *
- * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>.
- *
- * Be careful with error checking. Here is how you would typically
- * use these functions:
- *
- * bool ok = base32_decode_alloc (in, inlen, &out, &outlen);
- * if (!ok)
- * FAIL: input was not valid base32
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN
- *
- * size_t outlen = base32_encode_alloc (in, inlen, &out);
- * if (out == NULL && outlen == 0 && inlen != 0)
- * FAIL: input too long
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN.
- *
- */
-
-/* Get prototype. */
-#include "base32.h"
-
-/* Get malloc. */
-#include <stdlib.h>
-
-/* Get UCHAR_MAX. */
-#include <limits.h>
-
-/* C89 compliant way to cast 'char' to 'unsigned char'. */
-static inline unsigned char to_uchar(char ch)
-{
- return ch;
-}
-
-/* Base32 encode IN array of size INLEN into OUT array of size OUTLEN.
- If OUTLEN is less than BASE32_LENGTH(INLEN), write as many bytes as
- possible. If OUTLEN is larger than BASE32_LENGTH(INLEN), also zero
- terminate the output buffer. */
-void base32_encode(const char *in, size_t inlen, char *out, size_t outlen)
-{
- static const char b32str[32] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
-
- while (inlen && outlen) {
- *out++ = b32str[(to_uchar(in[0]) >> 3) & 0x1f];
- if (!--outlen) {
- break;
- }
- *out++ = b32str[((to_uchar(in[0]) << 2)
- + (--inlen ? to_uchar(in[1]) >> 6 : 0))
- & 0x1f];
- if (!--outlen) {
- break;
- }
- *out++ =(inlen
- ? b32str[(to_uchar(in[1]) >> 1) & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[1]) << 4)
- + (--inlen ? to_uchar(in[2]) >> 4 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[2]) << 1)
- + (--inlen ? to_uchar(in[3]) >> 7 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[(to_uchar(in[3]) >> 2) & 0x1f]
- : '=');
- if (!--outlen)
- {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[3]) << 3)
- + (--inlen ? to_uchar(in[4]) >> 5 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = inlen ? b32str[to_uchar(in[4]) & 0x1f] : '=';
- if (!--outlen) {
- break;
- }
- if (inlen) {
- inlen--;
- }
- if (inlen) {
- in += 5;
- }
- }
-
- if (outlen) {
- *out = '\0';
- }
-}
-
-/* Allocate a buffer and store zero terminated base32 encoded data
- from array IN of size INLEN, returning BASE32_LENGTH(INLEN), i.e.,
- the length of the encoded data, excluding the terminating zero. On
- return, the OUT variable will hold a pointer to newly allocated
- memory that must be deallocated by the caller. If output string
- length would overflow, 0 is returned and OUT is set to NULL. If
- memory allocation failed, OUT is set to NULL, and the return value
- indicates length of the requested memory block, i.e.,
- BASE32_LENGTH(inlen) + 1. */
-size_t base32_encode_alloc(const char *in, size_t inlen, char **out)
-{
- size_t outlen = 1 + BASE32_LENGTH (inlen);
-
- /* Check for overflow in outlen computation.
- *
- * If there is no overflow, outlen >= inlen.
- *
- * If the operation (inlen + 2) overflows then it yields at most +1, so
- * outlen is 0.
- *
- * If the multiplication overflows, we lose at least half of the
- * correct value, so the result is < ((inlen + 2) / 3) * 2, which is
- * less than (inlen + 2) * 0.66667, which is less than inlen as soon as
- * (inlen > 4).
- */
- if (inlen > outlen)
- {
- *out = NULL;
- return 0;
- }
-
- *out = malloc(outlen);
- if (!*out) {
- return outlen;
- }
-
- base32_encode(in, inlen, *out, outlen);
-
- return outlen - 1;
-}
-
-/* With this approach this file works independent of the charset used
- (think EBCDIC). However, it does assume that the characters in the
- Base32 alphabet (A-Z2-7) are encoded in 0..255. POSIX
- 1003.1-2001 require that char and unsigned char are 8-bit
- quantities, though, taking care of that problem. But this may be a
- potential problem on non-POSIX C99 platforms.
-
- IBM C V6 for AIX mishandles "#define B32(x) ...'x'...", so use "_"
- as the formal parameter rather than "x". */
-#define B32(_) \
- ((_) == 'A' ? 0 \
- : (_) == 'B' ? 1 \
- : (_) == 'C' ? 2 \
- : (_) == 'D' ? 3 \
- : (_) == 'E' ? 4 \
- : (_) == 'F' ? 5 \
- : (_) == 'G' ? 6 \
- : (_) == 'H' ? 7 \
- : (_) == 'I' ? 8 \
- : (_) == 'J' ? 9 \
- : (_) == 'K' ? 10 \
- : (_) == 'L' ? 11 \
- : (_) == 'M' ? 12 \
- : (_) == 'N' ? 13 \
- : (_) == 'O' ? 14 \
- : (_) == 'P' ? 15 \
- : (_) == 'Q' ? 16 \
- : (_) == 'R' ? 17 \
- : (_) == 'S' ? 18 \
- : (_) == 'T' ? 19 \
- : (_) == 'U' ? 20 \
- : (_) == 'V' ? 21 \
- : (_) == 'W' ? 22 \
- : (_) == 'X' ? 23 \
- : (_) == 'Y' ? 24 \
- : (_) == 'Z' ? 25 \
- : (_) == '2' ? 26 \
- : (_) == '3' ? 27 \
- : (_) == '4' ? 28 \
- : (_) == '5' ? 29 \
- : (_) == '6' ? 30 \
- : (_) == '7' ? 31 \
- : -1)
-
-static const signed char b32[0x100] = {
- B32 (0), B32 (1), B32 (2), B32 (3),
- B32 (4), B32 (5), B32 (6), B32 (7),
- B32 (8), B32 (9), B32 (10), B32 (11),
- B32 (12), B32 (13), B32 (14), B32 (15),
- B32 (16), B32 (17), B32 (18), B32 (19),
- B32 (20), B32 (21), B32 (22), B32 (23),
- B32 (24), B32 (25), B32 (26), B32 (27),
- B32 (28), B32 (29), B32 (30), B32 (31),
- B32 (32), B32 (33), B32 (34), B32 (35),
- B32 (36), B32 (37), B32 (38), B32 (39),
- B32 (40), B32 (41), B32 (42), B32 (43),
- B32 (44), B32 (45), B32 (46), B32 (47),
- B32 (48), B32 (49), B32 (50), B32 (51),
- B32 (52), B32 (53), B32 (54), B32 (55),
- B32 (56), B32 (57), B32 (58), B32 (59),
- B32 (60), B32 (61), B32 (62), B32 (63),
- B32 (64), B32 (65), B32 (66), B32 (67),
- B32 (68), B32 (69), B32 (70), B32 (71),
- B32 (72), B32 (73), B32 (74), B32 (75),
- B32 (76), B32 (77), B32 (78), B32 (79),
- B32 (80), B32 (81), B32 (82), B32 (83),
- B32 (84), B32 (85), B32 (86), B32 (87),
- B32 (88), B32 (89), B32 (90), B32 (91),
- B32 (92), B32 (93), B32 (94), B32 (95),
- B32 (96), B32 (97), B32 (98), B32 (99),
- B32 (100), B32 (101), B32 (102), B32 (103),
- B32 (104), B32 (105), B32 (106), B32 (107),
- B32 (108), B32 (109), B32 (110), B32 (111),
- B32 (112), B32 (113), B32 (114), B32 (115),
- B32 (116), B32 (117), B32 (118), B32 (119),
- B32 (120), B32 (121), B32 (122), B32 (123),
- B32 (124), B32 (125), B32 (126), B32 (127),
- B32 (128), B32 (129), B32 (130), B32 (131),
- B32 (132), B32 (133), B32 (134), B32 (135),
- B32 (136), B32 (137), B32 (138), B32 (139),
- B32 (140), B32 (141), B32 (142), B32 (143),
- B32 (144), B32 (145), B32 (146), B32 (147),
- B32 (148), B32 (149), B32 (150), B32 (151),
- B32 (152), B32 (153), B32 (154), B32 (155),
- B32 (156), B32 (157), B32 (158), B32 (159),
- B32 (160), B32 (161), B32 (162), B32 (163),
- B32 (164), B32 (165), B32 (166), B32 (167),
- B32 (168), B32 (169), B32 (170), B32 (171),
- B32 (172), B32 (173), B32 (174), B32 (175),
- B32 (176), B32 (177), B32 (178), B32 (179),
- B32 (180), B32 (181), B32 (182), B32 (183),
- B32 (184), B32 (185), B32 (186), B32 (187),
- B32 (188), B32 (189), B32 (190), B32 (191),
- B32 (192), B32 (193), B32 (194), B32 (195),
- B32 (196), B32 (197), B32 (198), B32 (199),
- B32 (200), B32 (201), B32 (202), B32 (203),
- B32 (204), B32 (205), B32 (206), B32 (207),
- B32 (208), B32 (209), B32 (210), B32 (211),
- B32 (212), B32 (213), B32 (214), B32 (215),
- B32 (216), B32 (217), B32 (218), B32 (219),
- B32 (220), B32 (221), B32 (222), B32 (223),
- B32 (224), B32 (225), B32 (226), B32 (227),
- B32 (228), B32 (229), B32 (230), B32 (231),
- B32 (232), B32 (233), B32 (234), B32 (235),
- B32 (236), B32 (237), B32 (238), B32 (239),
- B32 (240), B32 (241), B32 (242), B32 (243),
- B32 (244), B32 (245), B32 (246), B32 (247),
- B32 (248), B32 (249), B32 (250), B32 (251),
- B32 (252), B32 (253), B32 (254), B32 (255)
-};
-
-#if UCHAR_MAX == 255
-#define uchar_in_range(c) true
-#else
-#define uchar_in_range(c) ((c) <= 255)
-#endif
-
-/* Return true if CH is a character from the Base32 alphabet, and
- false otherwise. Note that '=' is padding and not considered to be
- part of the alphabet. */
-bool isbase32(char ch)
-{
- return uchar_in_range(to_uchar(ch)) && 0 <= b32[to_uchar(ch)];
-}
-
-/* Decode base32 encoded input array IN of length INLEN to output
- array OUT that can hold *OUTLEN bytes. Return true if decoding was
- successful, i.e. if the input was valid base32 data, false
- otherwise. If *OUTLEN is too small, as many bytes as possible will
- be written to OUT. On return, *OUTLEN holds the length of decoded
- bytes in OUT. Note that as soon as any non-alphabet characters are
- encountered, decoding is stopped and false is returned. This means
- that, when applicable, you must remove any line terminators that is
- part of the data stream before calling this function. */
-bool base32_decode(const char *in, size_t inlen, char *out, size_t *outlen)
-{
- size_t outleft = *outlen;
-
- while (inlen >= 2) {
- if (!isbase32(in[0]) || !isbase32(in[1])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[0])] << 3)
- | (b32[to_uchar(in[1])] >> 2));
- outleft--;
- }
-
- if (inlen == 2) {
- break;
- }
-
- if (in[2] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[3] != '=') ||
- (in[4] != '=') ||
- (in[5] != '=') ||
- (in[6] != '=') ||
- (in[7] != '=')) {
- break;
- }
- } else {
- if (!isbase32(in[2]) || !isbase32(in[3])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[1])] << 6)
- | ((b32[to_uchar(in[2])] << 1) & 0x3E)
- | (b32[to_uchar(in[3])] >> 4));
- outleft--;
- }
-
- if (inlen == 4) {
- break;
- }
-
- if (in[4] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[5] != '=') ||
- (in[6] != '=') ||
- (in[7] != '=')) {
- break;
- }
- } else {
- if (!isbase32 (in[3]) || !isbase32(in[4])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[3])] << 4)
- | (b32[to_uchar(in[4])] >> 1));
- outleft--;
- }
-
- if (inlen == 5) {
- break;
- }
-
- if (in[5] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[6] != '=')
- || (in[7] != '=')) {
- break;
- }
- } else {
- if (!isbase32 (in[5])
- || !isbase32 (in[6])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[4])]
- << 7)
- | (b32[to_uchar(in[5])] << 2)
- | (b32[to_uchar(in[6])]
- >> 3));
- outleft--;
- }
-
- if (inlen == 7) {
- break;
- }
-
- if (in[7] == '=') {
- if (inlen != 8) {
- break;
- }
- } else {
- if (!isbase32 (in[7])) {
- break;
- }
-
- if (outleft) {
- *out++ =
- ((b32[to_uchar(in[6])]
- << 5) | (b32[
- to_uchar(in[7])]));
- outleft--;
- }
- }
- }
- }
- }
-
- in += 8;
- inlen -= 8;
- }
-
- *outlen -= outleft;
-
- if (inlen != 0) {
- return false;
- }
-
- return true;
-}
-
-/* Allocate an output buffer in *OUT, and decode the base32 encoded
- data stored in IN of size INLEN to the *OUT buffer. On return, the
- size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL,
- if the caller is not interested in the decoded length. *OUT may be
- NULL to indicate an out of memory error, in which case *OUTLEN
- contains the size of the memory block needed. The function returns
- true on successful decoding and memory allocation errors. (Use the
- *OUT and *OUTLEN parameters to differentiate between successful
- decoding and memory error.) The function returns false if the
- input was invalid, in which case *OUT is NULL and *OUTLEN is
- undefined. */
-bool base32_decode_alloc(const char *in, size_t inlen, char **out,
- size_t *outlen)
-{
- /* This may allocate a few bytes too much, depending on input,
- but it's not worth the extra CPU time to compute the exact amount.
- The exact amount is 5 * inlen / 8, minus 1 if the input ends
- with "=" and minus another 1 if the input ends with "==", etc.
- Dividing before multiplying avoids the possibility of overflow. */
- size_t needlen = 5 * (inlen / 8) + 4;
-
- *out = malloc(needlen);
- if (!*out) {
- return true;
- }
-
- if (!base32_decode(in, inlen, *out, &needlen)) {
- free (*out);
- *out = NULL;
- return false;
- }
-
- if (outlen) {
- *outlen = needlen;
- }
-
- return true;
-}
-
-#ifdef MAIN
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <string.h>
-#include <stdio.h>
-#include "base32.h"
-
-int main(int argc, char **argv) {
- int i = 1;
- size_t inlen, outlen, argvlen;
- char *out;
- char *in;
- bool ok;
-
- while (argc > 1) {
- argv++; argc--;
- argvlen = strlen(*argv);
-
- outlen = base32_encode_alloc(*argv, argvlen, &out);
-
- if (out == NULL && outlen == 0 && inlen != 0) {
- fprintf(stderr, "ERROR(encode): input too long: %zd\n",
- outlen);
- return 1;
- }
-
- if (out == NULL) {
- fprintf(stderr, "ERROR(encode): memory allocation error"
- "\n");
- return 1;
- }
-
- ok = base32_decode_alloc(out, outlen, &in, &inlen);
-
- if (!ok) {
- fprintf(stderr, "ERROR(decode): input was not valid "
- "base32: `%s'\n", out);
- return 1;
- }
-
- if (in == NULL) {
- fprintf(stderr, "ERROR(decode): memory allocation "
- "error\n");
- }
-
- if ((inlen != argvlen) ||
- strcmp(*argv, in) != 0) {
- fprintf(stderr, "ERROR(encode/decode): input `%s' and "
- "output `%s'\n", *argv, in);
- return 1;
- }
- printf("INPUT: `%s'\nENCODE: `%s'\nDECODE: `%s'\n", *argv, out,
- in);
- }
-}
-
-#endif
diff --git a/src/common/base32.h b/src/common/base32.h
deleted file mode 100644
index 45df9fa..0000000
--- a/src/common/base32.h
+++ /dev/null
@@ -1,121 +0,0 @@
-/* base32.h -- Encode binary data using printable characters.
- Copyright (C) 2004, 2005, 2006, 2010 Free Software Foundation, Inc.
- Written by Ondřej Surý & Simon Josefsson.
-
- 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 2, 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, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-
-#ifndef _BASE32_H_
-#define _BASE32_H_
-
-/* Get size_t. */
-#include <stddef.h>
-
-/* Get bool. */
-#include <stdbool.h>
-
-/*!
- * \brief Counts the size of the Base32-encoded output for given input length.
- *
- * \note This uses that the expression (n+(k-1))/k means the smallest
- * integer >= n/k, i.e., the ceiling of n/k.
- */
-#define BASE32_LENGTH(inlen) ((((inlen) + 4) / 5) * 8)
-
-/*!
- * \brief Checks if the given character belongs to the Base32 alphabet.
- *
- * \param ch Character to check.
- *
- * \retval true if \a ch belongs to the Base32 alphabet.
- * \retval false otherwise.
- */
-extern bool isbase32(char ch);
-
-/*!
- * \brief Encodes the given character array using Base32 encoding.
- *
- * If \a outlen is less than BASE32_LENGTH(\a inlen), the function writes as
- * many bytes as possible to the output buffer. If \a outlen is more than
- * BASE32_LENGTH(\a inlen), the output will be zero-terminated.
- *
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
- * \param outlen Size of the output buffer.
- */
-extern void base32_encode(const char *in, size_t inlen, char *out,
- size_t outlen);
-
-/*!
- * \brief Encodes the given character array using Base32 encoding and allocates
- * space for the output.
- *
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
- *
- * \return Size of the allocated output buffer (0 if failed).
- */
-extern size_t base32_encode_alloc(const char *in, size_t inlen, char **out);
-
-/*!
- * \brief Decodes the given character array in Base32 encoding.
- *
- * If \a *outlen is too small, as many bytes as possible will be written to
- * \a out. On return, \a *outlen holds the length of decoded bytes in \a out.
- *
- * \note As soon as any non-alphabet characters are encountered, decoding is
- * stopped and false is returned. This means that, when applicable, you
- * must remove any line terminators that is part of the data stream before
- * calling this function.
- *
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
- * \param outlen Size of the output buffer.
- *
- * \retval true if decoding was successful, i.e. if the input was valid base32
- * data.
- * \retval false otherwise.
- */
-extern bool base32_decode(const char *in, size_t inlen, char *out,
- size_t *outlen);
-
-/*!
- * \brief Allocate an output buffer and decode the base32 encoded data to it.
- *
- * On return, the size of the decoded data is stored in \a *outlen. \a outlen
- * may be NULL, if the caller is not interested in the decoded length. \a *out
- * may be NULL to indicate an out of memory error, in which case \a *outlen
- * contains the size of the memory block needed.
- *
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer. \a *out may be NULL to indicate an out of memory
- * error in which case \a *outlen contains the size of the memory
- * block needed
- * \param outlen Size of the output buffer. May be NULL, if the caller is not
- * interested in the decoded length
- *
- * \retval true on successful decoding and memory allocation errors. (Use the
- * \a *out and \a *outlen parameters to differentiate between
- * successful decoding and memory error.)
- * \retval false if the input was invalid, in which case \a *out is NULL and
- * \a *outlen is undefined.
- */
-extern bool base32_decode_alloc(const char *in, size_t inlen, char **out,
- size_t *outlen);
-
-#endif /* _BASE32_H_ */
diff --git a/src/common/base32hex.c b/src/common/base32hex.c
index cd2d2ce..456d79b 100644
--- a/src/common/base32hex.c
+++ b/src/common/base32hex.c
@@ -1,562 +1,421 @@
-/* base32hex.c -- Encode binary data using printable characters.
- Copyright (C) 1999, 2000, 2001, 2004, 2005, 2006, 2010 Free Software
- Foundation, Inc.
-
- 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 2, 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, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-
-/* Adapted from base32.{h,c}. base32.{h,c} was adapted from
- * base64.{h,c} by Ondřej Surý. base64.{h,c} was written by Simon
- * Josefsson. Partially adapted from GNU MailUtils
- * (mailbox/filter_trans.c, as of 2004-11-28). Improved by review
- * from Paul Eggert, Bruno Haible, and Stepan Kasal.
- *
- * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>.
- *
- * Be careful with error checking. Here is how you would typically
- * use these functions:
- *
- * bool ok = base32hex_decode_alloc (in, inlen, &out, &outlen);
- * if (!ok)
- * FAIL: input was not valid base32hex
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN
- *
- * size_t outlen = base32hex_encode_alloc (in, inlen, &out);
- * if (out == NULL && outlen == 0 && inlen != 0)
- * FAIL: input too long
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN.
- *
- */
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
-/* Get prototype. */
-#include "base32hex.h"
+ 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.
-/* Get malloc. */
-#include <stdlib.h>
+ 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.
-/* Get UCHAR_MAX. */
-#include <limits.h>
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
-/* C89 compliant way to cast 'char' to 'unsigned char'. */
-static inline unsigned char to_uchar(char ch)
-{
- return ch;
-}
+#include "base32hex.h"
-/* Base32hex encode IN array of size INLEN into OUT array of size OUTLEN.
- If OUTLEN is less than BASE32HEX_LENGTH(INLEN), write as many bytes as
- possible. If OUTLEN is larger than BASE32HEX_LENGTH(INLEN), also zero
- terminate the output buffer. */
-void base32hex_encode(const char *in, size_t inlen, char *out, size_t outlen)
+#include <stdlib.h> // malloc
+#include <stdint.h> // uint8_t
+
+/*! \brief Maximal length of binary input to Base32hex encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 8) * 5)
+
+/*! \brief Base32hex padding character. */
+const uint8_t base32hex_pad = '=';
+/*! \brief Base32hex alphabet. */
+const uint8_t base32hex_enc[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+
+/*! \brief Indicates bad Base32hex character. */
+#define KO 255
+/*! \brief Indicates Base32hex padding character. */
+#define PD 32
+
+/*! \brief Transformation and validation table for decoding Base32hex. */
+const uint8_t base32hex_dec[256] = {
+ [ 0] = KO, [ 43] = KO, ['V'] = 31, [129] = KO, [172] = KO, [215] = KO,
+ [ 1] = KO, [ 44] = KO, ['W'] = KO, [130] = KO, [173] = KO, [216] = KO,
+ [ 2] = KO, [ 45] = KO, ['X'] = KO, [131] = KO, [174] = KO, [217] = KO,
+ [ 3] = KO, [ 46] = KO, ['Y'] = KO, [132] = KO, [175] = KO, [218] = KO,
+ [ 4] = KO, [ 47] = KO, ['Z'] = KO, [133] = KO, [176] = KO, [219] = KO,
+ [ 5] = KO, ['0'] = 0, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO,
+ [ 6] = KO, ['1'] = 1, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO,
+ [ 7] = KO, ['2'] = 2, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO,
+ [ 8] = KO, ['3'] = 3, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO,
+ [ 9] = KO, ['4'] = 4, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO,
+ [ 10] = KO, ['5'] = 5, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO,
+ [ 11] = KO, ['6'] = 6, ['a'] = 10, [140] = KO, [183] = KO, [226] = KO,
+ [ 12] = KO, ['7'] = 7, ['b'] = 11, [141] = KO, [184] = KO, [227] = KO,
+ [ 13] = KO, ['8'] = 8, ['c'] = 12, [142] = KO, [185] = KO, [228] = KO,
+ [ 14] = KO, ['9'] = 9, ['d'] = 13, [143] = KO, [186] = KO, [229] = KO,
+ [ 15] = KO, [ 58] = KO, ['e'] = 14, [144] = KO, [187] = KO, [230] = KO,
+ [ 16] = KO, [ 59] = KO, ['f'] = 15, [145] = KO, [188] = KO, [231] = KO,
+ [ 17] = KO, [ 60] = KO, ['g'] = 16, [146] = KO, [189] = KO, [232] = KO,
+ [ 18] = KO, ['='] = PD, ['h'] = 17, [147] = KO, [190] = KO, [233] = KO,
+ [ 19] = KO, [ 62] = KO, ['i'] = 18, [148] = KO, [191] = KO, [234] = KO,
+ [ 20] = KO, [ 63] = KO, ['j'] = 19, [149] = KO, [192] = KO, [235] = KO,
+ [ 21] = KO, [ 64] = KO, ['k'] = 20, [150] = KO, [193] = KO, [236] = KO,
+ [ 22] = KO, ['A'] = 10, ['l'] = 21, [151] = KO, [194] = KO, [237] = KO,
+ [ 23] = KO, ['B'] = 11, ['m'] = 22, [152] = KO, [195] = KO, [238] = KO,
+ [ 24] = KO, ['C'] = 12, ['n'] = 23, [153] = KO, [196] = KO, [239] = KO,
+ [ 25] = KO, ['D'] = 13, ['o'] = 24, [154] = KO, [197] = KO, [240] = KO,
+ [ 26] = KO, ['E'] = 14, ['p'] = 25, [155] = KO, [198] = KO, [241] = KO,
+ [ 27] = KO, ['F'] = 15, ['q'] = 26, [156] = KO, [199] = KO, [242] = KO,
+ [ 28] = KO, ['G'] = 16, ['r'] = 27, [157] = KO, [200] = KO, [243] = KO,
+ [ 29] = KO, ['H'] = 17, ['s'] = 28, [158] = KO, [201] = KO, [244] = KO,
+ [ 30] = KO, ['I'] = 18, ['t'] = 29, [159] = KO, [202] = KO, [245] = KO,
+ [ 31] = KO, ['J'] = 19, ['u'] = 30, [160] = KO, [203] = KO, [246] = KO,
+ [ 32] = KO, ['K'] = 20, ['v'] = 31, [161] = KO, [204] = KO, [247] = KO,
+ [ 33] = KO, ['L'] = 21, ['w'] = KO, [162] = KO, [205] = KO, [248] = KO,
+ [ 34] = KO, ['M'] = 22, ['x'] = KO, [163] = KO, [206] = KO, [249] = KO,
+ [ 35] = KO, ['N'] = 23, ['y'] = KO, [164] = KO, [207] = KO, [250] = KO,
+ [ 36] = KO, ['O'] = 24, ['z'] = KO, [165] = KO, [208] = KO, [251] = KO,
+ [ 37] = KO, ['P'] = 25, [123] = KO, [166] = KO, [209] = KO, [252] = KO,
+ [ 38] = KO, ['Q'] = 26, [124] = KO, [167] = KO, [210] = KO, [253] = KO,
+ [ 39] = KO, ['R'] = 27, [125] = KO, [168] = KO, [211] = KO, [254] = KO,
+ [ 40] = KO, ['S'] = 28, [126] = KO, [169] = KO, [212] = KO, [255] = KO,
+ [ 41] = KO, ['T'] = 29, [127] = KO, [170] = KO, [213] = KO,
+ [ 42] = KO, ['U'] = 30, [128] = KO, [171] = KO, [214] = KO,
+};
+
+int32_t base32hex_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
{
- static const char b32str[32] =
- "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+ uint8_t rest_len = in_len % 5;
+ const uint8_t *data = in;
+ const uint8_t *stop = in + in_len - rest_len;
+ uint8_t *text = out;
+ uint8_t num;
+
+ // Checking inputs.
+ if (in == NULL || out == NULL || in_len > MAX_BIN_DATA_LEN ||
+ out_len < ((in_len + 4) / 5) * 8) {
+ return -1;
+ }
- while (inlen && outlen) {
- *out++ = b32str[(to_uchar(in[0]) >> 3) & 0x1f];
- if (!--outlen) {
- break;
- }
- *out++ = b32str[((to_uchar(in[0]) << 2)
- + (--inlen ? to_uchar(in[1]) >> 6 : 0))
- & 0x1f];
- if (!--outlen) {
- break;
- }
- *out++ =(inlen
- ? b32str[(to_uchar(in[1]) >> 1) & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[1]) << 4)
- + (--inlen ? to_uchar(in[2]) >> 4 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[2]) << 1)
- + (--inlen ? to_uchar(in[3]) >> 7 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = (inlen
- ? b32str[(to_uchar(in[3]) >> 2) & 0x1f]
- : '=');
- if (!--outlen)
- {
- break;
- }
- *out++ = (inlen
- ? b32str[((to_uchar(in[3]) << 3)
- + (--inlen ? to_uchar(in[4]) >> 5 : 0))
- & 0x1f]
- : '=');
- if (!--outlen) {
- break;
- }
- *out++ = inlen ? b32str[to_uchar(in[4]) & 0x1f] : '=';
- if (!--outlen) {
- break;
- }
- if (inlen) {
- inlen--;
- }
- if (inlen) {
- in += 5;
- }
+ // Encoding loop takes 5 bytes and creates 8 characters.
+ while (data < stop) {
+ // Computing 1. Base32hex character.
+ num = *data >> 3;
+ *text++ = base32hex_enc[num];
+
+ // Computing 2. Base32hex character.
+ num = (*data++ & 0x07) << 2;
+ num += *data >> 6;
+ *text++ = base32hex_enc[num];
+
+ // Computing 3. Base32hex character.
+ num = (*data & 0x3E) >> 1;
+ *text++ = base32hex_enc[num];
+
+ // Computing 4. Base32hex character.
+ num = (*data++ & 0x01) << 4;
+ num += *data >> 4;
+ *text++ = base32hex_enc[num];
+
+ // Computing 5. Base32hex character.
+ num = (*data++ & 0x0F) << 1;
+ num += *data >> 7;
+ *text++ = base32hex_enc[num];
+
+ // Computing 6. Base32hex character.
+ num = (*data & 0x7C) >> 2;
+ *text++ = base32hex_enc[num];
+
+ // Computing 7. Base32hex character.
+ num = (*data++ & 0x03) << 3;
+ num += *data >> 5;
+ *text++ = base32hex_enc[num];
+
+ // Computing 8. Base32hex character.
+ num = *data++ & 0x1F;
+ *text++ = base32hex_enc[num];
}
- if (outlen) {
- *out = '\0';
+ // Processing of padding, if any.
+ switch (rest_len) {
+ // Input data has 4-byte last block => 1-char padding.
+ case 4:
+ // Computing 1. Base32hex character.
+ num = *data >> 3;
+ *text++ = base32hex_enc[num];
+
+ // Computing 2. Base32hex character.
+ num = (*data++ & 0x07) << 2;
+ num += *data >> 6;
+ *text++ = base32hex_enc[num];
+
+ // Computing 3. Base32hex character.
+ num = (*data & 0x3E) >> 1;
+ *text++ = base32hex_enc[num];
+
+ // Computing 4. Base32hex character.
+ num = (*data++ & 0x01) << 4;
+ num += *data >> 4;
+ *text++ = base32hex_enc[num];
+
+ // Computing 5. Base32hex character.
+ num = (*data++ & 0x0F) << 1;
+ num += *data >> 7;
+ *text++ = base32hex_enc[num];
+
+ // Computing 6. Base32hex character.
+ num = (*data & 0x7C) >> 2;
+ *text++ = base32hex_enc[num];
+
+ // Computing 7. Base32hex character.
+ num = (*data++ & 0x03) << 3;
+ *text++ = base32hex_enc[num];
+
+ // 1 padding character.
+ *text++ = base32hex_pad;
+
+ break;
+ // Input data has 3-byte last block => 3-char padding.
+ case 3:
+ // Computing 1. Base32hex character.
+ num = *data >> 3;
+ *text++ = base32hex_enc[num];
+
+ // Computing 2. Base32hex character.
+ num = (*data++ & 0x07) << 2;
+ num += *data >> 6;
+ *text++ = base32hex_enc[num];
+
+ // Computing 3. Base32hex character.
+ num = (*data & 0x3E) >> 1;
+ *text++ = base32hex_enc[num];
+
+ // Computing 4. Base32hex character.
+ num = (*data++ & 0x01) << 4;
+ num += *data >> 4;
+ *text++ = base32hex_enc[num];
+
+ // Computing 5. Base32hex character.
+ num = (*data++ & 0x0F) << 1;
+ *text++ = base32hex_enc[num];
+
+ // 3 padding characters.
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+
+ break;
+ // Input data has 2-byte last block => 4-char padding.
+ case 2:
+ // Computing 1. Base32hex character.
+ num = *data >> 3;
+ *text++ = base32hex_enc[num];
+
+ // Computing 2. Base32hex character.
+ num = (*data++ & 0x07) << 2;
+ num += *data >> 6;
+ *text++ = base32hex_enc[num];
+
+ // Computing 3. Base32hex character.
+ num = (*data & 0x3E) >> 1;
+ *text++ = base32hex_enc[num];
+
+ // Computing 4. Base32hex character.
+ num = (*data++ & 0x01) << 4;
+ *text++ = base32hex_enc[num];
+
+ // 4 padding characters.
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+
+ break;
+ // Input data has 1-byte last block => 6-char padding.
+ case 1:
+ // Computing 1. Base32hex character.
+ num = *data >> 3;
+ *text++ = base32hex_enc[num];
+
+ // Computing 2. Base32hex character.
+ num = (*data++ & 0x07) << 2;
+ *text++ = base32hex_enc[num];
+
+ // 6 padding characters.
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+ *text++ = base32hex_pad;
+
+ break;
}
+
+ return (text - out);
}
-/* Allocate a buffer and store zero terminated base32hex encoded data
- from array IN of size INLEN, returning BASE32HEX_LENGTH(INLEN), i.e.,
- the length of the encoded data, excluding the terminating zero. On
- return, the OUT variable will hold a pointer to newly allocated
- memory that must be deallocated by the caller. If output string
- length would overflow, 0 is returned and OUT is set to NULL. If
- memory allocation failed, OUT is set to NULL, and the return value
- indicates length of the requested memory block, i.e.,
- BASE32HEX_LENGTH(inlen) + 1. */
-size_t base32hex_encode_alloc(const char *in, size_t inlen, char **out)
+int32_t base32hex_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
{
- size_t outlen = 1 + BASE32HEX_LENGTH (inlen);
-
- /* Check for overflow in outlen computation.
- *
- * If there is no overflow, outlen >= inlen.
- *
- * If the operation (inlen + 2) overflows then it yields at most +1, so
- * outlen is 0.
- *
- * If the multiplication overflows, we lose at least half of the
- * correct value, so the result is < ((inlen + 2) / 3) * 2, which is
- * less than (inlen + 2) * 0.66667, which is less than inlen as soon as
- * (inlen > 4).
- */
- if (inlen > outlen)
- {
- *out = NULL;
- return 0;
- }
+ uint32_t out_len = ((in_len + 4) / 5) * 8;
- *out = malloc(outlen);
- if (!*out) {
- return outlen;
+ // Checking inputs.
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return -1;
}
- base32hex_encode(in, inlen, *out, outlen);
+ // Allocating output buffer.
+ *out = malloc(out_len);
- return outlen - 1;
-}
-
-/* With this approach this file works independent of the charset used
- (think EBCDIC). However, it does assume that the characters in the
- Base32hex alphabet (A-Z2-7) are encoded in 0..255. POSIX
- 1003.1-2001 require that char and unsigned char are 8-bit
- quantities, though, taking care of that problem. But this may be a
- potential problem on non-POSIX C99 platforms.
-
- IBM C V6 for AIX mishandles "#define B32(x) ...'x'...", so use "_"
- as the formal parameter rather than "x". */
-#define B32(_) \
- ((_) == '0' ? 0 \
- : (_) == '1' ? 1 \
- : (_) == '2' ? 2 \
- : (_) == '3' ? 3 \
- : (_) == '4' ? 4 \
- : (_) == '5' ? 5 \
- : (_) == '6' ? 6 \
- : (_) == '7' ? 7 \
- : (_) == '8' ? 8 \
- : (_) == '9' ? 9 \
- : (_) == 'A' ? 10 \
- : (_) == 'B' ? 11 \
- : (_) == 'C' ? 12 \
- : (_) == 'D' ? 13 \
- : (_) == 'E' ? 14 \
- : (_) == 'F' ? 15 \
- : (_) == 'G' ? 16 \
- : (_) == 'H' ? 17 \
- : (_) == 'I' ? 18 \
- : (_) == 'J' ? 19 \
- : (_) == 'K' ? 20 \
- : (_) == 'L' ? 21 \
- : (_) == 'M' ? 22 \
- : (_) == 'N' ? 23 \
- : (_) == 'O' ? 24 \
- : (_) == 'P' ? 25 \
- : (_) == 'Q' ? 26 \
- : (_) == 'R' ? 27 \
- : (_) == 'S' ? 28 \
- : (_) == 'T' ? 29 \
- : (_) == 'U' ? 30 \
- : (_) == 'V' ? 31 \
- : (_) == 'a' ? 10 \
- : (_) == 'b' ? 11 \
- : (_) == 'c' ? 12 \
- : (_) == 'd' ? 13 \
- : (_) == 'e' ? 14 \
- : (_) == 'f' ? 15 \
- : (_) == 'g' ? 16 \
- : (_) == 'h' ? 17 \
- : (_) == 'i' ? 18 \
- : (_) == 'j' ? 19 \
- : (_) == 'k' ? 20 \
- : (_) == 'l' ? 21 \
- : (_) == 'm' ? 22 \
- : (_) == 'n' ? 23 \
- : (_) == 'o' ? 24 \
- : (_) == 'p' ? 25 \
- : (_) == 'q' ? 26 \
- : (_) == 'r' ? 27 \
- : (_) == 's' ? 28 \
- : (_) == 't' ? 29 \
- : (_) == 'u' ? 30 \
- : (_) == 'v' ? 31 \
- : -1)
-
-static const signed char b32[0x100] = {
- B32 (0), B32 (1), B32 (2), B32 (3),
- B32 (4), B32 (5), B32 (6), B32 (7),
- B32 (8), B32 (9), B32 (10), B32 (11),
- B32 (12), B32 (13), B32 (14), B32 (15),
- B32 (16), B32 (17), B32 (18), B32 (19),
- B32 (20), B32 (21), B32 (22), B32 (23),
- B32 (24), B32 (25), B32 (26), B32 (27),
- B32 (28), B32 (29), B32 (30), B32 (31),
- B32 (32), B32 (33), B32 (34), B32 (35),
- B32 (36), B32 (37), B32 (38), B32 (39),
- B32 (40), B32 (41), B32 (42), B32 (43),
- B32 (44), B32 (45), B32 (46), B32 (47),
- B32 (48), B32 (49), B32 (50), B32 (51),
- B32 (52), B32 (53), B32 (54), B32 (55),
- B32 (56), B32 (57), B32 (58), B32 (59),
- B32 (60), B32 (61), B32 (62), B32 (63),
- B32 (64), B32 (65), B32 (66), B32 (67),
- B32 (68), B32 (69), B32 (70), B32 (71),
- B32 (72), B32 (73), B32 (74), B32 (75),
- B32 (76), B32 (77), B32 (78), B32 (79),
- B32 (80), B32 (81), B32 (82), B32 (83),
- B32 (84), B32 (85), B32 (86), B32 (87),
- B32 (88), B32 (89), B32 (90), B32 (91),
- B32 (92), B32 (93), B32 (94), B32 (95),
- B32 (96), B32 (97), B32 (98), B32 (99),
- B32 (100), B32 (101), B32 (102), B32 (103),
- B32 (104), B32 (105), B32 (106), B32 (107),
- B32 (108), B32 (109), B32 (110), B32 (111),
- B32 (112), B32 (113), B32 (114), B32 (115),
- B32 (116), B32 (117), B32 (118), B32 (119),
- B32 (120), B32 (121), B32 (122), B32 (123),
- B32 (124), B32 (125), B32 (126), B32 (127),
- B32 (128), B32 (129), B32 (130), B32 (131),
- B32 (132), B32 (133), B32 (134), B32 (135),
- B32 (136), B32 (137), B32 (138), B32 (139),
- B32 (140), B32 (141), B32 (142), B32 (143),
- B32 (144), B32 (145), B32 (146), B32 (147),
- B32 (148), B32 (149), B32 (150), B32 (151),
- B32 (152), B32 (153), B32 (154), B32 (155),
- B32 (156), B32 (157), B32 (158), B32 (159),
- B32 (160), B32 (161), B32 (162), B32 (163),
- B32 (164), B32 (165), B32 (166), B32 (167),
- B32 (168), B32 (169), B32 (170), B32 (171),
- B32 (172), B32 (173), B32 (174), B32 (175),
- B32 (176), B32 (177), B32 (178), B32 (179),
- B32 (180), B32 (181), B32 (182), B32 (183),
- B32 (184), B32 (185), B32 (186), B32 (187),
- B32 (188), B32 (189), B32 (190), B32 (191),
- B32 (192), B32 (193), B32 (194), B32 (195),
- B32 (196), B32 (197), B32 (198), B32 (199),
- B32 (200), B32 (201), B32 (202), B32 (203),
- B32 (204), B32 (205), B32 (206), B32 (207),
- B32 (208), B32 (209), B32 (210), B32 (211),
- B32 (212), B32 (213), B32 (214), B32 (215),
- B32 (216), B32 (217), B32 (218), B32 (219),
- B32 (220), B32 (221), B32 (222), B32 (223),
- B32 (224), B32 (225), B32 (226), B32 (227),
- B32 (228), B32 (229), B32 (230), B32 (231),
- B32 (232), B32 (233), B32 (234), B32 (235),
- B32 (236), B32 (237), B32 (238), B32 (239),
- B32 (240), B32 (241), B32 (242), B32 (243),
- B32 (244), B32 (245), B32 (246), B32 (247),
- B32 (248), B32 (249), B32 (250), B32 (251),
- B32 (252), B32 (253), B32 (254), B32 (255)
-};
-
-#if UCHAR_MAX == 255
-#define uchar_in_range(c) true
-#else
-#define uchar_in_range(c) ((c) <= 255)
-#endif
+ if (*out == NULL) {
+ return -1;
+ }
-/* Return true if CH is a character from the Base32hex alphabet, and
- false otherwise. Note that '=' is padding and not considered to be
- part of the alphabet. */
-bool isbase32hex(char ch)
-{
- return uchar_in_range(to_uchar(ch)) && 0 <= b32[to_uchar(ch)];
+ // Encoding data.
+ return base32hex_encode(in, in_len, *out, out_len);
}
-/* Decode base32hex encoded input array IN of length INLEN to output
- array OUT that can hold *OUTLEN bytes. Return true if decoding was
- successful, i.e. if the input was valid base32hex data, false
- otherwise. If *OUTLEN is too small, as many bytes as possible will
- be written to OUT. On return, *OUTLEN holds the length of decoded
- bytes in OUT. Note that as soon as any non-alphabet characters are
- encountered, decoding is stopped and false is returned. This means
- that, when applicable, you must remove any line terminators that is
- part of the data stream before calling this function. */
-bool base32hex_decode(const char *in, size_t inlen, char *out, size_t *outlen)
+int32_t base32hex_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
{
- size_t outleft = *outlen;
+ const uint8_t *data = in;
+ const uint8_t *stop = in + in_len;
+ uint8_t *bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4, c5, c6, c7, c8;
+
+ // Checking inputs.
+ if (in == NULL || out == NULL || (in_len % 8) != 0 ||
+ in_len > INT32_MAX || out_len < ((in_len + 7) / 8) * 5) {
+ return -1;
+ }
- while (inlen >= 2) {
- if (!isbase32hex(in[0]) || !isbase32hex(in[1])) {
- break;
+ // Decoding loop takes 8 characters and creates 5 bytes.
+ while (data < stop) {
+ // Filling and transforming 8 Base32hex chars.
+ c1 = base32hex_dec[*data++];
+ c2 = base32hex_dec[*data++];
+ c3 = base32hex_dec[*data++];
+ c4 = base32hex_dec[*data++];
+ c5 = base32hex_dec[*data++];
+ c6 = base32hex_dec[*data++];
+ c7 = base32hex_dec[*data++];
+ c8 = base32hex_dec[*data++];
+
+ // Check 8. char if is bad or padding.
+ if (c8 >= PD) {
+ if (c8 == PD) {
+ pad_len = 1;
+ } else {
+ return -2;
+ }
}
- if (outleft) {
- *out++ = ((b32[to_uchar(in[0])] << 3)
- | (b32[to_uchar(in[1])] >> 2));
- outleft--;
+ // Check 7. char if is bad or padding (if so, 6. must be too).
+ if (c7 >= PD) {
+ if (c7 == PD && c6 == PD) {
+ pad_len = 3;
+ } else {
+ return -2;
+ }
}
- if (inlen == 2) {
- break;
+ // Check 6. char if is bad or padding.
+ if (c6 >= PD) {
+ if (c6 == PD) {
+ pad_len = 3;
+ } else {
+ return -2;
+ }
}
- if (in[2] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[3] != '=') ||
- (in[4] != '=') ||
- (in[5] != '=') ||
- (in[6] != '=') ||
- (in[7] != '=')) {
- break;
- }
- } else {
- if (!isbase32hex(in[2]) || !isbase32hex(in[3])) {
- break;
+ // Check 5. char if is bad or padding.
+ if (c5 >= PD) {
+ if (c5 == PD) {
+ pad_len = 4;
+ } else {
+ return -2;
}
+ }
- if (outleft) {
- *out++ = ((b32[to_uchar(in[1])] << 6)
- | ((b32[to_uchar(in[2])] << 1) & 0x3E)
- | (b32[to_uchar(in[3])] >> 4));
- outleft--;
- }
-
- if (inlen == 4) {
- break;
- }
-
- if (in[4] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[5] != '=') ||
- (in[6] != '=') ||
- (in[7] != '=')) {
- break;
- }
+ // Check 4. char if is bad or padding (if so, 3. must be too).
+ if (c4 >= PD) {
+ if (c4 == PD && c3 == PD) {
+ pad_len = 6;
} else {
- if (!isbase32hex (in[3]) || !isbase32hex(in[4])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[3])] << 4)
- | (b32[to_uchar(in[4])] >> 1));
- outleft--;
- }
-
- if (inlen == 5) {
- break;
- }
-
- if (in[5] == '=') {
- if (inlen != 8) {
- break;
- }
-
- if ((in[6] != '=')
- || (in[7] != '=')) {
- break;
- }
- } else {
- if (!isbase32hex (in[5])
- || !isbase32hex (in[6])) {
- break;
- }
-
- if (outleft) {
- *out++ = ((b32[to_uchar(in[4])]
- << 7)
- | (b32[to_uchar(in[5])] << 2)
- | (b32[to_uchar(in[6])]
- >> 3));
- outleft--;
- }
-
- if (inlen == 7) {
- break;
- }
-
- if (in[7] == '=') {
- if (inlen != 8) {
- break;
- }
- } else {
- if (!isbase32hex (in[7])) {
- break;
- }
-
- if (outleft) {
- *out++ =
- ((b32[to_uchar(in[6])]
- << 5) | (b32[
- to_uchar(in[7])]));
- outleft--;
- }
- }
- }
+ return -2;
}
}
-
- in += 8;
- inlen -= 8;
- }
-
- *outlen -= outleft;
-
- if (inlen != 0) {
- return false;
- }
-
- return true;
-}
-
-/* Allocate an output buffer in *OUT, and decode the base32hex encoded
- data stored in IN of size INLEN to the *OUT buffer. On return, the
- size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL,
- if the caller is not interested in the decoded length. *OUT may be
- NULL to indicate an out of memory error, in which case *OUTLEN
- contains the size of the memory block needed. The function returns
- true on successful decoding and memory allocation errors. (Use the
- *OUT and *OUTLEN parameters to differentiate between successful
- decoding and memory error.) The function returns false if the
- input was invalid, in which case *OUT is NULL and *OUTLEN is
- undefined. */
-bool base32hex_decode_alloc(const char *in, size_t inlen, char **out,
- size_t *outlen)
-{
- /* This may allocate a few bytes too much, depending on input,
- but it's not worth the extra CPU time to compute the exact amount.
- The exact amount is 5 * inlen / 8, minus 1 if the input ends
- with "=" and minus another 1 if the input ends with "==", etc.
- Dividing before multiplying avoids the possibility of overflow. */
- size_t needlen = 5 * (inlen / 8) + 4;
-
- *out = malloc(needlen);
- if (!*out) {
- return true;
- }
-
- if (!base32hex_decode(in, inlen, *out, &needlen)) {
- free (*out);
- *out = NULL;
- return false;
- }
-
- if (outlen) {
- *outlen = needlen;
- }
-
- return true;
-}
-#ifdef MAIN
-
-#include <stddef.h>
-#include <stdbool.h>
-#include <string.h>
-#include <stdio.h>
-#include "base32hex.h"
-
-int main(int argc, char **argv) {
- int i = 1;
- size_t inlen, outlen, argvlen;
- char *out;
- char *in;
- bool ok;
-
- while (argc > 1) {
- argv++; argc--;
- argvlen = strlen(*argv);
-
- outlen = base32hex_encode_alloc(*argv, argvlen, &out);
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (c3 == PD) {
+ pad_len = 6;
+ } else {
+ return -2;
+ }
+ }
- if (out == NULL && outlen == 0 && inlen != 0) {
- fprintf(stderr, "ERROR(encode): input too long: %zd\n",
- outlen);
- return 1;
+ // 1. and 2. chars must not be padding.
+ if (c2 >= PD || c1 >= PD) {
+ return -2;
}
- if (out == NULL) {
- fprintf(stderr, "ERROR(encode): memory allocation error"
- "\n");
- return 1;
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ // No padding => output has 5 bytess.
+ case 0:
+ *bin++ = (c1 << 3) + (c2 >> 2);
+ *bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+ *bin++ = (c4 << 4) + (c5 >> 1);
+ *bin++ = (c5 << 7) + (c6 << 2) + (c7 >> 3);
+ *bin++ = (c7 << 5) + c8;
+ break;
+ // 1-char padding => output has 4 bytes.
+ case 1:
+ *bin++ = (c1 << 3) + (c2 >> 2);
+ *bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+ *bin++ = (c4 << 4) + (c5 >> 1);
+ *bin++ = (c5 << 7) + (c6 << 2) + (c7 >> 3);
+ break;
+ // 3-char padding => output has 3 bytes.
+ case 3:
+ *bin++ = (c1 << 3) + (c2 >> 2);
+ *bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+ *bin++ = (c4 << 4) + (c5 >> 1);
+ break;
+ // 4-char padding => output has 2 bytes.
+ case 4:
+ *bin++ = (c1 << 3) + (c2 >> 2);
+ *bin++ = (c2 << 6) + (c3 << 1) + (c4 >> 4);
+ break;
+ // 6-char padding => output has 1 byte.
+ case 6:
+ *bin++ = (c1 << 3) + (c2 >> 2);
+ break;
}
+ }
- ok = base32hex_decode_alloc(out, outlen, &in, &inlen);
+ return (bin - out);
+}
- if (!ok) {
- fprintf(stderr, "ERROR(decode): input was not valid "
- "base32hex: `%s'\n", out);
- return 1;
- }
+int32_t base32hex_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
+{
+ uint32_t out_len = ((in_len + 7) / 8) * 5;
- if (in == NULL) {
- fprintf(stderr, "ERROR(decode): memory allocation "
- "error\n");
- }
+ // Allocating output buffer.
+ *out = malloc(out_len);
- if ((inlen != argvlen) ||
- strcmp(*argv, in) != 0) {
- fprintf(stderr, "ERROR(encode/decode): input `%s' and "
- "output `%s'\n", *argv, in);
- return 1;
- }
- printf("INPUT: `%s'\nENCODE: `%s'\nDECODE: `%s'\n", *argv, out,
- in);
+ if (*out == NULL) {
+ return -1;
}
+
+ // Decoding data.
+ return base32hex_decode(in, in_len, *out, out_len);
}
-#endif
diff --git a/src/common/base32hex.h b/src/common/base32hex.h
index 9ac4fa8..c7f3f12 100644
--- a/src/common/base32hex.h
+++ b/src/common/base32hex.h
@@ -1,124 +1,117 @@
-/* base32hex.h -- Encode binary data using printable characters.
- Copyright (C) 2004, 2005, 2006, 2010 Free Software Foundation, Inc.
- Written by Ondřej Surý & Simon Josefsson.
+/* Copyright (C) 2011 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 2, or (at your option)
- any later version.
+ 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, write to the Free Software Foundation,
- Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
-
-#ifndef _BASE32HEX_H_
-#define _BASE32HEX_H_
-
-/* Get size_t. */
-#include <stddef.h>
-
-/* Get bool. */
-#include <stdbool.h>
+ 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/>.
+ */
/*!
- * \brief Counts the size of the Base32Hex-encoded output for given input
- * length.
+ * \file base32hex.h
*
- * \note This uses that the expression (n+(k-1))/k means the smallest
- * integer >= n/k, i.e., the ceiling of n/k.
+ * \author Daniel Salzman <daniel.salzman@nic.cz>
+ *
+ * \brief Base32hex implementation (RFC 4648).
+ *
+ * \note Input Base32hex string can contain a-v characters. These characters
+ * are considered as A-V equivalent.
+ *
+ * \addtogroup common_lib
+ * @{
*/
-#define BASE32HEX_LENGTH(inlen) ((((inlen) + 4) / 5) * 8)
+
+#ifndef _KNOTD_COMMON__BASE32HEX_H_
+#define _KNOTD_COMMON__BASE32HEX_H_
+
+#include <stdint.h> // uint8_t
/*!
- * \brief Checks if the given character belongs to the Base32Hex alphabet.
+ * \brief Encodes binary data using Base32hex.
+ *
+ * \note Output data buffer contains Base32hex text string which isn't
+ * terminated with '\0'!
*
- * \param ch Character to check.
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
*
- * \retval true if \a ch belongs to the Base32Hex alphabet.
- * \retval false otherwise.
+ * \retval >=0 length of output string.
+ * \retval -1 if error.
*/
-extern bool isbase32hex(char ch);
+int32_t base32hex_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
/*!
- * \brief Encodes the given character array using Base32 encoding with extended
- * hex alphabet.
+ * \brief Encodes binary data using Base32hex and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base32hex text string which isn't
+ * terminated with '\0'!
*
- * If \a outlen is less than BASE32HEX_LENGTH(\a inlen), the function writes as
- * many bytes as possible to the output buffer. If \a outlen is more than
- * BASE32HEX_LENGTH(\a inlen), the output will be zero-terminated.
+ * \note Output buffer should be deallocated after use.
*
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
- * \param outlen Size of the output buffer.
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval -1 if error.
*/
-extern void base32hex_encode(const char *in, size_t inlen, char *out,
- size_t outlen);
+int32_t base32hex_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
/*!
- * \brief Encodes the given character array using Base32 encoding with extended
- * hex alphabet and allocates space for the output.
+ * \brief Decodes text data using Base32hex.
+ *
+ * \note Input data needn't be terminated with '\0'.
*
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
+ * \note Input data must be continuous Base32hex string!
*
- * \return Size of the allocated output buffer (0 if failed).
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval -1 if error.
+ * \retval -2 if bad input data.
*/
-extern size_t base32hex_encode_alloc(const char *in, size_t inlen, char **out);
+int32_t base32hex_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
/*!
- * \brief Decodes the given character array in Base32 encoding with extended
- * hex alphabet.
+ * \brief Decodes text data using Base32hex and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
*
- * If \a *outlen is too small, as many bytes as possible will be written to
- * \a out. On return, \a *outlen holds the length of decoded bytes in \a out.
+ * \note Input data must be continuous Base32hex string!
*
- * \note As soon as any non-alphabet characters are encountered, decoding is
- * stopped and false is returned. This means that, when applicable, you
- * must remove any line terminators that is part of the data stream before
- * calling this function.
+ * \note Output buffer should be deallocated after use.
*
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer.
- * \param outlen Size of the output buffer.
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
*
- * \retval true if decoding was successful, i.e. if the input was valid
- * base32hex data.
- * \retval false otherwise.
+ * \retval >=0 length of output data.
+ * \retval -1 if error.
+ * \retval -2 if bad input data.
*/
-extern bool base32hex_decode(const char *in, size_t inlen, char *out,
- size_t *outlen);
+int32_t base32hex_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
-/*!
- * \brief Allocate an output buffer and decode the base32hex encoded data to it.
- *
- * On return, the size of the decoded data is stored in \a *outlen. \a outlen
- * may be NULL, if the caller is not interested in the decoded length. \a *out
- * may be NULL to indicate an out of memory error, in which case \a *outlen
- * contains the size of the memory block needed.
- *
- * \param in Input array of characters.
- * \param inlen Length of the input array.
- * \param out Output buffer. \a *out may be NULL to indicate an out of memory
- * error in which case \a *outlen contains the size of the memory
- * block needed
- * \param outlen Size of the output buffer. May be NULL, if the caller is not
- * interested in the decoded length
- *
- * \retval true on successful decoding and memory allocation errors. (Use the
- * \a *out and \a *outlen parameters to differentiate between
- * successful decoding and memory error.)
- * \retval false if the input was invalid, in which case \a *out is NULL and
- * \a *outlen is undefined.
- */
-extern bool base32hex_decode_alloc(const char *in, size_t inlen, char **out,
- size_t *outlen);
+#endif // _KNOTD_COMMON__BASE32HEX_H_
-#endif /* _BASE32HEX_H_ */
+/*! @} */
diff --git a/src/common/base64.c b/src/common/base64.c
index f1f601c..a153a70 100644
--- a/src/common/base64.c
+++ b/src/common/base64.c
@@ -1,574 +1,271 @@
-/* base64.c -- Encode binary data using printable characters.
- Copyright (C) 1999-2001, 2004-2006, 2009-2012 Free Software Foundation, Inc.
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1, 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 Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>. */
-
-/* Written by Simon Josefsson. Partially adapted from GNU MailUtils
- * (mailbox/filter_trans.c, as of 2004-11-28). Improved by review
- * from Paul Eggert, Bruno Haible, and Stepan Kasal.
- *
- * See also RFC 4648 <http://www.ietf.org/rfc/rfc4648.txt>.
- *
- * Be careful with error checking. Here is how you would typically
- * use these functions:
- *
- * bool ok = base64_decode_alloc (in, inlen, &out, &outlen);
- * if (!ok)
- * FAIL: input was not valid base64
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN
- *
- * size_t outlen = base64_encode_alloc (in, inlen, &out);
- * if (out == NULL && outlen == 0 && inlen != 0)
- * FAIL: input too long
- * if (out == NULL)
- * FAIL: memory allocation error
- * OK: data in OUT/OUTLEN.
- *
- */
+/* Copyright (C) 2011 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
-#include <config.h>
+ 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.
-/* Get prototype. */
-#include "base64.h"
+ 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.
-/* Get malloc. */
-#include <stdlib.h>
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
-/* Get UCHAR_MAX. */
-#include <limits.h>
+#include "base64.h"
-#include <string.h>
+#include <stdlib.h> // malloc
+#include <stdint.h> // uint8_t
+
+/*! \brief Maximal length of binary input to Base64 encoding. */
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+/*! \brief Base64 padding character. */
+const uint8_t base64_pad = '=';
+/*! \brief Base64 alphabet. */
+const uint8_t base64_enc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/*! \brief Indicates bad Base64 character. */
+#define KO 255
+/*! \brief Indicates Base64 padding character. */
+#define PD 64
+
+/*! \brief Transformation and validation table for decoding Base64. */
+const uint8_t base64_dec[256] = {
+ [ 0] = KO, ['+'] = 62, ['V'] = 21, [129] = KO, [172] = KO, [215] = KO,
+ [ 1] = KO, [ 44] = KO, ['W'] = 22, [130] = KO, [173] = KO, [216] = KO,
+ [ 2] = KO, [ 45] = KO, ['X'] = 23, [131] = KO, [174] = KO, [217] = KO,
+ [ 3] = KO, [ 46] = KO, ['Y'] = 24, [132] = KO, [175] = KO, [218] = KO,
+ [ 4] = KO, ['/'] = 63, ['Z'] = 25, [133] = KO, [176] = KO, [219] = KO,
+ [ 5] = KO, ['0'] = 52, [ 91] = KO, [134] = KO, [177] = KO, [220] = KO,
+ [ 6] = KO, ['1'] = 53, [ 92] = KO, [135] = KO, [178] = KO, [221] = KO,
+ [ 7] = KO, ['2'] = 54, [ 93] = KO, [136] = KO, [179] = KO, [222] = KO,
+ [ 8] = KO, ['3'] = 55, [ 94] = KO, [137] = KO, [180] = KO, [223] = KO,
+ [ 9] = KO, ['4'] = 56, [ 95] = KO, [138] = KO, [181] = KO, [224] = KO,
+ [ 10] = KO, ['5'] = 57, [ 96] = KO, [139] = KO, [182] = KO, [225] = KO,
+ [ 11] = KO, ['6'] = 58, ['a'] = 26, [140] = KO, [183] = KO, [226] = KO,
+ [ 12] = KO, ['7'] = 59, ['b'] = 27, [141] = KO, [184] = KO, [227] = KO,
+ [ 13] = KO, ['8'] = 60, ['c'] = 28, [142] = KO, [185] = KO, [228] = KO,
+ [ 14] = KO, ['9'] = 61, ['d'] = 29, [143] = KO, [186] = KO, [229] = KO,
+ [ 15] = KO, [ 58] = KO, ['e'] = 30, [144] = KO, [187] = KO, [230] = KO,
+ [ 16] = KO, [ 59] = KO, ['f'] = 31, [145] = KO, [188] = KO, [231] = KO,
+ [ 17] = KO, [ 60] = KO, ['g'] = 32, [146] = KO, [189] = KO, [232] = KO,
+ [ 18] = KO, ['='] = PD, ['h'] = 33, [147] = KO, [190] = KO, [233] = KO,
+ [ 19] = KO, [ 62] = KO, ['i'] = 34, [148] = KO, [191] = KO, [234] = KO,
+ [ 20] = KO, [ 63] = KO, ['j'] = 35, [149] = KO, [192] = KO, [235] = KO,
+ [ 21] = KO, [ 64] = KO, ['k'] = 36, [150] = KO, [193] = KO, [236] = KO,
+ [ 22] = KO, ['A'] = 0, ['l'] = 37, [151] = KO, [194] = KO, [237] = KO,
+ [ 23] = KO, ['B'] = 1, ['m'] = 38, [152] = KO, [195] = KO, [238] = KO,
+ [ 24] = KO, ['C'] = 2, ['n'] = 39, [153] = KO, [196] = KO, [239] = KO,
+ [ 25] = KO, ['D'] = 3, ['o'] = 40, [154] = KO, [197] = KO, [240] = KO,
+ [ 26] = KO, ['E'] = 4, ['p'] = 41, [155] = KO, [198] = KO, [241] = KO,
+ [ 27] = KO, ['F'] = 5, ['q'] = 42, [156] = KO, [199] = KO, [242] = KO,
+ [ 28] = KO, ['G'] = 6, ['r'] = 43, [157] = KO, [200] = KO, [243] = KO,
+ [ 29] = KO, ['H'] = 7, ['s'] = 44, [158] = KO, [201] = KO, [244] = KO,
+ [ 30] = KO, ['I'] = 8, ['t'] = 45, [159] = KO, [202] = KO, [245] = KO,
+ [ 31] = KO, ['J'] = 9, ['u'] = 46, [160] = KO, [203] = KO, [246] = KO,
+ [ 32] = KO, ['K'] = 10, ['v'] = 47, [161] = KO, [204] = KO, [247] = KO,
+ [ 33] = KO, ['L'] = 11, ['w'] = 48, [162] = KO, [205] = KO, [248] = KO,
+ [ 34] = KO, ['M'] = 12, ['x'] = 49, [163] = KO, [206] = KO, [249] = KO,
+ [ 35] = KO, ['N'] = 13, ['y'] = 50, [164] = KO, [207] = KO, [250] = KO,
+ [ 36] = KO, ['O'] = 14, ['z'] = 51, [165] = KO, [208] = KO, [251] = KO,
+ [ 37] = KO, ['P'] = 15, [123] = KO, [166] = KO, [209] = KO, [252] = KO,
+ [ 38] = KO, ['Q'] = 16, [124] = KO, [167] = KO, [210] = KO, [253] = KO,
+ [ 39] = KO, ['R'] = 17, [125] = KO, [168] = KO, [211] = KO, [254] = KO,
+ [ 40] = KO, ['S'] = 18, [126] = KO, [169] = KO, [212] = KO, [255] = KO,
+ [ 41] = KO, ['T'] = 19, [127] = KO, [170] = KO, [213] = KO,
+ [ 42] = KO, ['U'] = 20, [128] = KO, [171] = KO, [214] = KO,
+};
-/* C89 compliant way to cast 'char' to 'unsigned char'. */
-static inline unsigned char
-to_uchar (char ch)
+int32_t base64_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
{
- return ch;
-}
+ uint8_t rest_len = in_len % 3;
+ const uint8_t *data = in;
+ const uint8_t *stop = in + in_len - rest_len;
+ uint8_t *text = out;
+ uint8_t num;
+
+ // Checking inputs.
+ if (in == NULL || out == NULL || in_len > MAX_BIN_DATA_LEN ||
+ out_len < ((in_len + 2) / 3) * 4) {
+ return -1;
+ }
+
+ // Encoding loop takes 3 bytes and creates 4 characters.
+ while (data < stop) {
+ // Computing 1. Base64 character.
+ num = *data >> 2;
+ *text++ = base64_enc[num];
+
+ // Computing 2. Base64 character.
+ num = (*data++ & 0x03) << 4;
+ num += *data >> 4;
+ *text++ = base64_enc[num];
+
+ // Computing 3. Base64 character.
+ num = (*data++ & 0x0F) << 2;
+ num += *data >> 6;
+ *text++ = base64_enc[num];
+
+ // Computing 4. Base64 character.
+ num = *data++ & 0x3F;
+ *text++ = base64_enc[num];
+ }
+
+ // Processing of padding, if any.
+ switch (rest_len) {
+ // Input data has 2-byte last block => 1-char padding.
+ case 2:
+ // Computing 1. Base64 character.
+ num = *data >> 2;
+ *text++ = base64_enc[num];
+
+ // Computing 2. Base64 character.
+ num = (*data++ & 0x03) << 4;
+ num += *data >> 4;
+ *text++ = base64_enc[num];
+
+ // Computing 3. Base64 character.
+ num = (*data++ & 0x0F) << 2;
+ *text++ = base64_enc[num];
+
+ // 1 padding character.
+ *text++ = base64_pad;
-/* Base64 encode IN array of size INLEN into OUT array of size OUTLEN.
- If OUTLEN is less than BASE64_LENGTH(INLEN), write as many bytes as
- possible. If OUTLEN is larger than BASE64_LENGTH(INLEN), also zero
- terminate the output buffer. */
-void
-base64_encode (const char *restrict in, size_t inlen,
- char *restrict out, size_t outlen)
-{
- static const char b64str[64] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
-
- while (inlen && outlen)
- {
- *out++ = b64str[(to_uchar (in[0]) >> 2) & 0x3f];
- if (!--outlen)
- break;
- *out++ = b64str[((to_uchar (in[0]) << 4)
- + (--inlen ? to_uchar (in[1]) >> 4 : 0))
- & 0x3f];
- if (!--outlen)
- break;
- *out++ =
- (inlen
- ? b64str[((to_uchar (in[1]) << 2)
- + (--inlen ? to_uchar (in[2]) >> 6 : 0))
- & 0x3f]
- : '=');
- if (!--outlen)
- break;
- *out++ = inlen ? b64str[to_uchar (in[2]) & 0x3f] : '=';
- if (!--outlen)
- break;
- if (inlen)
- inlen--;
- if (inlen)
- in += 3;
- }
-
- if (outlen)
- *out = '\0';
-}
+ break;
+ // Input data has 1-byte last block => 2-char padding.
+ case 1:
+ // Computing 1. Base64 character.
+ num = *data >> 2;
+ *text++ = base64_enc[num];
-/* Allocate a buffer and store zero terminated base64 encoded data
- from array IN of size INLEN, returning BASE64_LENGTH(INLEN), i.e.,
- the length of the encoded data, excluding the terminating zero. On
- return, the OUT variable will hold a pointer to newly allocated
- memory that must be deallocated by the caller. If output string
- length would overflow, 0 is returned and OUT is set to NULL. If
- memory allocation failed, OUT is set to NULL, and the return value
- indicates length of the requested memory block, i.e.,
- BASE64_LENGTH(inlen) + 1. */
-size_t
-base64_encode_alloc (const char *in, size_t inlen, char **out)
-{
- size_t outlen = 1 + BASE64_LENGTH (inlen);
-
- /* Check for overflow in outlen computation.
- *
- * If there is no overflow, outlen >= inlen.
- *
- * If the operation (inlen + 2) overflows then it yields at most +1, so
- * outlen is 0.
- *
- * If the multiplication overflows, we lose at least half of the
- * correct value, so the result is < ((inlen + 2) / 3) * 2, which is
- * less than (inlen + 2) * 0.66667, which is less than inlen as soon as
- * (inlen > 4).
- */
- if (inlen > outlen)
- {
- *out = NULL;
- return 0;
- }
-
- *out = malloc (outlen);
- if (!*out)
- return outlen;
-
- base64_encode (in, inlen, *out, outlen);
-
- return outlen - 1;
-}
+ // Computing 2. Base64 character.
+ num = (*data++ & 0x03) << 4;
+ *text++ = base64_enc[num];
-/* With this approach this file works independent of the charset used
- (think EBCDIC). However, it does assume that the characters in the
- Base64 alphabet (A-Za-z0-9+/) are encoded in 0..255. POSIX
- 1003.1-2001 require that char and unsigned char are 8-bit
- quantities, though, taking care of that problem. But this may be a
- potential problem on non-POSIX C99 platforms.
-
- IBM C V6 for AIX mishandles "#define B64(x) ...'x'...", so use "_"
- as the formal parameter rather than "x". */
-#define B64(_) \
- ((_) == 'A' ? 0 \
- : (_) == 'B' ? 1 \
- : (_) == 'C' ? 2 \
- : (_) == 'D' ? 3 \
- : (_) == 'E' ? 4 \
- : (_) == 'F' ? 5 \
- : (_) == 'G' ? 6 \
- : (_) == 'H' ? 7 \
- : (_) == 'I' ? 8 \
- : (_) == 'J' ? 9 \
- : (_) == 'K' ? 10 \
- : (_) == 'L' ? 11 \
- : (_) == 'M' ? 12 \
- : (_) == 'N' ? 13 \
- : (_) == 'O' ? 14 \
- : (_) == 'P' ? 15 \
- : (_) == 'Q' ? 16 \
- : (_) == 'R' ? 17 \
- : (_) == 'S' ? 18 \
- : (_) == 'T' ? 19 \
- : (_) == 'U' ? 20 \
- : (_) == 'V' ? 21 \
- : (_) == 'W' ? 22 \
- : (_) == 'X' ? 23 \
- : (_) == 'Y' ? 24 \
- : (_) == 'Z' ? 25 \
- : (_) == 'a' ? 26 \
- : (_) == 'b' ? 27 \
- : (_) == 'c' ? 28 \
- : (_) == 'd' ? 29 \
- : (_) == 'e' ? 30 \
- : (_) == 'f' ? 31 \
- : (_) == 'g' ? 32 \
- : (_) == 'h' ? 33 \
- : (_) == 'i' ? 34 \
- : (_) == 'j' ? 35 \
- : (_) == 'k' ? 36 \
- : (_) == 'l' ? 37 \
- : (_) == 'm' ? 38 \
- : (_) == 'n' ? 39 \
- : (_) == 'o' ? 40 \
- : (_) == 'p' ? 41 \
- : (_) == 'q' ? 42 \
- : (_) == 'r' ? 43 \
- : (_) == 's' ? 44 \
- : (_) == 't' ? 45 \
- : (_) == 'u' ? 46 \
- : (_) == 'v' ? 47 \
- : (_) == 'w' ? 48 \
- : (_) == 'x' ? 49 \
- : (_) == 'y' ? 50 \
- : (_) == 'z' ? 51 \
- : (_) == '0' ? 52 \
- : (_) == '1' ? 53 \
- : (_) == '2' ? 54 \
- : (_) == '3' ? 55 \
- : (_) == '4' ? 56 \
- : (_) == '5' ? 57 \
- : (_) == '6' ? 58 \
- : (_) == '7' ? 59 \
- : (_) == '8' ? 60 \
- : (_) == '9' ? 61 \
- : (_) == '+' ? 62 \
- : (_) == '/' ? 63 \
- : -1)
-
-static const signed char b64[0x100] = {
- B64 (0), B64 (1), B64 (2), B64 (3),
- B64 (4), B64 (5), B64 (6), B64 (7),
- B64 (8), B64 (9), B64 (10), B64 (11),
- B64 (12), B64 (13), B64 (14), B64 (15),
- B64 (16), B64 (17), B64 (18), B64 (19),
- B64 (20), B64 (21), B64 (22), B64 (23),
- B64 (24), B64 (25), B64 (26), B64 (27),
- B64 (28), B64 (29), B64 (30), B64 (31),
- B64 (32), B64 (33), B64 (34), B64 (35),
- B64 (36), B64 (37), B64 (38), B64 (39),
- B64 (40), B64 (41), B64 (42), B64 (43),
- B64 (44), B64 (45), B64 (46), B64 (47),
- B64 (48), B64 (49), B64 (50), B64 (51),
- B64 (52), B64 (53), B64 (54), B64 (55),
- B64 (56), B64 (57), B64 (58), B64 (59),
- B64 (60), B64 (61), B64 (62), B64 (63),
- B64 (64), B64 (65), B64 (66), B64 (67),
- B64 (68), B64 (69), B64 (70), B64 (71),
- B64 (72), B64 (73), B64 (74), B64 (75),
- B64 (76), B64 (77), B64 (78), B64 (79),
- B64 (80), B64 (81), B64 (82), B64 (83),
- B64 (84), B64 (85), B64 (86), B64 (87),
- B64 (88), B64 (89), B64 (90), B64 (91),
- B64 (92), B64 (93), B64 (94), B64 (95),
- B64 (96), B64 (97), B64 (98), B64 (99),
- B64 (100), B64 (101), B64 (102), B64 (103),
- B64 (104), B64 (105), B64 (106), B64 (107),
- B64 (108), B64 (109), B64 (110), B64 (111),
- B64 (112), B64 (113), B64 (114), B64 (115),
- B64 (116), B64 (117), B64 (118), B64 (119),
- B64 (120), B64 (121), B64 (122), B64 (123),
- B64 (124), B64 (125), B64 (126), B64 (127),
- B64 (128), B64 (129), B64 (130), B64 (131),
- B64 (132), B64 (133), B64 (134), B64 (135),
- B64 (136), B64 (137), B64 (138), B64 (139),
- B64 (140), B64 (141), B64 (142), B64 (143),
- B64 (144), B64 (145), B64 (146), B64 (147),
- B64 (148), B64 (149), B64 (150), B64 (151),
- B64 (152), B64 (153), B64 (154), B64 (155),
- B64 (156), B64 (157), B64 (158), B64 (159),
- B64 (160), B64 (161), B64 (162), B64 (163),
- B64 (164), B64 (165), B64 (166), B64 (167),
- B64 (168), B64 (169), B64 (170), B64 (171),
- B64 (172), B64 (173), B64 (174), B64 (175),
- B64 (176), B64 (177), B64 (178), B64 (179),
- B64 (180), B64 (181), B64 (182), B64 (183),
- B64 (184), B64 (185), B64 (186), B64 (187),
- B64 (188), B64 (189), B64 (190), B64 (191),
- B64 (192), B64 (193), B64 (194), B64 (195),
- B64 (196), B64 (197), B64 (198), B64 (199),
- B64 (200), B64 (201), B64 (202), B64 (203),
- B64 (204), B64 (205), B64 (206), B64 (207),
- B64 (208), B64 (209), B64 (210), B64 (211),
- B64 (212), B64 (213), B64 (214), B64 (215),
- B64 (216), B64 (217), B64 (218), B64 (219),
- B64 (220), B64 (221), B64 (222), B64 (223),
- B64 (224), B64 (225), B64 (226), B64 (227),
- B64 (228), B64 (229), B64 (230), B64 (231),
- B64 (232), B64 (233), B64 (234), B64 (235),
- B64 (236), B64 (237), B64 (238), B64 (239),
- B64 (240), B64 (241), B64 (242), B64 (243),
- B64 (244), B64 (245), B64 (246), B64 (247),
- B64 (248), B64 (249), B64 (250), B64 (251),
- B64 (252), B64 (253), B64 (254), B64 (255)
-};
+ // 2 padding character.
+ *text++ = base64_pad;
+ *text++ = base64_pad;
-#if UCHAR_MAX == 255
-# define uchar_in_range(c) true
-#else
-# define uchar_in_range(c) ((c) <= 255)
-#endif
-
-/* Return true if CH is a character from the Base64 alphabet, and
- false otherwise. Note that '=' is padding and not considered to be
- part of the alphabet. */
-bool
-isbase64 (char ch)
-{
- return uchar_in_range (to_uchar (ch)) && 0 <= b64[to_uchar (ch)];
-}
+ break;
+ }
-/* Initialize decode-context buffer, CTX. */
-void
-base64_decode_ctx_init (struct base64_decode_context *ctx)
-{
- ctx->i = 0;
+ return (text - out);
}
-/* If CTX->i is 0 or 4, there are four or more bytes in [*IN..IN_END), and
- none of those four is a newline, then return *IN. Otherwise, copy up to
- 4 - CTX->i non-newline bytes from that range into CTX->buf, starting at
- index CTX->i and setting CTX->i to reflect the number of bytes copied,
- and return CTX->buf. In either case, advance *IN to point to the byte
- after the last one processed, and set *N_NON_NEWLINE to the number of
- verified non-newline bytes accessible through the returned pointer. */
-static inline char *
-get_4 (struct base64_decode_context *ctx,
- char const *restrict *in, char const *restrict in_end,
- size_t *n_non_newline)
+int32_t base64_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
{
- if (ctx->i == 4)
- ctx->i = 0;
-
- if (ctx->i == 0)
- {
- char const *t = *in;
- if (4 <= in_end - *in && memchr (t, '\n', 4) == NULL)
- {
- /* This is the common case: no newline. */
- *in += 4;
- *n_non_newline = 4;
- return (char *) t;
- }
- }
-
- {
- /* Copy non-newline bytes into BUF. */
- char const *p = *in;
- while (p < in_end)
- {
- char c = *p++;
- if (c != '\n')
- {
- ctx->buf[ctx->i++] = c;
- if (ctx->i == 4)
- break;
- }
- }
-
- *in = p;
- *n_non_newline = ctx->i;
- return ctx->buf;
- }
+ uint32_t out_len = ((in_len + 2) / 3) * 4;
+
+ // Checking inputs.
+ if (in_len > MAX_BIN_DATA_LEN) {
+ return -1;
+ }
+
+ // Allocating output buffer.
+ *out = malloc(out_len);
+
+ if (*out == NULL) {
+ return -1;
+ }
+
+ // Encoding data.
+ return base64_encode(in, in_len, *out, out_len);
}
-#define return_false \
- do \
- { \
- *outp = out; \
- return false; \
- } \
- while (false)
-
-/* Decode up to four bytes of base64-encoded data, IN, of length INLEN
- into the output buffer, *OUT, of size *OUTLEN bytes. Return true if
- decoding is successful, false otherwise. If *OUTLEN is too small,
- as many bytes as possible are written to *OUT. On return, advance
- *OUT to point to the byte after the last one written, and decrement
- *OUTLEN to reflect the number of bytes remaining in *OUT. */
-static inline bool
-decode_4 (char const *restrict in, size_t inlen,
- char *restrict *outp, size_t *outleft)
+int32_t base64_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len)
{
- char *out = *outp;
- if (inlen < 2)
- return false;
-
- if (!isbase64 (in[0]) || !isbase64 (in[1]))
- return false;
-
- if (*outleft)
- {
- *out++ = ((b64[to_uchar (in[0])] << 2)
- | (b64[to_uchar (in[1])] >> 4));
- --*outleft;
- }
-
- if (inlen == 2)
- return_false;
-
- if (in[2] == '=')
- {
- if (inlen != 4)
- return_false;
-
- if (in[3] != '=')
- return_false;
- }
- else
- {
- if (!isbase64 (in[2]))
- return_false;
-
- if (*outleft)
- {
- *out++ = (((b64[to_uchar (in[1])] << 4) & 0xf0)
- | (b64[to_uchar (in[2])] >> 2));
- --*outleft;
- }
-
- if (inlen == 3)
- return_false;
-
- if (in[3] == '=')
- {
- if (inlen != 4)
- return_false;
- }
- else
- {
- if (!isbase64 (in[3]))
- return_false;
-
- if (*outleft)
- {
- *out++ = (((b64[to_uchar (in[2])] << 6) & 0xc0)
- | b64[to_uchar (in[3])]);
- --*outleft;
- }
- }
- }
-
- *outp = out;
- return true;
+ const uint8_t *data = in;
+ const uint8_t *stop = in + in_len;
+ uint8_t *bin = out;
+ uint8_t pad_len = 0;
+ uint8_t c1, c2, c3, c4;
+
+ // Checking inputs.
+ if (in == NULL || out == NULL || (in_len % 4) != 0 ||
+ in_len > INT32_MAX || out_len < ((in_len + 3) / 4) * 3) {
+ return -1;
+ }
+
+ // Decoding loop takes 4 characters and creates 3 bytes.
+ while (data < stop) {
+ // Filling and transforming 4 Base64 chars.
+ c1 = base64_dec[*data++];
+ c2 = base64_dec[*data++];
+ c3 = base64_dec[*data++];
+ c4 = base64_dec[*data++];
+
+ // Check 4. char if is bad or padding.
+ if (c4 >= PD) {
+ if (c4 == PD) {
+ pad_len = 1;
+ } else {
+ return -2;
+ }
+ }
+
+ // Check 3. char if is bad or padding.
+ if (c3 >= PD) {
+ if (c3 == PD) {
+ pad_len = 2;
+ } else {
+ return -2;
+ }
+ }
+
+ // 1. and 2. chars must not be padding.
+ if (c2 >= PD || c1 >= PD) {
+ return -2;
+ }
+
+ // Computing of output data based on padding length.
+ switch (pad_len) {
+ // No padding => output has 3 bytess.
+ case 0:
+ *bin++ = (c1 << 2) + (c2 >> 4);
+ *bin++ = (c2 << 4) + (c3 >> 2);
+ *bin++ = (c3 << 6) + c4;
+ break;
+ // 1-char padding => output has 2 bytes.
+ case 1:
+ *bin++ = (c1 << 2) + (c2 >> 4);
+ *bin++ = (c2 << 4) + (c3 >> 2);
+ break;
+ // 2-char padding => output has 1 bytes.
+ case 2:
+ *bin++ = (c1 << 2) + (c2 >> 4);
+ break;
+ }
+ }
+
+ return (bin - out);
}
-/* Decode base64-encoded input array IN of length INLEN to output array
- OUT that can hold *OUTLEN bytes. The input data may be interspersed
- with newlines. Return true if decoding was successful, i.e. if the
- input was valid base64 data, false otherwise. If *OUTLEN is too
- small, as many bytes as possible will be written to OUT. On return,
- *OUTLEN holds the length of decoded bytes in OUT. Note that as soon
- as any non-alphabet, non-newline character is encountered, decoding
- is stopped and false is returned. If INLEN is zero, then process
- only whatever data is stored in CTX.
-
- Initially, CTX must have been initialized via base64_decode_ctx_init.
- Subsequent calls to this function must reuse whatever state is recorded
- in that buffer. It is necessary for when a quadruple of base64 input
- bytes spans two input buffers.
-
- If CTX is NULL then newlines are treated as garbage and the input
- buffer is processed as a unit. */
-
-bool
-base64_decode_ctx (struct base64_decode_context *ctx,
- const char *restrict in, size_t inlen,
- char *restrict out, size_t *outlen)
+int32_t base64_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out)
{
- size_t outleft = *outlen;
- bool ignore_newlines = ctx != NULL;
- bool flush_ctx = false;
- unsigned int ctx_i = 0;
-
- if (ignore_newlines)
- {
- ctx_i = ctx->i;
- flush_ctx = inlen == 0;
- }
-
-
- while (true)
- {
- size_t outleft_save = outleft;
- if (ctx_i == 0 && !flush_ctx)
- {
- while (true)
- {
- /* Save a copy of outleft, in case we need to re-parse this
- block of four bytes. */
- outleft_save = outleft;
- if (!decode_4 (in, inlen, &out, &outleft))
- break;
+ uint32_t out_len = ((in_len + 3) / 4) * 3;
- in += 4;
- inlen -= 4;
- }
- }
-
- if (inlen == 0 && !flush_ctx)
- break;
-
- /* Handle the common case of 72-byte wrapped lines.
- This also handles any other multiple-of-4-byte wrapping. */
- if (inlen && *in == '\n' && ignore_newlines)
- {
- ++in;
- --inlen;
- continue;
- }
-
- /* Restore OUT and OUTLEFT. */
- out -= outleft_save - outleft;
- outleft = outleft_save;
-
- {
- char const *in_end = in + inlen;
- char const *non_nl;
-
- if (ignore_newlines)
- non_nl = get_4 (ctx, &in, in_end, &inlen);
- else
- non_nl = in; /* Might have nl in this case. */
-
- /* If the input is empty or consists solely of newlines (0 non-newlines),
- then we're done. Likewise if there are fewer than 4 bytes when not
- flushing context and not treating newlines as garbage. */
- if (inlen == 0 || (inlen < 4 && !flush_ctx && ignore_newlines))
- {
- inlen = 0;
- break;
- }
- if (!decode_4 (non_nl, inlen, &out, &outleft))
- break;
-
- inlen = in_end - in;
- }
- }
-
- *outlen -= outleft;
-
- return inlen == 0;
-}
+ *out = malloc(out_len);
-/* Allocate an output buffer in *OUT, and decode the base64 encoded
- data stored in IN of size INLEN to the *OUT buffer. On return, the
- size of the decoded data is stored in *OUTLEN. OUTLEN may be NULL,
- if the caller is not interested in the decoded length. *OUT may be
- NULL to indicate an out of memory error, in which case *OUTLEN
- contains the size of the memory block needed. The function returns
- true on successful decoding and memory allocation errors. (Use the
- *OUT and *OUTLEN parameters to differentiate between successful
- decoding and memory error.) The function returns false if the
- input was invalid, in which case *OUT is NULL and *OUTLEN is
- undefined. */
-bool
-base64_decode_alloc_ctx (struct base64_decode_context *ctx,
- const char *in, size_t inlen, char **out,
- size_t *outlen)
-{
- /* This may allocate a few bytes too many, depending on input,
- but it's not worth the extra CPU time to compute the exact size.
- The exact size is 3 * (inlen + (ctx ? ctx->i : 0)) / 4, minus 1 if the
- input ends with "=" and minus another 1 if the input ends with "==".
- Dividing before multiplying avoids the possibility of overflow. */
- size_t needlen = 3 * (inlen / 4) + 3;
-
- *out = malloc (needlen);
- if (!*out)
- return true;
-
- if (!base64_decode_ctx (ctx, in, inlen, *out, &needlen))
- {
- free (*out);
- *out = NULL;
- return false;
- }
-
- if (outlen)
- *outlen = needlen;
-
- return true;
+ // Allocating output buffer.
+ if (*out == NULL) {
+ return -1;
+ }
+
+ // Decoding data.
+ return base64_decode(in, in_len, *out, out_len);
}
+
diff --git a/src/common/base64.h b/src/common/base64.h
index aa0b696..8f99891 100644
--- a/src/common/base64.h
+++ b/src/common/base64.h
@@ -1,60 +1,114 @@
-/* base64.h -- Encode binary data using printable characters.
- Copyright (C) 2004-2006, 2009-2012 Free Software Foundation, Inc.
- Written by Simon Josefsson.
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1, 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 Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with this program; if not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef BASE64_H
-# define BASE64_H
-
-/* Get size_t. */
-# include <stddef.h>
-
-/* Get bool. */
-# include <stdbool.h>
-
-/* This uses that the expression (n+(k-1))/k means the smallest
- integer >= n/k, i.e., the ceiling of n/k. */
-# define BASE64_LENGTH(inlen) ((((inlen) + 2) / 3) * 4)
-
-struct base64_decode_context
-{
- unsigned int i;
- char buf[4];
-};
-
-extern bool isbase64 (char ch);
-
-extern void base64_encode (const char *restrict in, size_t inlen,
- char *restrict out, size_t outlen);
-
-extern size_t base64_encode_alloc (const char *in, size_t inlen, char **out);
-
-extern void base64_decode_ctx_init (struct base64_decode_context *ctx);
-
-extern bool base64_decode_ctx (struct base64_decode_context *ctx,
- const char *restrict in, size_t inlen,
- char *restrict out, size_t *outlen);
-
-extern bool base64_decode_alloc_ctx (struct base64_decode_context *ctx,
- const char *in, size_t inlen,
- char **out, size_t *outlen);
-
-#define base64_decode(in, inlen, out, outlen) \
- base64_decode_ctx (NULL, in, inlen, out, outlen)
-
-#define base64_decode_alloc(in, inlen, out, outlen) \
- base64_decode_alloc_ctx (NULL, in, inlen, out, outlen)
-
-#endif /* BASE64_H */
+/* Copyright (C) 2011 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/>.
+ */
+/*!
+ * \file base64.h
+ *
+ * \author Daniel Salzman <daniel.salzman@nic.cz>
+ *
+ * \brief Base64 implementation (RFC 4648).
+ *
+ * \addtogroup common_lib
+ * @{
+ */
+
+#ifndef _KNOTD_COMMON__BASE64_H_
+#define _KNOTD_COMMON__BASE64_H_
+
+#include <stdint.h> // uint8_t
+
+/*!
+ * \brief Encodes binary data using Base64.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval -1 if error.
+ */
+int32_t base64_encode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Encodes binary data using Base64 and output stores to own buffer.
+ *
+ * \note Output data buffer contains Base64 text string which isn't
+ * terminated with '\0'!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input binary data.
+ * \param in_len Length of input data.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output string.
+ * \retval -1 if error.
+ */
+int32_t base64_encode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+/*!
+ * \brief Decodes text data using Base64.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ * \param out_len Size of output buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval -1 if error.
+ * \retval -2 if bad input data.
+ */
+int32_t base64_decode(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t *out,
+ const uint32_t out_len);
+
+/*!
+ * \brief Decodes text data using Base64 and output stores to own buffer.
+ *
+ * \note Input data needn't be terminated with '\0'.
+ *
+ * \note Input data must be continuous Base64 string!
+ *
+ * \note Output buffer should be deallocated after use.
+ *
+ * \param in Input text data.
+ * \param in_len Length of input string.
+ * \param out Output data buffer.
+ *
+ * \retval >=0 length of output data.
+ * \retval -1 if error.
+ * \retval -2 if bad input data.
+ */
+int32_t base64_decode_alloc(const uint8_t *in,
+ const uint32_t in_len,
+ uint8_t **out);
+
+#endif // _KNOTD_COMMON__BASE64_H_
+
+/*! @} */
diff --git a/src/common/errcode.c b/src/common/errcode.c
index 9f5aff3..ff9be63 100644
--- a/src/common/errcode.c
+++ b/src/common/errcode.c
@@ -69,6 +69,7 @@ const error_table_t knot_error_msgs[] = {
{KNOT_EIXFRSPACE, "IXFR reply did not fit in."},
{KNOT_ECNAME, "CNAME loop found in zone."},
{KNOT_ENODIFF, "Cannot create zone diff."},
+ {KNOT_EDSDIGESTLEN, "DS digest length does not match digest type." },
{KNOT_ERROR, 0}
};
diff --git a/src/common/errcode.h b/src/common/errcode.h
index 50535ad..0693a0d 100644
--- a/src/common/errcode.h
+++ b/src/common/errcode.h
@@ -82,7 +82,8 @@ enum knot_error {
KNOT_ECONN, /*!< Connection reset. */
KNOT_EIXFRSPACE, /*!< IXFR reply did not fit in. */
KNOT_ECNAME, /*!< CNAME loop found in zone. */
- KNOT_ENODIFF /*!< No zone diff can be created. */
+ KNOT_ENODIFF, /*!< No zone diff can be created. */
+ KNOT_EDSDIGESTLEN /*!< DS digest length does not match digest type. */
};
/*! \brief Table linking error messages to error codes. */
diff --git a/src/common/evqueue.c b/src/common/evqueue.c
index 240ced6..efd6d4d 100644
--- a/src/common/evqueue.c
+++ b/src/common/evqueue.c
@@ -19,84 +19,8 @@
#include <unistd.h>
#include <config.h>
-/* OpenBSD compatibility. */
-#ifndef HAVE_PSELECT
-/*
- * Like select(2) but set the signals to block while waiting in
- * select. This version is not entirely race condition safe. Only
- * operating system support can make it so.
- *
- * Copyright (c) 2001-2011, NLnet Labs. All rights reserved.
- *
- * This software is open source.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * Neither the name of the NLNET LABS nor the names of its contributors may
- * be used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
- * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-#include <sys/time.h>
-#include <sys/types.h>
-#ifdef HAVE_SYS_SELECT_H
-#include <sys/select.h>
-#endif
-#include <unistd.h>
-#include <signal.h>
-
-static int
-pselect (int n,
- fd_set *readfds,
- fd_set *writefds,
- fd_set *exceptfds,
- const struct timespec *timeout,
- const sigset_t *sigmask)
-{
- int result;
- sigset_t saved_sigmask;
- struct timeval saved_timeout;
-
- if (sigmask && sigprocmask(SIG_SETMASK, sigmask, &saved_sigmask) == -1)
- return -1;
-
- if (timeout) {
- saved_timeout.tv_sec = timeout->tv_sec;
- saved_timeout.tv_usec = timeout->tv_nsec / 1000;
- result = select(n, readfds, writefds, exceptfds, &saved_timeout);
- } else {
- result = select(n, readfds, writefds, exceptfds, NULL);
- }
-
- if (sigmask && sigprocmask(SIG_SETMASK, &saved_sigmask, NULL) == -1)
- return -1;
-
- return result;
-}
-
-#endif
-
#include "common/evqueue.h"
+#include "common/fdset.h"
/*! \brief Singleton application-wide event queue. */
evqueue_t *s_evqueue = 0;
@@ -148,7 +72,8 @@ int evqueue_poll(evqueue_t *q, const struct timespec *ts,
FD_SET(q->fds[EVQUEUE_READFD], &rfds);
/* Wait for events. */
- int ret = pselect(q->fds[EVQUEUE_READFD] + 1, &rfds, 0, 0, ts, sigmask);
+ int ret = fdset_pselect(q->fds[EVQUEUE_READFD] + 1, &rfds,
+ 0, 0, ts, sigmask);
if (ret < 0) {
return -1;
}
diff --git a/src/common/evsched.c b/src/common/evsched.c
index 9bfdef0..5e2073a 100644
--- a/src/common/evsched.c
+++ b/src/common/evsched.c
@@ -72,6 +72,7 @@ evsched_t *evsched_new()
if (!s) {
return 0;
}
+ memset(s, 0, sizeof(evsched_t));
/* Initialize event calendar. */
pthread_mutex_init(&s->rl, 0);
@@ -183,10 +184,18 @@ event_t* evsched_next(evsched_t *s)
/* Immediately return. */
if (timercmp_ge(&dt, &next_ev->tv)) {
- s->current = next_ev;
- heap_delmin(&s->heap);
+ s->cur = next_ev;
+ heap_delmin(&s->heap);
pthread_mutex_unlock(&s->mx);
pthread_mutex_lock(&s->rl);
+
+ /* Check back for late cancellation. */
+ if (s->cur == NULL) {
+ pthread_mutex_unlock(&s->rl);
+ pthread_mutex_lock(&s->mx);
+ continue;
+ }
+
return next_ev;
}
@@ -215,8 +224,8 @@ int evsched_event_finished(evsched_t *s)
}
/* Mark as finished. */
- if (s->current) {
- s->current = 0;
+ if (s->cur) {
+ s->cur = NULL;
pthread_mutex_unlock(&s->rl);
return 0;
}
@@ -308,6 +317,15 @@ int evsched_cancel(evsched_t *s, event_t *ev)
if ((found = heap_find(&s->heap, ev))) {
heap_delete(&s->heap, found);
}
+
+ /* Check if not being processed, invalidate if yes.
+ * Could happen if next 'cur' was set, but
+ * the evsched_next() waits until we release rl.
+ */
+ if (s->cur == ev) {
+ s->cur = NULL; /* Invalidate */
+ found = 1; /* Mark as found (although not in heap). */
+ }
/* Unlock calendar. */
pthread_cond_broadcast(&s->notify);
diff --git a/src/common/evsched.h b/src/common/evsched.h
index 47bf672..9078749 100644
--- a/src/common/evsched.h
+++ b/src/common/evsched.h
@@ -89,7 +89,7 @@ typedef enum evsched_ev_t {
*/
typedef struct {
pthread_mutex_t rl; /*!< Event running lock. */
- event_t *current; /*!< Current running event. */
+ volatile event_t *cur; /*!< Current running event. */
pthread_mutex_t mx; /*!< Event queue locking. */
pthread_cond_t notify; /*!< Event queue notification. */
struct heap heap;
diff --git a/src/common/fdset.c b/src/common/fdset.c
index c915e01..6c5f0d3 100644
--- a/src/common/fdset.c
+++ b/src/common/fdset.c
@@ -203,3 +203,83 @@ int fdset_sweep(fdset_t* fdset, void(*cb)(fdset_t*, int, void*), void *data)
return sweeped;
}
+
+/* OpenBSD compatibility. */
+#ifndef HAVE_PSELECT
+/*
+ * Like select(2) but set the signals to block while waiting in
+ * select. This version is not entirely race condition safe. Only
+ * operating system support can make it so.
+ *
+ * Copyright (c) 2001-2011, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <signal.h>
+
+static int
+pselect (int n,
+ fd_set *readfds,
+ fd_set *writefds,
+ fd_set *exceptfds,
+ const struct timespec *timeout,
+ const sigset_t *sigmask)
+{
+ int result;
+ sigset_t saved_sigmask;
+ struct timeval saved_timeout;
+
+ if (sigmask && sigprocmask(SIG_SETMASK, sigmask, &saved_sigmask) == -1)
+ return -1;
+
+ if (timeout) {
+ saved_timeout.tv_sec = timeout->tv_sec;
+ saved_timeout.tv_usec = timeout->tv_nsec / 1000;
+ result = select(n, readfds, writefds, exceptfds, &saved_timeout);
+ } else {
+ result = select(n, readfds, writefds, exceptfds, NULL);
+ }
+
+ if (sigmask && sigprocmask(SIG_SETMASK, &saved_sigmask, NULL) == -1)
+ return -1;
+
+ return result;
+}
+
+#endif
+
+int fdset_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
+ const struct timespec *timeout, const sigset_t *sigmask)
+{
+ return pselect(n, readfds, writefds, exceptfds, timeout, sigmask);
+}
diff --git a/src/common/fdset.h b/src/common/fdset.h
index 4038083..12fc5c7 100644
--- a/src/common/fdset.h
+++ b/src/common/fdset.h
@@ -33,6 +33,9 @@
#define _KNOTD_FDSET_H_
#include <stddef.h>
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
#include "skip-list.h"
#include "mempattern.h"
@@ -232,6 +235,20 @@ int fdset_set_watchdog(fdset_t* fdset, int fd, int interval);
*/
int fdset_sweep(fdset_t* fdset, void(*cb)(fdset_t*, int, void*), void *data);
+/*!
+ * \brief pselect(2) compatibility wrapper.
+ * \param n Number of file descriptors.
+ * \param readfds Array of fds to read.
+ * \param writefds Array of fds to write.
+ * \param exceptfds Array of fds for exceptions.
+ * \param timeout Upper bound of time elapsed.
+ * \param sigmask If != NULL, replaces current signal mask.
+ * \return Number of events or -1 if fails.
+ */
+int fdset_pselect(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
+ const struct timespec *timeout, const sigset_t *sigmask);
+
+
#endif /* _KNOTD_FDSET_H_ */
/*! @} */
diff --git a/src/common/general-tree.c b/src/common/general-tree.c
index e1048e7..1edd0f3 100644
--- a/src/common/general-tree.c
+++ b/src/common/general-tree.c
@@ -141,6 +141,7 @@ void gen_tree_clear(general_tree_t *tree)
{
MOD_TREE_DESTROY(tree->tree, general_tree_node, avl, NULL,
gen_rem_func, NULL);
+ tree->tree->th_root = NULL; /* Invalidate tree root. */
}
//static void add_node_to_tree(void *n, void *data)
diff --git a/src/common/mempattern.c b/src/common/mempattern.c
index 5982e18..acf6eac 100644
--- a/src/common/mempattern.c
+++ b/src/common/mempattern.c
@@ -19,6 +19,7 @@
#include <string.h>
#include <sys/resource.h>
#include <config.h>
+#include <stdarg.h>
#include "common/slab/alloc-common.h"
@@ -45,6 +46,44 @@ int mreserve(char **p, size_t tlen, size_t min, size_t allow, size_t *reserved)
return 0;
}
+char* sprintf_alloc(const char *fmt, ...)
+{
+ int size = 100;
+ char *p = NULL, *np = NULL;
+ va_list ap;
+
+ if ((p = malloc(size)) == NULL)
+ return NULL;
+
+ while (1) {
+
+ /* Try to print in the allocated space. */
+ va_start(ap, fmt);
+ int n = vsnprintf(p, size, fmt, ap);
+ va_end(ap);
+
+ /* If that worked, return the string. */
+ if (n > -1 && n < size)
+ return p;
+
+ /* Else try again with more space. */
+ if (n > -1) { /* glibc 2.1 */
+ size = n+1; /* precisely what is needed */
+ } else { /* glibc 2.0 */
+ size *= 2; /* twice the old size */
+ }
+ if ((np = realloc (p, size)) == NULL) {
+ free(p);
+ return NULL;
+ } else {
+ p = np;
+ }
+ }
+
+ /* Should never get here. */
+ return p;
+}
+
#ifdef MEM_DEBUG
/*
* ((destructor)) attribute executes this function after main().
diff --git a/src/common/mempattern.h b/src/common/mempattern.h
index ae1fa78..ebfe4ae 100644
--- a/src/common/mempattern.h
+++ b/src/common/mempattern.h
@@ -54,6 +54,16 @@
*/
int mreserve(char **p, size_t tlen, size_t min, size_t allow, size_t *reserved);
+/*!
+ * \brief Format string and take care of allocating memory.
+ *
+ * \note sprintf(3) manual page reference implementation.
+ *
+ * \param fmt Message format.
+ * \return formatted message or NULL.
+ */
+char* sprintf_alloc(const char *fmt, ...);
+
/*! \brief Print usage statistics.
*
* \note This function has destructor attribute set if MEM_DEBUG is enabled.
diff --git a/src/config.h.in b/src/config.h.in
index b711d4f..bc3eb0e 100644
--- a/src/config.h.in
+++ b/src/config.h.in
@@ -195,6 +195,9 @@
/* Define to 1 if you have the <sys/param.h> header file. */
#undef HAVE_SYS_PARAM_H
+/* Define to 1 if you have the <sys/select.h> header file. */
+#undef HAVE_SYS_SELECT_H
+
/* Define to 1 if you have the <sys/socket.h> header file. */
#undef HAVE_SYS_SOCKET_H
@@ -207,6 +210,9 @@
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
+/* Define to 1 if you have the <sys/wait.h> header file. */
+#undef HAVE_SYS_WAIT_H
+
/* Define to 1 if you have the <unistd.h> header file. */
#undef HAVE_UNISTD_H
diff --git a/src/knot.conf.5 b/src/knot.conf.5
index 62954e3..f99739c 100644
--- a/src/knot.conf.5
+++ b/src/knot.conf.5
@@ -1,4 +1,4 @@
-.TH "knot.conf" "5" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.1.3"
+.TH "knot.conf" "5" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2"
.SH "NAME"
.LP
.B knot.conf
@@ -11,21 +11,27 @@
serves as an example of the configuration for knotc(8) and knotd(8).
.SH "EXAMPLE"
.LP
-# This is a comment.
+ #
+ # knot.sample.conf
+ #
+ # This is a sample configuration file for Knot DNS server.
+ #
-#
-# There are 4 main sections of this config file:
-# system, zones, interfaces and log
-#
+ # This is a comment.
-# Section 'system' contains general options for the server
-system {
+ #
+ # There are 5 main sections of this config file:
+ # system, zones, interfaces, control and log
+ #
+
+ # Section 'system' contains general options for the server
+ system {
# Identity of the server (see RFC 4892). Not used yet.
identity "I have no mouth and must scream";
# Version of the server (see RFC 4892). Not used yet.
- version "0.1";
+ version "1.2;
# Server identifier
# Use string format "text"
@@ -43,15 +49,34 @@ system {
# Number of workers per interface
# This option is used to force number of threads used per interface
# Default: unset (auto-estimates optimal value from the number of online CPUs)
- # workers 1;
+ # workers 3;
# User for running server
- # May also specify user.group (f.e. knot.users)
+ # May also specify user.group (e.g. knot.users)
# user knot.users;
-}
-# Section 'keys' contains list of TSIG keys
-keys {
+ # Maximum idle time between requests on a TCP connection
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 60s
+ max-conn-idle 60s;
+
+ # Maximum time between newly accepted TCP connection and first query
+ # This is useful to disconnect inactive connections faster
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 10s
+ max-conn-handshake 10s;
+
+ # Maximum time to wait for a reply to SOA query
+ # It is also possible to suffix with unit size [s/m/h/d]
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # Default: 10s
+ max-conn-reply 10s;
+ }
+
+ # Section 'keys' contains list of TSIG keys
+ keys {
# TSIG key
#
@@ -68,10 +93,10 @@ keys {
# TSIG key for zone
key0.example.com hmac-md5 "==gW";
-}
+ }
-# Section 'interfaces' contains definitions of listening interfaces.
-interfaces {
+ # Section 'interfaces' contains definitions of listening interfaces.
+ interfaces {
# Interface entry
#
@@ -96,11 +121,11 @@ interfaces {
# address [::1]@53534;
# }
-}
+ }
-# Section 'remotes' contains symbolic names for remote servers.
-# Syntax for 'remotes' is the same as for 'interfaces'.
-remotes {
+ # Section 'remotes' contains symbolic names for remote servers.
+ # Syntax for 'remotes' is the same as for 'interfaces'.
+ remotes {
# Remote entry
#
@@ -118,15 +143,30 @@ remotes {
server1 {
address 127.0.0.1@53001;
}
-}
+ }
+
+ # Section 'control' specifies on which interface to listen for RC commands
+ control {
+
+ # Specifies interface, syntax is exactly the same as in 'interfaces' section
+ # Note: as of now, it is possible replay commands in a short time frame
+ # with MitM type attacks, so you should keep the interface on localnet.
+ # Default port is: 5553
+ listen-on { address 127.0.0.1@5553; }
+
+ # Specifies ACL list for remote control
+ # Same syntax as for ACLs in zones
+ # List of remotes delimited by comma
+ allow server0;
+ }
-# Section 'zones' contains information about zones to be served.
-zones {
+ # Section 'zones' contains information about zones to be served.
+ zones {
# Shared options for all listed zones
#
- # Build differences from zone file changes
+ # Build differences from zone file changes. EXPERIMENTAL feature.
# Possible values: on|off
# Default value: off
ixfr-from-differences off;
@@ -203,7 +243,7 @@ zones {
# Possible values: <1..INT_MAX> (seconds)
# Default value: inherited from zones.zonefile-sync
# It is also possible to suffix with unit size [s/m/h/d]
- # f.e. 1s = 1 day, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
+ # f.e. 1s = 1 second, 1m = 1 minute, 1h = 1 hour, 1d = 1 day
zonefile-sync 1h;
# XFR master server
@@ -217,48 +257,51 @@ zones {
# List of servers to send NOTIFY to
notify-out server0, server1;
+
+ # List of servers to allow UPDATE queries
+ update-in server0;
}
-}
-
-# Section 'log' configures logging of server messages.
-#
-# Logging recognizes 3 symbolic names of log devices:
-# stdout - Standard output
-# stderr - Standard error output
-# syslog - Syslog
-#
-# In addition, arbitrary number of log files may be specified (see below).
-#
-# Log messages are characterized by severity and category.
-# Supported severities:
-# debug - Debug messages. Must be turned on at compile time.
-# info - Informational messages.
-# notice - Notices and hints.
-# warning - Warnings. An action from the operator may be required.
-# error - Recoverable error. Some action should be taken.
-# fatal - Non-recoverable errors resulting in server shutdown.
-# (Not supported yet.)
-# all - All severities.
-#
-# Categories designate the source of the log message and roughly correspond
-# to server modules
-# Supported categories:
-# server - Messages related to general operation of the server.
-# zone - Messages related to zones, zone parsing and loading.
-# answering - Messages regarding query processing and response creation.
-# any - All categories
-#
-# More severities (separated by commas) may be listed for each category.
-# All applicable severities must be listed.
-# (I.e. specifying 'error' severity does mean: 'log error messages',
-# and NOT 'log all messages of severity error and above'.)
-#
-# Default settings (in case there are no entries in 'log' section or the section
-# is missing at all):
-#
-# stderr { any error; }
-# syslog { any error; }
-log {
+ }
+
+ # Section 'log' configures logging of server messages.
+ #
+ # Logging recognizes 3 symbolic names of log devices:
+ # stdout - Standard output
+ # stderr - Standard error output
+ # syslog - Syslog
+ #
+ # In addition, arbitrary number of log files may be specified (see below).
+ #
+ # Log messages are characterized by severity and category.
+ # Supported severities:
+ # debug - Debug messages. Must be turned on at compile time.
+ # info - Informational messages.
+ # notice - Notices and hints.
+ # warning - Warnings. An action from the operator may be required.
+ # error - Recoverable error. Some action should be taken.
+ # fatal - Non-recoverable errors resulting in server shutdown.
+ # (Not supported yet.)
+ # all - All severities.
+ #
+ # Categories designate the source of the log message and roughly correspond
+ # to server modules
+ # Supported categories:
+ # server - Messages related to general operation of the server.
+ # zone - Messages related to zones, zone parsing and loading.
+ # answering - Messages regarding query processing and response creation.
+ # any - All categories
+ #
+ # More severities (separated by commas) may be listed for each category.
+ # All applicable severities must be listed.
+ # (I.e. specifying 'error' severity does mean: 'log error messages',
+ # and NOT 'log all messages of severity error and above'.)
+ #
+ # Default settings (in case there are no entries in 'log' section or the section
+ # is missing at all):
+ #
+ # stderr { any error; }
+ # syslog { any error; }
+ log {
# Log entry
#
@@ -290,7 +333,8 @@ log {
file "/tmp/knot-sample/knotd.debug" { # <path> is absolute or relative path to log file
server debug;
}
-}
+ }
+
.SH "SEE ALSO"
.LP
knotd(8), knotc(8)
diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l
index 58d1f4b..4be9405 100644
--- a/src/knot/conf/cf-lex.l
+++ b/src/knot/conf/cf-lex.l
@@ -68,7 +68,7 @@ BLANK [ \t\n]
[\!\$\%\^\&\*\(\)\/\+\-\@\{\}\;\,] { return yytext[0]; }
system { lval.t = yytext; return SYSTEM; }
identity { lval.t = yytext; return IDENTITY; }
-version { lval.t = yytext; return VERSION; }
+version { lval.t = yytext; return SVERSION; }
nsid { lval.t = yytext; return NSID; }
storage { lval.t = yytext; return STORAGE; }
key { lval.t = yytext; return KEY; }
@@ -85,18 +85,26 @@ zonefile-sync { lval.t = yytext; return DBSYNC_TIMEOUT; }
ixfr-fslimit { lval.t = yytext; return IXFR_FSLIMIT; }
xfr-in { lval.t = yytext; return XFR_IN; }
xfr-out { lval.t = yytext; return XFR_OUT; }
+update-in { lval.t = yytext; return UPDATE_IN; }
notify-in { lval.t = yytext; return NOTIFY_IN; }
notify-out { lval.t = yytext; return NOTIFY_OUT; }
workers { lval.t = yytext; return WORKERS; }
user { lval.t = yytext; return USER; }
pidfile { lval.t = yytext; return PIDFILE; }
ixfr-from-differences { lval.t = yytext; return BUILD_DIFFS; }
+max-conn-idle { lval.t = yytext; return MAX_CONN_IDLE; }
+max-conn-handshake { lval.t = yytext; return MAX_CONN_HS; }
+max-conn-reply { lval.t = yytext; return MAX_CONN_REPLY; }
interfaces { lval.t = yytext; return INTERFACES; }
address { lval.t = yytext; return ADDRESS; }
port { lval.t = yytext; return PORT; }
via { lval.t = yytext; return VIA; }
+control { lval.t = yytext; return CONTROL; }
+allow { lval.t = yytext; return ALLOW; }
+listen-on { lval.t = yytext; return LISTEN_ON; }
+
log { lval.t = yytext; return LOG; }
any { lval.t = yytext; lval.i = LOG_ANY; return LOG_SRC; }
diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y
index 4f490b5..a0c6aaf 100644
--- a/src/knot/conf/cf-parse.y
+++ b/src/knot/conf/cf-parse.y
@@ -28,7 +28,7 @@ static conf_log_t *this_log = 0;
static conf_log_map_t *this_logmap = 0;
//#define YYERROR_VERBOSE 1
-static void conf_start_iface(void *scanner, char* ifname)
+static void conf_init_iface(void *scanner, char* ifname, int port)
{
this_iface = malloc(sizeof(conf_iface_t));
if (this_iface == NULL) {
@@ -37,7 +37,12 @@ static void conf_start_iface(void *scanner, char* ifname)
}
memset(this_iface, 0, sizeof(conf_iface_t));
this_iface->name = ifname;
- this_iface->port = CONFIG_DEFAULT_PORT;
+ this_iface->port = port;
+}
+
+static void conf_start_iface(void *scanner, char* ifname)
+{
+ conf_init_iface(scanner, ifname, -1);
add_tail(&new_config->ifaces, &this_iface->n);
++new_config->ifaces_count;
}
@@ -49,6 +54,7 @@ static void conf_start_remote(void *scanner, char *remote)
cf_error(scanner, "not enough memory when allocating remote");
return;
}
+
memset(this_remote, 0, sizeof(conf_iface_t));
this_remote->name = remote;
add_tail(&new_config->remotes, &this_remote->n);
@@ -181,7 +187,7 @@ static void conf_zone_start(void *scanner, char *name) {
if (this_zone->name != NULL) {
memcpy(this_zone->name, name, nlen);
this_zone->name[nlen] = '.';
- this_zone->name[nlen + 1] = '\0';
+ this_zone->name[++nlen] = '\0';
}
free(name);
} else {
@@ -192,7 +198,7 @@ static void conf_zone_start(void *scanner, char *name) {
char buf[512];
knot_dname_t *dn = NULL;
if (this_zone->name != NULL) {
- dn = knot_dname_new_from_str(this_zone->name, nlen + 1, 0);
+ dn = knot_dname_new_from_str(this_zone->name, nlen, 0);
}
if (dn == NULL) {
free(this_zone->name);
@@ -224,6 +230,7 @@ static void conf_zone_start(void *scanner, char *name) {
init_list(&this_zone->acl.xfr_out);
init_list(&this_zone->acl.notify_in);
init_list(&this_zone->acl.notify_out);
+ init_list(&this_zone->acl.update_in);
}
}
@@ -263,7 +270,7 @@ static int conf_mask(void* scanner, int nval, int prefixlen) {
%token <tok> SIZE
%token <tok> BOOL
-%token <tok> SYSTEM IDENTITY VERSION NSID STORAGE KEY KEYS
+%token <tok> SYSTEM IDENTITY SVERSION NSID STORAGE KEY KEYS
%token <tok> TSIG_ALGO_NAME
%token <tok> WORKERS
%token <tok> USER
@@ -280,15 +287,21 @@ static int conf_mask(void* scanner, int nval, int prefixlen) {
%token <tok> IXFR_FSLIMIT
%token <tok> XFR_IN
%token <tok> XFR_OUT
+%token <tok> UPDATE_IN
%token <tok> NOTIFY_IN
%token <tok> NOTIFY_OUT
%token <tok> BUILD_DIFFS
+%token <tok> MAX_CONN_IDLE
+%token <tok> MAX_CONN_HS
+%token <tok> MAX_CONN_REPLY
%token <tok> INTERFACES ADDRESS PORT
%token <tok> IPA
%token <tok> IPA6
%token <tok> VIA
+%token <tok> CONTROL ALLOW LISTEN_ON
+
%token <tok> LOG
%token <tok> LOG_DEST
%token <tok> LOG_SRC
@@ -309,12 +322,12 @@ interface_start:
| LOG_SRC { conf_start_iface(scanner, strdup($1.t)); }
| LOG { conf_start_iface(scanner, strdup($1.t)); }
| LOG_LEVEL { conf_start_iface(scanner, strdup($1.t)); }
+ | CONTROL { conf_start_iface(scanner, strdup($1.t)); }
;
interface:
- interface_start '{'
| interface PORT NUM ';' {
- if (this_iface->port != CONFIG_DEFAULT_PORT) {
+ if (this_iface->port > 0) {
cf_error(scanner, "only one port definition is allowed in interface section\n");
} else {
this_iface->port = $3.i;
@@ -334,7 +347,7 @@ interface:
} else {
this_iface->address = $3.t;
this_iface->family = AF_INET;
- if (this_iface->port != CONFIG_DEFAULT_PORT) {
+ if (this_iface->port > 0) {
cf_error(scanner, "only one port definition is allowed in interface section\n");
} else {
this_iface->port = $5.i;
@@ -355,7 +368,7 @@ interface:
} else {
this_iface->address = $3.t;
this_iface->family = AF_INET6;
- if (this_iface->port != CONFIG_DEFAULT_PORT) {
+ if (this_iface->port > 0) {
cf_error(scanner, "only one port definition is allowed in interface section\n");
} else {
this_iface->port = $5.i;
@@ -366,7 +379,7 @@ interface:
interfaces:
INTERFACES '{'
- | interfaces interface '}' {
+ | interfaces interface_start '{' interface '}' {
if (this_iface->address == 0) {
char buf[512];
snprintf(buf, sizeof(buf), "interface '%s' has no defined address", this_iface->name);
@@ -377,7 +390,7 @@ interfaces:
system:
SYSTEM '{'
- | system VERSION TEXT ';' { new_config->version = $3.t; }
+ | system SVERSION TEXT ';' { new_config->version = $3.t; }
| system IDENTITY TEXT ';' { new_config->identity = $3.t; }
| system NSID HEXSTR ';' { new_config->nsid = $3.t; new_config->nsid_len = $3.l; }
| system NSID TEXT ';' { new_config->nsid = $3.t; new_config->nsid_len = strlen(new_config->nsid); }
@@ -419,6 +432,9 @@ system:
free($3.t);
}
+ | system MAX_CONN_IDLE INTERVAL ';' { new_config->max_conn_idle = $3.i; }
+ | system MAX_CONN_HS INTERVAL ';' { new_config->max_conn_hs = $3.i; }
+ | system MAX_CONN_REPLY INTERVAL ';' { new_config->max_conn_reply = $3.i; }
;
keys:
@@ -478,10 +494,10 @@ remote_start:
| LOG_SRC { conf_start_remote(scanner, strdup($1.t)); }
| LOG { conf_start_remote(scanner, strdup($1.t)); }
| LOG_LEVEL { conf_start_remote(scanner, strdup($1.t)); }
+ | CONTROL { conf_start_remote(scanner, strdup($1.t)); }
;
remote:
- remote_start '{'
| remote PORT NUM ';' {
if (this_remote->port != 0) {
cf_error(scanner, "only one port definition is allowed in remote section\n");
@@ -577,7 +593,7 @@ remote:
remotes:
REMOTES '{'
- | remotes remote '}' {
+ | remotes remote_start '{' remote '}' {
if (this_remote->address == 0) {
char buf[512];
snprintf(buf, sizeof(buf), "remote '%s' has no defined address", this_remote->name);
@@ -599,6 +615,9 @@ zone_acl_start:
| NOTIFY_OUT {
this_list = &this_zone->acl.notify_out;
}
+ | UPDATE_IN {
+ this_list = &this_zone->acl.update_in;
+ }
;
zone_acl_item:
@@ -606,16 +625,15 @@ zone_acl_item:
| LOG_SRC { conf_acl_item(scanner, strdup($1.t)); }
| LOG { conf_acl_item(scanner, strdup($1.t)); }
| LOG_LEVEL { conf_acl_item(scanner, strdup($1.t)); }
+ | CONTROL { conf_acl_item(scanner, strdup($1.t)); }
;
zone_acl_list:
- zone_acl_start
| zone_acl_list zone_acl_item ','
| zone_acl_list zone_acl_item ';'
;
zone_acl:
- zone_acl_start '{'
| zone_acl TEXT ';' {
/* Find existing node in remotes. */
node* r = 0; conf_iface_t* found = 0;
@@ -652,6 +670,7 @@ zone_start:
| LOG_SRC { conf_zone_start(scanner, strdup($1.t)); }
| LOG { conf_zone_start(scanner, strdup($1.t)); }
| LOG_LEVEL { conf_zone_start(scanner, strdup($1.t)); }
+ | CONTROL { conf_zone_start(scanner, strdup($1.t)); }
| NUM '/' TEXT {
if ($1.i < 0 || $1.i > 255) {
char buf[256] = "";
@@ -676,8 +695,8 @@ zone_start:
zone:
zone_start '{'
- | zone zone_acl '}'
- | zone zone_acl_list
+ | zone zone_acl_start '{' zone_acl '}'
+ | zone zone_acl_start zone_acl_list
| zone FILENAME TEXT ';' { this_zone->file = $3.t; }
| zone BUILD_DIFFS BOOL ';' { this_zone->build_diffs = $3.i; }
| zone SEMANTIC_CHECKS BOOL ';' { this_zone->enable_checks = $3.i; }
@@ -816,8 +835,30 @@ log_start:
log: LOG '{' log_start log_end;
+ctl_listen_start:
+ LISTEN_ON { conf_init_iface(scanner, NULL, -1); }
+ ;
+
+ctl_allow_start:
+ ALLOW {
+ this_list = &new_config->ctl.allow;
+ }
+ ;
+
+control:
+ CONTROL '{'
+ | control ctl_listen_start '{' interface '}' {
+ if (this_iface->address == 0) {
+ cf_error(scanner, "control interface has no defined address");
+ } else {
+ new_config->ctl.iface = this_iface;
+ }
+ }
+ | control ctl_allow_start '{' zone_acl '}'
+ | control ctl_allow_start zone_acl_list
+ ;
-conf: ';' | system '}' | interfaces '}' | keys '}' | remotes '}' | zones '}' | log '}';
+conf: ';' | system '}' | interfaces '}' | keys '}' | remotes '}' | zones '}' | log '}' | control '}';
%%
diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c
index d9d0a77..ac36670 100644
--- a/src/knot/conf/conf.c
+++ b/src/knot/conf/conf.c
@@ -26,6 +26,7 @@
#include <urcu.h>
#include "knot/conf/conf.h"
#include "knot/common.h"
+#include "knot/ctl/remote.h"
/*
* Defaults.
@@ -158,6 +159,28 @@ static int conf_process(conf_t *conf)
return KNOT_ENOMEM;
}
}
+
+ /* Default TCP/UDP limits. */
+ if (conf->max_conn_idle < 1) {
+ conf->max_conn_idle = CONFIG_IDLE_WD;
+ }
+ if (conf->max_conn_hs < 1) {
+ conf->max_conn_hs = CONFIG_HANDSHAKE_WD;
+ }
+ if (conf->max_conn_reply < 1) {
+ conf->max_conn_reply = CONFIG_REPLY_WD;
+ }
+
+ // Postprocess interfaces
+ conf_iface_t *cfg_if = NULL;
+ WALK_LIST(cfg_if, conf->ifaces) {
+ if (cfg_if->port <= 0) {
+ cfg_if->port = CONFIG_DEFAULT_PORT;
+ }
+ }
+ if (conf->ctl.iface && conf->ctl.iface->port <= 0) {
+ conf->ctl.iface->port = REMOTE_DPORT;
+ }
// Postprocess zones
int ret = KNOT_EOK;
@@ -289,6 +312,17 @@ static int conf_process(conf_t *conf)
/* Update UID and GID. */
if (conf->uid < 0) conf->uid = getuid();
if (conf->gid < 0) conf->gid = getgid();
+
+ /* Build remote control ACL. */
+ sockaddr_t addr;
+ conf_remote_t *r = NULL;
+ WALK_LIST(r, conf->ctl.allow) {
+ conf_iface_t *i = r->remote;
+ sockaddr_init(&addr, -1);
+ sockaddr_set(&addr, i->family, i->address, 0);
+ sockaddr_setprefix(&addr, i->prefix);
+ acl_create(conf->ctl.acl, &addr, ACL_ACCEPT, i, 0);
+ }
return ret;
}
@@ -304,6 +338,9 @@ void __attribute__ ((constructor)) conf_init()
{
// Create new config
s_config = conf_new(0);
+ if (!s_config) {
+ return;
+ }
/* Create default interface. */
conf_iface_t * iface = malloc(sizeof(conf_iface_t));
@@ -436,20 +473,21 @@ conf_t *conf_new(const char* path)
conf_t *c = malloc(sizeof(conf_t));
memset(c, 0, sizeof(conf_t));
- // Add path
+ /* Add path. */
if (path) {
c->filename = strdup(path);
}
- // Initialize lists
+ /* Initialize lists. */
init_list(&c->logs);
init_list(&c->ifaces);
init_list(&c->zones);
init_list(&c->hooks);
init_list(&c->remotes);
init_list(&c->keys);
+ init_list(&c->ctl.allow);
- // Defaults
+ /* Defaults. */
c->zone_checks = 0;
c->notify_retries = CONFIG_NOTIFY_RETRIES;
c->notify_timeout = CONFIG_NOTIFY_TIMEOUT;
@@ -458,6 +496,14 @@ conf_t *conf_new(const char* path)
c->uid = -1;
c->gid = -1;
c->build_diffs = 0; /* Disable by default. */
+
+ /* ACLs. */
+ c->ctl.acl = acl_new(ACL_DENY, "remote_ctl");
+ if (!c->ctl.acl) {
+ free(c->filename);
+ free(c);
+ c = NULL;
+ }
return c;
}
@@ -557,7 +603,7 @@ void conf_truncate(conf_t *conf, int unload_hooks)
conf->logs_count = 0;
init_list(&conf->logs);
- // Free remotes
+ // Free remote interfaces
WALK_LIST_DELSAFE(n, nxt, conf->remotes) {
conf_free_iface((conf_iface_t*)n);
}
@@ -595,6 +641,19 @@ void conf_truncate(conf_t *conf, int unload_hooks)
free(conf->nsid);
conf->nsid = 0;
}
+
+ /* Free remote control list. */
+ WALK_LIST_DELSAFE(n, nxt, conf->ctl.allow) {
+ conf_free_remote((conf_remote_t*)n);
+ }
+ conf->remotes_count = 0;
+ init_list(&conf->remotes);
+
+ /* Free remote control ACL. */
+ acl_truncate(conf->ctl.acl);
+
+ /* Free remote control iface. */
+ conf_free_iface(conf->ctl.iface);
}
void conf_free(conf_t *conf)
@@ -603,10 +662,13 @@ void conf_free(conf_t *conf)
return;
}
- // Truncate config
+ /* Truncate config. */
conf_truncate(conf, 1);
+
+ /* Free remote control ACL. */
+ acl_delete(&conf->ctl.acl);
- // Free config
+ /* Free config. */
free(conf);
}
@@ -653,6 +715,9 @@ int conf_open(const char* path)
/* Create new config. */
conf_t *nconf = conf_new(path);
+ if (!nconf) {
+ return KNOT_ENOMEM;
+ }
/* Parse config. */
conf_parse_begin(nconf);
@@ -784,6 +849,7 @@ void conf_free_zone(conf_zone_t *zone)
WALK_LIST_FREE(zone->acl.xfr_out);
WALK_LIST_FREE(zone->acl.notify_in);
WALK_LIST_FREE(zone->acl.notify_out);
+ WALK_LIST_FREE(zone->acl.update_in);
free(zone->name);
free(zone->file);
@@ -814,6 +880,11 @@ void conf_free_iface(conf_iface_t *iface)
free(iface);
}
+void conf_free_remote(conf_remote_t *r)
+{
+ free(r);
+}
+
void conf_free_log(conf_log_t *log)
{
if (!log) {
diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h
index 9f2440e..a64388e 100644
--- a/src/knot/conf/conf.h
+++ b/src/knot/conf/conf.h
@@ -38,6 +38,7 @@
#include "libknot/tsig.h"
#include "common/lists.h"
#include "common/log.h"
+#include "common/acl.h"
#include "common/sockaddr.h"
#include "common/general-tree.h"
@@ -46,6 +47,9 @@
#define CONFIG_NOTIFY_RETRIES 5 /*!< 5 retries (suggested in RFC1996) */
#define CONFIG_NOTIFY_TIMEOUT 60 /*!< 60s (suggested in RFC1996) */
#define CONFIG_DBSYNC_TIMEOUT (60*60) /*!< 1 hour. */
+#define CONFIG_REPLY_WD 10 /*!< SOA/NOTIFY query timeout [s]. */
+#define CONFIG_HANDSHAKE_WD 10 /*!< [secs] for connection to make a request.*/
+#define CONFIG_IDLE_WD 60 /*!< [secs] of allowed inactivity between requests */
/*!
* \brief Configuration for the interface
@@ -103,6 +107,7 @@ typedef struct conf_zone_t {
list xfr_out; /*!< Remotes accepted for xfr-out.*/
list notify_in; /*!< Remotes accepted for notify-in.*/
list notify_out; /*!< Remotes accepted for notify-out.*/
+ list update_in; /*!< Remotes accepted for DDNS.*/
} acl;
} conf_zone_t;
@@ -145,6 +150,15 @@ typedef struct conf_key_t {
} conf_key_t;
/*!
+ * \brief Remote control interface.
+ */
+typedef struct conf_control_t {
+ conf_iface_t *iface; /*!< Remote control interface. */
+ list allow; /*!< List of allowed remotes. */
+ acl_t* acl; /*!< ACL. */
+} conf_control_t;
+
+/*!
* \brief Main config structure.
*
* Configuration structure.
@@ -163,6 +177,9 @@ typedef struct conf_t {
int workers; /*!< Number of workers per interface. */
int uid; /*!< Specified user id. */
int gid; /*!< Specified group id. */
+ int max_conn_idle; /*!< TCP idle timeout. */
+ int max_conn_hs; /*!< TCP of inactivity before first query. */
+ int max_conn_reply; /*!< TCP/UDP query timeout. */
/*
* Log
@@ -203,6 +220,11 @@ typedef struct conf_t {
general_tree_t *zone_tree; /*!< Zone tree for duplicate checking. */
/*
+ * Remote control interface.
+ */
+ conf_control_t ctl;
+
+ /*
* Implementation specifics
*/
list hooks; /*!< List of config hooks. */
@@ -360,6 +382,9 @@ void conf_free_key(conf_key_t *k);
/*! \brief Free interface config. */
void conf_free_iface(conf_iface_t *iface);
+/*! \brief Free remotes config. */
+void conf_free_remote(conf_remote_t *r);
+
/*! \brief Free log config. */
void conf_free_log(conf_log_t *log);
diff --git a/src/knot/ctl/knotc_main.c b/src/knot/ctl/knotc_main.c
index dd9a8e7..92c5c09 100644
--- a/src/knot/ctl/knotc_main.c
+++ b/src/knot/ctl/knotc_main.c
@@ -12,73 +12,124 @@
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 <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
-#include <sys/wait.h>
#include <time.h>
-#include <sys/select.h>
-#include <sys/stat.h>
#include <getopt.h>
+#include <ctype.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_SYS_SELECT_H
+#include <sys/select.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
#include "knot/common.h"
#include "knot/ctl/process.h"
+#include "knot/ctl/remote.h"
#include "knot/conf/conf.h"
#include "knot/zone/zone-load.h"
+#include "knot/server/socket.h"
+#include "knot/server/tcp-handler.h"
+#include "libknot/util/wire.h"
+#include "libknot/packet/query.h"
+#include "libknot/packet/response.h"
/*! \brief Controller constants. */
enum knotc_constants_t {
- WAITPID_TIMEOUT = 10 /*!< \brief Timeout for waiting for process. */
+ WAITPID_TIMEOUT = 120 /*!< \brief Timeout for waiting for process. */
};
/*! \brief Controller flags. */
enum knotc_flag_t {
- F_NULL = 0 << 0,
- F_FORCE = 1 << 0,
- F_VERBOSE = 1 << 1,
- F_WAIT = 1 << 2,
+ F_NULL = 0 << 0,
+ F_FORCE = 1 << 0,
+ F_VERBOSE = 1 << 1,
+ F_WAIT = 1 << 2,
F_INTERACTIVE = 1 << 3,
- F_AUTO = 1 << 4,
- F_UNPRIVILEGED= 1 << 5
+ F_AUTO = 1 << 4,
+ F_UNPRIVILEGED = 1 << 5,
+ F_NOCONF = 1 << 6,
+ F_DRYRUN = 1 << 7
};
-static inline unsigned has_flag(unsigned flags, enum knotc_flag_t f) {
+/*! \brief Check if flag is present. */
+static inline unsigned has_flag(unsigned flags, enum knotc_flag_t f)
+{
return flags & f;
}
+/*! \brief Callback prototype for command. */
+typedef int (*knot_cmdf_t)(int argc, char *argv[], unsigned flags, int jobs);
+
+/*! \brief Command table item. */
+typedef struct knot_cmd_t {
+ const char *name;
+ knot_cmdf_t cb;
+ const char *desc;
+ int need_conf;
+} knot_cmd_t;
+
+/* Forward decls. */
+static int cmd_start(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_stop(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_restart(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_reload(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_refresh(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_flush(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_status(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_checkconf(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_checkzone(int argc, char *argv[], unsigned flags, int jobs);
+static int cmd_compile(int argc, char *argv[], unsigned flags, int jobs);
+
+/*! \brief Table of remote commands. */
+knot_cmd_t knot_cmd_tbl[] = {
+ {"start", &cmd_start, "\tStart server (no-op if running).", 1},
+ {"stop", &cmd_stop, "\tStop server (no-op if running).", 1},
+ {"restart", &cmd_restart, "Restarts server (no-op if running).", 1},
+ {"reload", &cmd_reload, "\tReloads configuration and changed zones.",0},
+ {"refresh", &cmd_refresh,"Refresh slave zones (all if not specified).",0},
+ {"flush", &cmd_flush, "\tFlush journal and update zone files.",0},
+ {"status", &cmd_status, "\tCheck if server is running.",0},
+ {"checkconf", &cmd_checkconf, "Check server configuration.",1},
+ {"checkzone", &cmd_checkzone, "Check specified zone files.",1},
+ {"compile", &cmd_compile, "Compile zone files (all if not specified).",1},
+ {NULL, NULL, NULL,0}
+};
+
/*! \brief Print help. */
void help(int argc, char **argv)
{
- printf("Usage: %sc [parameters] start|stop|restart|reload|running|"
- "compile [additional]\n", PACKAGE_NAME);
+ printf("Usage: %sc [parameters] <action> [action_args]\n", PACKAGE_NAME);
printf("\nParameters:\n"
- " -c [file], --config=[file] Select configuration file.\n"
- " -j [num], --jobs=[num] Number of parallel tasks to run when compiling.\n"
- " -f, --force Force operation - override some checks.\n"
- " -v, --verbose Verbose mode - additional runtime information.\n"
- " -V, --version Print %s server version.\n"
- " -w, --wait Wait for the server to finish start/stop operations.\n"
- " -i, --interactive Interactive mode (do not daemonize).\n"
- " -a, --auto Enable automatic recompilation (start or reload).\n"
- " -h, --help Print help and usage.\n",
- PACKAGE_NAME);
- printf("\nActions:\n"
- " start Start %s server zone (no-op if running).\n"
- " stop Stop %s server (no-op if not running).\n"
- " restart Stops and then starts %s server.\n"
- " reload Reload %s configuration and compiled zones.\n"
- " refresh Refresh all slave zones.\n"
- " running Check if server is running.\n"
- " checkconf Check server configuration.\n"
- "\n"
- " checkzone Check zones (accepts specific zones, \n"
- " e.g. 'knotc checkzone example1.com example2.com').\n"
- " compile Compile zones (accepts specific zones, see above).\n",
- PACKAGE_NAME, PACKAGE_NAME, PACKAGE_NAME, PACKAGE_NAME);
+ " -c [file], --config=[file]\tSelect configuration file.\n"
+ " -j [num], --jobs=[num] \tNumber of parallel tasks to run when compiling.\n"
+ " -s [server] \tRemote server address (default %s)\n"
+ " -p [port] \tRemote server port (default %d)\n"
+ " -y [hmac:]name:key] \tUse key_id specified on the command line.\n"
+ " -k [file] \tUse key file (as in config section 'keys').\n"
+ " \t f.e. echo \"knotc-key hmac-md5 Wg==\" > knotc.key\n"
+ " -f, --force \tForce operation - override some checks.\n"
+ " -v, --verbose \tVerbose mode - additional runtime information.\n"
+ " -V, --version \tPrint %s server version.\n"
+ " -w, --wait \tWait for the server to finish start/stop operations.\n"
+ " -i, --interactive \tInteractive mode (do not daemonize).\n"
+ " -h, --help \tPrint help and usage.\n",
+ "127.0.0.1", REMOTE_DPORT, PACKAGE_NAME);
+ printf("\nActions:\n");
+ knot_cmd_t *c = knot_cmd_tbl;
+ while (c->name != NULL) {
+ printf(" %s\t\t\t%s\n", c->name, c->desc);
+ ++c;
+ }
}
/*!
@@ -90,14 +141,14 @@ void help(int argc, char **argv)
* \retval KNOT_EOK if up to date.
* \retval KNOT_ERROR if needs recompilation.
*/
-int check_zone(const char *db, const char* source)
+static int check_zone(const char *db, const char *source)
{
/* Check zonefile. */
struct stat st;
if (stat(source, &st) != 0) {
int reason = errno;
const char *emsg = "";
- switch(reason) {
+ switch (reason) {
case EACCES:
emsg = "Not enough permissions to access zone file '%s'.\n";
break;
@@ -108,7 +159,6 @@ int check_zone(const char *db, const char* source)
emsg = "Unable to stat zone file '%s'.\n";
break;
}
-
log_zone_error(emsg, source);
return KNOT_ENOENT;
}
@@ -131,65 +181,6 @@ int check_zone(const char *db, const char* source)
return ret;
}
-pid_t wait_cmd(pid_t proc, int *rc)
-{
- /* Wait for finish. */
- sigset_t newset;
- sigfillset(&newset);
- sigprocmask(SIG_BLOCK, &newset, 0);
- proc = waitpid(proc, rc, 0);
- sigprocmask(SIG_UNBLOCK, &newset, 0);
- return proc;
-}
-
-pid_t start_cmd(const char *argv[], int argc, int flags)
-{
- pid_t chproc = fork();
- if (chproc == 0) {
-
- /* Alter privileges. */
- if (flags & F_UNPRIVILEGED) {
- proc_update_privileges(conf()->uid, conf()->gid);
- }
-
- /* Duplicate, it doesn't run from stack address anyway. */
- char **args = malloc((argc + 1) * sizeof(char*));
- memset(args, 0, (argc + 1) * sizeof(char*));
- int ci = 0;
- for (int i = 0; i < argc; ++i) {
- if (strlen(argv[i]) > 0) {
- args[ci++] = strdup(argv[i]);
- }
- }
- args[ci] = 0;
-
- /* Execute command. */
- fflush(stdout);
- fflush(stderr);
- execvp(args[0], args);
-
- /* Execute failed. */
- log_server_error("Failed to run executable '%s'\n", args[0]);
- for (int i = 0; i < argc; ++i) {
- free(args[i]);
- }
- free(args);
-
- exit(1);
- return -1;
- }
-
- return chproc;
-}
-
-int exec_cmd(const char *argv[], int argc)
-{
- int ret = 0;
- pid_t proc = start_cmd(argv, argc, 0);
- wait_cmd(proc, &ret);
- return ret;
-}
-
/*! \brief Zone compiler task. */
typedef struct {
conf_zone_t *zone;
@@ -197,44 +188,44 @@ typedef struct {
} knotc_zctask_t;
/*! \brief Create set of watched tasks. */
-knotc_zctask_t *zctask_create(int count)
+static knotc_zctask_t *zctask_create(int count)
{
if (count <= 0) {
return 0;
}
-
+
knotc_zctask_t *t = malloc(count * sizeof(knotc_zctask_t));
for (unsigned i = 0; i < count; ++i) {
t[i].proc = -1;
t[i].zone = 0;
}
-
+
return t;
}
/*! \brief Wait for single task to finish. */
-int zctask_wait(knotc_zctask_t *tasks, int count, int is_checkzone)
+static int zctask_wait(knotc_zctask_t *tasks, int count, int is_checkzone)
{
/* Wait for children to finish. */
int rc = 0;
- pid_t pid = wait_cmd(-1, &rc);
+ pid_t pid = pid_wait(-1, &rc);
/* Find task. */
conf_zone_t *z = 0;
for (unsigned i = 0; i < count; ++i) {
if (tasks[i].proc == pid) {
- tasks[i].proc = -1; /* Invalidate. */
+ tasks[i].proc = -1; /* Invalidate. */
z = tasks[i].zone;
break;
}
}
-
+
if (z == 0) {
log_server_error("Failed to find zone for finished "
"zone compilation process.\n");
return 1;
}
-
+
/* Evaluate. */
if (!WIFEXITED(rc)) {
log_server_error("%s of '%s' failed, process was killed.\n",
@@ -249,15 +240,17 @@ int zctask_wait(knotc_zctask_t *tasks, int count, int is_checkzone)
"return code was '%d'\n",
z->name, WEXITSTATUS(rc));
}
+
return 1;
}
}
-
+
return 0;
}
/*! \brief Register running zone compilation process. */
-int zctask_add(knotc_zctask_t *tasks, int count, pid_t pid, conf_zone_t *zone)
+static int zctask_add(knotc_zctask_t *tasks, int count, pid_t pid,
+ conf_zone_t *zone)
{
/* Find free space. */
for (unsigned i = 0; i < count; ++i) {
@@ -267,479 +260,792 @@ int zctask_add(knotc_zctask_t *tasks, int count, pid_t pid, conf_zone_t *zone)
return 0;
}
}
-
+
/* Free space not found. */
return -1;
}
-/*!
- * \brief Execute specified action.
- *
- * \param action Action to be executed (start, stop, restart...)
- * \param argv Additional arguments vector.
- * \param argc Addition arguments count.
- * \param pid Specified PID for action.
- * \param verbose True if running in verbose mode.
- * \param force True if forced operation is required.
- * \param wait Wait for the operation to finish.
- * \param interactive Interactive mode.
- * \param automatic Automatic compilation.
- * \param jobs Number of parallel tasks to run.
- * \param pidfile Specified PID file for action.
- *
- * \retval 0 on success.
- * \retval error return code for main on error.
- */
-int execute(const char *action, char **argv, int argc, pid_t pid,
- unsigned flags, unsigned jobs, const char *pidfile)
+static int cmd_remote_print_reply(const knot_rrset_t *rr)
{
- int valid_cmd = 0;
- int rc = 0;
- if (strcmp(action, "start") == 0) {
- // Check pidfile for w+
- log_server_info("Starting server...\n");
-
- // Check PID
- valid_cmd = 1;
-// if (pid < 0 && pid == KNOT_ERANGE) {
-// fprintf(stderr, "control: Another server instance "
-// "is already starting.\n");
-// return 1;
-// }
- if (pid > 0 && pid_running(pid)) {
-
- log_server_error("Server PID found, "
- "already running.\n");
-
- if (!has_flag(flags, F_FORCE)) {
- return 1;
- } else {
- log_server_info("Forcing server start, "
- "killing old pid=%ld.\n",
- (long)pid);
- kill(pid, SIGKILL);
- pid_remove(pidfile);
- }
- }
-
- // Recompile zones if needed
- if (has_flag(flags, F_AUTO)) {
- rc = execute("compile", argv, argc, -1, flags,
- jobs, pidfile);
- }
-
- // Lock configuration
- rcu_read_lock();
-
- // Prepare command
- const char *cfg = conf()->filename;
- const char *args[] = {
- PROJECT_EXEC,
- has_flag(flags, F_INTERACTIVE) ? "" : "-d",
- cfg ? "-c" : "",
- cfg ? cfg : "",
- has_flag(flags, F_VERBOSE) ? "-v" : "",
- argc > 0 ? argv[0] : ""
- };
-
- // Unlock configuration
- rcu_read_unlock();
-
- // Execute command
- if (has_flag(flags, F_INTERACTIVE)) {
- log_server_info("Running in interactive mode.\n");
- fflush(stderr);
- fflush(stdout);
- }
- if ((rc = exec_cmd(args, 6)) < 0) {
- pid_remove(pidfile);
- rc = 1;
- }
- fflush(stderr);
- fflush(stdout);
-
- // Wait for finish
- if (has_flag(flags, F_WAIT) && !has_flag(flags, F_INTERACTIVE)) {
- if (has_flag(flags, F_VERBOSE)) {
- log_server_info("Waiting for server to load.\n");
- }
- /* Periodically read pidfile and wait for
- * valid result. */
- pid = 0;
- while(pid == 0 || !pid_running(pid)) {
- pid = pid_read(pidfile);
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 500 * 1000;
- select(0, 0, 0, 0, &tv);
- }
- }
+ /* Process first RRSet in data section. */
+ if (knot_rrset_type(rr) != KNOT_RRTYPE_TXT) {
+ return KNOT_EMALF;
}
- if (strcmp(action, "stop") == 0) {
-
- // Check PID
- log_server_info("Stopping server...\n");
- valid_cmd = 1;
- rc = 0;
- if (pid <= 0 || !pid_running(pid)) {
- log_server_warning("Server PID not found, "
- "probably not running.\n");
-
- if (!has_flag(flags, F_FORCE)) {
- rc = 1;
- } else {
- log_server_info("Forcing server stop.\n");
- }
+
+ const knot_rdata_t *rd = knot_rrset_rdata(rr);
+ while (rd != NULL) {
+ /* Skip empty nodes. */
+ if (knot_rdata_item_count(rd) < 1) {
+ rd = knot_rrset_rdata_next(rr, rd);
+ continue;
}
- // Stop
- if (rc == 0) {
- if (kill(pid, SIGTERM) < 0) {
- pid_remove(pidfile);
- rc = 1;
- }
+ /* Parse TXT. */
+ char* txt = remote_parse_txt(rd);
+ if (txt) {
+ log_server_info("Server reply: %s\n", txt);
}
+ free(txt);
- // Wait for finish
- if (rc == 0 && has_flag(flags, F_WAIT)) {
- if (has_flag(flags, F_VERBOSE)) {
- log_server_info("Waiting for server "
- "to stop.\n");
- }
- /* Periodically read pidfile and wait for
- * valid result. */
- while(pid_running(pid)) {
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 500 * 1000;
- select(0, 0, 0, 0, &tv);
- }
- }
+ rd = knot_rrset_rdata_next(rr, rd);
}
- if (strcmp(action, "restart") == 0) {
- valid_cmd = 1;
- execute("stop", argv, argc, pid, flags, jobs, pidfile);
-
- int i = 0;
- while((pid = pid_read(pidfile)) > 0) {
+
+ return KNOT_EOK;
+}
- if (!pid_running(pid)) {
- pid_remove(pidfile);
- break;
- }
- if (i == WAITPID_TIMEOUT) {
- log_server_error("Timeout while waiting for "
- "the server to finish.\n");
- //pid_remove(pidfile);
- break;
- } else {
- sleep(1);
- ++i;
- }
+static int cmd_remote_reply(int c)
+{
+ uint8_t *rwire = malloc(SOCKET_MTU_SZ);
+ knot_packet_t *reply = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE);
+ if (!rwire || !reply) {
+ free(rwire);
+ knot_packet_free(&reply);
+ return KNOT_ENOMEM;
+ }
+
+ /* Read response packet. */
+ int n = tcp_recv(c, rwire, SOCKET_MTU_SZ, NULL);
+ if (n < 0) {
+ dbg_server("remote: couldn't receive response = %d\n", n);
+ knot_packet_free(&reply);
+ free(rwire);
+ return KNOT_ECONN;
+ }
+
+ /* Parse packet and check response. */
+ int ret = remote_parse(reply, rwire, n);
+ if (ret == KNOT_EOK) {
+ /* Check RCODE */
+ ret = knot_packet_rcode(reply);
+
+ /* Check extra data. */
+ if (knot_packet_authority_rrset_count(reply) > 0) {
+ ret = cmd_remote_print_reply(reply->authority[0]);
}
-
- rc = execute("start", argv, argc, -1, flags, jobs, pidfile);
}
- if (strcmp(action, "reload") == 0) {
-
- // Check PID
- valid_cmd = 1;
- if (pid <= 0 || !pid_running(pid)) {
- log_server_warning("Server PID not found, "
- "probably not running.\n");
+
+ /* Response cleanup. */
+ knot_packet_free(&reply);
+ free(rwire);
+ return ret;
+}
- return 1;
- }
+static int cmd_remote(const char *cmd, uint16_t rrt, int argc, char *argv[])
+{
+ int rc = 0;
- // Recompile zones if needed
- if (has_flag(flags, F_AUTO)) {
- rc = execute("compile", argv, argc, -1, flags,
- jobs, pidfile);
+ /* Check remote address. */
+ conf_iface_t *r = conf()->ctl.iface;
+ if (!r || !r->address) {
+ log_server_error("No remote address for '%s' configured.\n",
+ cmd);
+ return 1;
+ }
+
+ /* Make query. */
+ uint8_t *buf = NULL;
+ size_t buflen = 0;
+ knot_packet_t *qr = remote_query(cmd, r->key);
+ if (!qr) {
+ log_server_warning("Could not prepare query for '%s'.\n",
+ cmd);
+ return 1;
+ }
+
+ /* Build query data. */
+ knot_rdata_t *rd = NULL;
+ knot_rrset_t *rr = remote_build_rr("data.", rrt);
+ for (int i = 0; i < argc; ++i) {
+ switch(rrt) {
+ case KNOT_RRTYPE_CNAME:
+ rd = remote_create_cname(argv[i]);
+ break;
+ case KNOT_RRTYPE_TXT:
+ default:
+ rd = remote_create_txt(argv[i]);
+ break;
}
+ knot_rrset_add_rdata(rr, rd);
+ rd = NULL;
+ }
+ remote_query_append(qr, rr);
+ if (knot_packet_to_wire(qr, &buf, &buflen) != KNOT_EOK) {
+ knot_rrset_deep_free(&rr, 1, 1, 1);
+ knot_packet_free(&qr);
+ return 1;
+ }
- // Stop
- if (kill(pid, SIGHUP) < 0) {
+ if (r->key) {
+ remote_query_sign(buf, &buflen, qr->max_size, r->key);
+ }
+
+ /* Send query. */
+ int s = socket_create(r->family, SOCK_STREAM);
+ int conn_state = socket_connect(s, r->address, r->port);
+ if (conn_state != KNOT_EOK || tcp_send(s, buf, buflen) <= 0) {
+ log_server_error("Couldn't connect to remote host "
+ " %s@%d.\n", r->address, r->port);
+ rc = 1;
+ }
+
+ /* Wait for reply. */
+ if (rc == 0) {
+ int ret = cmd_remote_reply(s);
+ if (ret != KNOT_EOK) {
+ log_server_warning("Remote command reply: %s\n",
+ knot_strerror(ret));
rc = 1;
- } else {
- log_server_info("Server reload queued - OK.\n");
}
}
- if (strcmp(action, "refresh") == 0) {
-
- // Check PID
- valid_cmd = 1;
- if (pid <= 0 || !pid_running(pid)) {
- log_server_warning("Server PID not found, "
- "probably not running.\n");
+
+ /* Cleanup. */
+ knot_rrset_deep_free(&rr, 1, 1, 1);
+
+ /* Close connection. */
+ socket_close(s);
+ knot_packet_free(&qr);
+ return rc;
+}
- return 1;
- }
+static knot_lookup_table_t tsig_algn_tbl[] = {
+ { KNOT_TSIG_ALG_NULL, "gss-tsig" },
+ { KNOT_TSIG_ALG_HMAC_MD5, "hmac-md5" },
+ { KNOT_TSIG_ALG_HMAC_SHA1, "hmac-sha1" },
+ { KNOT_TSIG_ALG_HMAC_SHA224, "hmac-sha224" },
+ { KNOT_TSIG_ALG_HMAC_SHA256, "hmac-sha256" },
+ { KNOT_TSIG_ALG_HMAC_SHA384, "hmac-sha384" },
+ { KNOT_TSIG_ALG_HMAC_SHA512, "hmac-sha512" },
+ { KNOT_TSIG_ALG_NULL, NULL }
+};
- // Stop
- if (kill(pid, SIGUSR2) < 0) {
- rc = 1;
- } else {
- log_server_info("Zones refresh queued - OK.\n");
- }
+static int tsig_parse_str(knot_key_t *key, const char *str)
+{
+ char *h = strdup(str);
+ if (!h) {
+ return KNOT_ENOMEM;
}
- if (strcmp(action, "running") == 0) {
-
- // Check PID
- valid_cmd = 1;
- if (pid <= 0) {
- log_server_info("Server PID not found, "
- "probably not running.\n");
- rc = 1;
+
+ char *k = NULL, *s = NULL;
+ if ((k = (char*)strchr(h, ':'))) { /* Second part - NAME|SECRET */
+ *k++ = '\0'; /* String separator */
+ s = (char*)strchr(k, ':'); /* Thirt part - |SECRET */
+ }
+
+ /* Determine algorithm. */
+ key->algorithm = KNOT_TSIG_ALG_HMAC_MD5;
+ if (s) {
+ *s++ = '\0'; /* Last part separator */
+ knot_lookup_table_t *alg = NULL;
+ alg = knot_lookup_by_name(tsig_algn_tbl, h);
+ if (alg) {
+ key->algorithm = alg->id;
} else {
- if (!pid_running(pid)) {
- log_server_info("Server PID not found, "
- "probably not running.\n");
- log_server_warning("PID file is stale.\n");
- } else {
- log_server_info("Server running as PID %ld.\n",
- (long)pid);
- }
- rc = 0;
+ free(h);
+ return KNOT_EINVAL;
}
+ } else {
+ s = k; /* Ignore first part, push down. */
+ k = h;
}
- if (strcmp(action, "checkconf") == 0) {
- log_server_info("OK, configuration is valid.\n");
- rc = 0;
- valid_cmd = 1;
+
+ /* Parse key name. */
+ key->name = remote_dname_fqdn(k);
+ key->secret = strdup(s);
+ free(h);
+
+ /* Check name and secret. */
+ if (!key->name || !key->secret) {
+ return KNOT_EINVAL;
}
- if (strcmp(action, "compile") == 0 || strcmp(action, "checkzone") == 0){
-
- // Print job count
- if (jobs > 1 && argc == 0) {
- log_server_warning("Will attempt to compile %d zones "
- "in parallel, this increases memory "
- "consumption for large zones.\n",
- jobs);
- }
-
- // Zone checking
- int is_checkzone = (strcmp(action, "checkzone") == 0);
-
- // Check zone
- valid_cmd = 1;
-
- // Lock configuration
- rcu_read_lock();
-
- // Generate databases for all zones
- node *n = 0;
- int running = 0;
- knotc_zctask_t *tasks = zctask_create(jobs);
- WALK_LIST(n, conf()->zones) {
+
+ return KNOT_EOK;
+}
- // Fetch zone
- conf_zone_t *zone = (conf_zone_t*)n;
-
- // Specific zone requested
- int zone_match = 0;
- for (unsigned i = 0; i < argc; ++i) {
- size_t len = strlen(zone->name);
- if (len > 1) {
- len -= 1;
- } // All (except root) without final dot
- if (strncmp(zone->name, argv[i], len) == 0) {
- zone_match = 1;
- break;
- }
- }
- if (!zone_match && argc > 0) {
- continue;
- }
+static int tsig_parse_line(knot_key_t *k, char *l)
+{
+ const char *n, *a, *s;
+ n = a = s = NULL;
+ int fw = 1; /* 0 = reading word, 1 = between words */
+ while (*l != '\0') {
+ if (isspace(*l) || *l == '"') {
+ *l = '\0';
+ fw = 1; /* End word. */
+ } else if (fw) {
+ if (!n) { n = l; }
+ else if (!a) { a = l; }
+ else { s = l; }
+ fw = 0; /* Start word. */
+ }
+ l++;
+ }
- // Check source files and mtime
- int zone_status = check_zone(zone->db, zone->file);
- if (zone_status == KNOT_EOK && !is_checkzone) {
- log_zone_info("Zone '%s' is up-to-date.\n",
- zone->name);
-
- if (has_flag(flags, F_FORCE)) {
- log_zone_info("Forcing zone "
- "recompilation.\n");
- } else {
- continue;
- }
- }
-
- // Check for not existing source
- if (zone_status == KNOT_ENOENT) {
- continue;
- }
-
-
- /* Evaluate space for new task. */
- if (running == jobs) {
- rc |= zctask_wait(tasks, jobs, is_checkzone);
- --running;
- }
+ /* No name parsed - assume wrong format. */
+ if (!n) {
+ return KNOT_EMALF;
+ }
+
+ /* Assume hmac-md5 if no algo specified. */
+ if (!s) {
+ s = a;
+ a = "hmac-md5";
+ }
+
+ /* Set algorithm. */
+ knot_lookup_table_t *alg = knot_lookup_by_name(tsig_algn_tbl, a);
+ if (alg) {
+ k->algorithm = alg->id;
+ } else {
+ return KNOT_EMALF;
+ }
+
+ /* Set name. */
+ k->name = remote_dname_fqdn(n);
+ k->secret = strdup(s);
+
+ /* Check name and secret. */
+ if (!k->name || !k->secret) {
+ return KNOT_EINVAL;
+ }
- int ac = 0;
- const char *args[7] = { NULL };
- args[ac++] = ZONEPARSER_EXEC;
- if (zone->enable_checks) {
- args[ac++] = "-s";
- }
- if (has_flag(flags, F_VERBOSE)) {
- args[ac++] = "-v";
- }
-
- if (!is_checkzone) {
- args[ac++] = "-o";
- args[ac++] = zone->db;
- }
- args[ac++] = zone->name;
- args[ac++] = zone->file;
+ return KNOT_EOK;
+}
- // Execute command
- if (has_flag(flags, F_VERBOSE) && !is_checkzone) {
- log_zone_info("Compiling '%s' as '%s'...\n",
- zone->name, zone->db);
+static int tsig_parse_file(knot_key_t *k, const char *f)
+{
+ FILE* fp = fopen(f, "r");
+ if (!fp) {
+ log_server_error("Couldn't open key-file '%s'.\n", f);
+ return KNOT_EINVAL;
+ }
+
+ int c = 0;
+ int ret = KNOT_EOK;
+ char *line = malloc(64);
+ size_t llen = 0;
+ size_t lres = 0;
+ if (line) {
+ lres = 64;
+ }
+
+ while ((c = fgetc(fp)) != EOF) {
+ if (mreserve(&line, sizeof(char), llen + 1, 512, &lres) != 0) {
+ ret = KNOT_ENOMEM;
+ break;
+ }
+ if (c == '\n') {
+ if (k->name) {
+ log_server_error("Only 1 key definition "
+ "allowed in '%s'.\n",
+ f);
+ ret = KNOT_EMALF;
+ break;
}
- fflush(stdout);
- fflush(stderr);
- pid_t zcpid = start_cmd(args, ac, F_UNPRIVILEGED);
- zctask_add(tasks, jobs, zcpid, zone);
- ++running;
+ line[llen++] = '\0';
+ ret = tsig_parse_line(k, line);
+ llen = 0;
+ } else {
+ line[llen++] = (char)c;
}
- /* Wait for all running tasks. */
- while (running > 0) {
- rc |= zctask_wait(tasks, jobs, is_checkzone);
- --running;
- }
- free(tasks);
-
- // Unlock configuration
- rcu_read_unlock();
- }
- if (!valid_cmd) {
- log_server_error("Invalid command: '%s'\n", action);
- return 1;
}
+
+ free(line);
+ fclose(fp);
+ return ret;
+}
- // Log
- if (has_flag(flags, F_VERBOSE)) {
- log_server_info("'%s' finished (return code %d)\n", action, rc);
- }
- return rc;
+static void tsig_key_cleanup(knot_key_t *k)
+{
+ knot_dname_free(&k->name);
+ free(k->secret);
}
int main(int argc, char **argv)
{
- // Parse command line arguments
+ /* Parse command line arguments */
int c = 0, li = 0;
unsigned jobs = 1;
unsigned flags = F_NULL;
- const char* config_fn = 0;
+ char *config_fn = NULL;
+
+ /* Remote server descriptor. */
+ int ret = KNOT_EOK;
+ const char *r_addr = "127.0.0.1";
+ int r_port = REMOTE_DPORT;
+ knot_key_t r_key;
+ memset(&r_key, 0, sizeof(knot_key_t));
+
+ /* Initialize. */
+ log_init();
/* Long options. */
struct option opts[] = {
- {"wait", no_argument, 0, 'w'},
- {"force", no_argument, 0, 'f'},
- {"config", required_argument, 0, 'c'},
- {"verbose", no_argument, 0, 'v'},
- {"interactive", no_argument, 0, 'i'},
- {"auto", no_argument, 0, 'a'},
- {"jobs", required_argument, 0, 'j'},
- {"version", no_argument, 0, 'V'},
- {"help", no_argument, 0, 'h'},
+ {"wait", no_argument, 0, 'w'},
+ {"force", no_argument, 0, 'f'},
+ {"config", required_argument, 0, 'c'},
+ {"verbose", no_argument, 0, 'v'},
+ {"interactive", no_argument, 0, 'i'},
+ {"jobs", required_argument, 0, 'j'},
+ {"version", no_argument, 0, 'V'},
+ {"help", no_argument, 0, 'h'},
+ {"server", required_argument, 0, 's' },
+ {"port", required_argument, 0, 's' },
+ {"y", required_argument, 0, 'y' },
+ {"k", required_argument, 0, 'k' },
{0, 0, 0, 0}
};
-
- while ((c = getopt_long(argc, argv, "wfc:viaj:Vh", opts, &li)) != -1) {
+
+ while ((c = getopt_long(argc, argv, "s:p:y:k:wfc:vij:Vh", opts, &li)) != -1) {
switch (c) {
- case 'w': flags |= F_WAIT; break;
- case 'f': flags |= F_FORCE; break;
- case 'v': flags |= F_VERBOSE; break;
- case 'i': flags |= F_INTERACTIVE; break;
- case 'a': flags |= F_AUTO; break;
+ case 's':
+ r_addr = optarg;
+ break;
+ case 'p':
+ r_port = atoi(optarg);
+ break;
+ case 'y':
+ ret = tsig_parse_str(&r_key, optarg);
+ if (ret != KNOT_EOK) {
+ log_server_error("Couldn't parse TSIG key '%s' "
+ "\n", optarg);
+ tsig_key_cleanup(&r_key);
+ log_close();
+ return 1;
+ }
+ break;
+ case 'k':
+ ret = tsig_parse_file(&r_key, optarg);
+ if (ret != KNOT_EOK) {
+ log_server_error("Couldn't parse TSIG key file "
+ "'%s'\n", optarg);
+ tsig_key_cleanup(&r_key);
+ log_close();
+ return 1;
+ }
+ break;
+ case 'w':
+ flags |= F_WAIT;
+ break;
+ case 'f':
+ flags |= F_FORCE;
+ break;
+ case 'v':
+ flags |= F_VERBOSE;
+ break;
+ case 'i':
+ flags |= F_INTERACTIVE;
+ break;
case 'c':
- config_fn = optarg;
+ config_fn = strdup(optarg);
break;
case 'j':
jobs = atoi(optarg);
+
if (jobs < 1) {
- fprintf(stderr, "Invalid parameter '%s' to "
- "'-j', expects number <1..n>\n",
- optarg);
+ log_server_error("Invalid parameter '%s' to '-j'"
+ ", expects number <1..n>\n",
+ optarg);
help(argc, argv);
+ log_close();
return 1;
}
+
break;
case 'V':
printf("%s, version %s\n", "Knot DNS", PACKAGE_VERSION);
+ log_close();
return 0;
case 'h':
case '?':
default:
help(argc, argv);
+ log_close();
return 1;
}
}
- // Check if there's at least one remaining non-option
+ /* Check if there's at least one remaining non-option. */
if (argc - optind < 1) {
help(argc, argv);
+ tsig_key_cleanup(&r_key);
+ log_close();
+ return 1;
+ }
+
+ /* Find requested command. */
+ knot_cmd_t *cmd = knot_cmd_tbl;
+ while (cmd->name) {
+ if (strcmp(cmd->name, argv[optind]) == 0) {
+ break;
+ }
+ ++cmd;
+ }
+
+ /* Command not found. */
+ if (!cmd->name) {
+ log_server_error("Invalid command: '%s'\n", argv[optind]);
+ tsig_key_cleanup(&r_key);
+ log_close();
return 1;
}
- // Initialize log
- log_init();
+ /* Open config, allow if not exists. */
+ if (cmd->need_conf) {
+ if (!config_fn) {
+ config_fn = conf_find_default();
+ }
+ if (conf_open(config_fn) != KNOT_EOK) {
+ flags |= F_NOCONF;
+ }
+ free(config_fn);
+ config_fn = NULL;
+ }
+
+ /* Discard remote interface. */
+ conf_iface_t *ctl_if = conf()->ctl.iface;
+ conf_free_iface(ctl_if);
+
+ /* Update remote interface. */
+ ctl_if = malloc(sizeof(conf_iface_t));
+ if (ctl_if) {
+ memset(ctl_if, 0, sizeof(conf_iface_t));
+ ctl_if->address = strdup(r_addr);
+ ctl_if->port = r_port;
+ ctl_if->family = AF_INET;
+ if (strchr(r_addr, ':')) { /* Dumb way to check for v6 addr. */
+ ctl_if->family = AF_INET6;
+ }
+ if (r_key.name) {
+ ctl_if->key = malloc(sizeof(knot_key_t));
+ if (ctl_if->key) {
+ memcpy(ctl_if->key, &r_key, sizeof(knot_key_t));
+ }
+ }
+ }
+ conf()->ctl.iface = ctl_if;
- // Find implicit configuration file
- char *default_fn = 0;
- if (!config_fn) {
- default_fn = conf_find_default();
- config_fn = default_fn;
+ /* Verbose mode. */
+ if (has_flag(flags, F_VERBOSE)) {
+ log_levels_add(LOGT_STDOUT, LOG_ANY,
+ LOG_MASK(LOG_INFO) | LOG_MASK(LOG_DEBUG));
}
- // Open configuration
- int conf_ret = conf_open(config_fn);
- if (conf_ret != KNOT_EOK) {
- if (conf_ret == KNOT_ENOENT) {
- log_server_error("Couldn't open configuration file "
- "'%s'.\n", config_fn);
+ /* Execute command. */
+ int rc = cmd->cb(argc - optind - 1, argv + optind + 1, flags, jobs);
+
+ /* Finish */
+ tsig_key_cleanup(&r_key); /* Not cleaned by config deinit. */
+ log_close();
+ return rc;
+}
+
+static int cmd_start(int argc, char *argv[], unsigned flags, int jobs)
+{
+ /* Check config. */
+ if (has_flag(flags, F_NOCONF)) {
+ log_server_error("Couldn't parse config file, refusing to "
+ "continue.\n");
+ return 1;
+ }
+
+ /* Fetch PID. */
+ char *pidfile = pid_filename();
+ pid_t pid = pid_read(pidfile);
+ log_server_info("Starting server...\n");
+
+ /* Prevent concurrent daemon launch. */
+ int rc = 0;
+ struct stat st;
+ int is_pidf = 0;
+
+ /* Check PID. */
+ if (pid > 0 && pid_running(pid)) {
+ log_server_error("Server PID found, already running.\n");
+ is_pidf = 1;
+ } else if (stat(pidfile, &st) == 0) {
+ log_server_warning("PID file '%s' exists, another process "
+ "is starting or PID file is stale.\n",
+ pidfile);
+ is_pidf = 1;
+ }
+ if (is_pidf) {
+ if (!has_flag(flags, F_FORCE)) {
+ free(pidfile);
+ return 1;
} else {
- log_server_error("Failed to load configuration '%s'.\n",
- config_fn);
+ log_server_info("Forcing server start.\n");
+ pid_remove(pidfile);
+ pid = -1;
+ }
+ } else {
+ /* Create empty PID file. */
+ FILE *f = fopen(pidfile, "w");
+ if (f == NULL) {
+ log_server_warning("PID file '%s' is not writeable.\n",
+ pidfile);
+ free(pidfile);
+ return 1;
}
- free(default_fn);
+ fclose(f);
+ }
+
+ /* Recompile zones if needed. */
+ cmd_compile(argc, argv, flags, jobs);
+
+ /* Prepare command */
+ const char *cfg = conf()->filename;
+ size_t args_c = 6;
+ const char *args[] = {
+ PROJECT_EXEC,
+ has_flag(flags, F_INTERACTIVE) ? "" : "-d",
+ cfg ? "-c" : "",
+ cfg ? cfg : "",
+ has_flag(flags, F_VERBOSE) ? "-v" : "",
+ argc > 0 ? argv[0] : ""
+ };
+
+ /* Execute command */
+ if (has_flag(flags, F_INTERACTIVE)) {
+ log_server_info("Running in interactive mode.\n");
+ fflush(stderr);
+ fflush(stdout);
+ }
+ if ((rc = cmd_exec(args, args_c)) < 0) {
+ pid_remove(pidfile);
+ rc = 1;
+ }
+ fflush(stderr);
+ fflush(stdout);
+
+ /* Wait for finish */
+ if (has_flag(flags, F_WAIT) && !has_flag(flags, F_INTERACTIVE)) {
+ if (has_flag(flags, F_VERBOSE)) {
+ log_server_info("Waiting for server to load.\n");
+ }
+
+ /* Periodically read pidfile and wait for valid result. */
+ pid = 0;
+ while (pid == 0 || !pid_running(pid)) {
+ pid = pid_read(pidfile);
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 500 * 1000;
+ select(0, 0, 0, 0, &tv);
+ }
+ }
+
+ free(pidfile);
+ return rc;
+}
+
+static int cmd_stop(int argc, char *argv[], unsigned flags, int jobs)
+{
+ /* Check config. */
+ if (has_flag(flags, F_NOCONF)) {
+ log_server_error("Couldn't parse config file, refusing to "
+ "continue.\n");
return 1;
}
+
+ /* Fetch PID. */
+ char *pidfile = pid_filename();
+ pid_t pid = pid_read(pidfile);
+ int rc = 0;
+ struct stat st;
+
+ /* Check for non-existent PID file. */
+ int has_pidf = (stat(pidfile, &st) == 0);
+ if(has_pidf && pid <= 0) {
+ log_server_warning("Empty PID file '%s' exists, daemon process "
+ "is starting or PID file is stale.\n",
+ pidfile);
+ free(pidfile);
+ return 1;
+ } else if (pid <= 0 || !pid_running(pid)) {
+ log_server_warning("Server PID not found, "
+ "probably not running.\n");
+ if (!has_flag(flags, F_FORCE)) {
+ free(pidfile);
+ return 1;
+ } else {
+ log_server_info("Forcing server stop.\n");
+ }
+ }
- // Free default config filename if exists
- free(default_fn);
+ /* Stop */
+ log_server_info("Stopping server...\n");
+ if (kill(pid, SIGTERM) < 0) {
+ pid_remove(pidfile);
+ rc = 1;
+ }
+
- // Verbose mode
- if (has_flag(flags, F_VERBOSE)) {
- log_levels_add(LOGT_STDOUT, LOG_ANY,
- LOG_MASK(LOG_INFO)|LOG_MASK(LOG_DEBUG));
+ /* Wait for finish */
+ if (rc == 0 && has_flag(flags, F_WAIT)) {
+ log_server_info("Waiting for server to finish.\n");
+ while (pid_running(pid)) {
+ struct timeval tv;
+ tv.tv_sec = 0;
+ tv.tv_usec = 500 * 1000;
+ select(0, 0, 0, 0, &tv);
+ pid = pid_read(pidfile); /* Update */
+ }
}
- // Fetch PID
- char* pidfile = pid_filename();
- if (!pidfile) {
- log_server_error("No configuration found, "
- "please specify with '-c' parameter.\n");
- log_close();
+ return rc;
+}
+
+static int cmd_restart(int argc, char *argv[], unsigned flags, int jobs)
+{
+ /* Check config. */
+ if (has_flag(flags, F_NOCONF)) {
+ log_server_error("Couldn't parse config file, refusing to "
+ "continue.\n");
return 1;
}
+
+ int rc = 0;
+ rc |= cmd_stop(argc, argv, flags | F_WAIT, jobs);
+ rc |= cmd_start(argc, argv, flags, jobs);
+ return rc;
+}
- pid_t pid = pid_read(pidfile);
+static int cmd_reload(int argc, char *argv[], unsigned flags, int jobs)
+{
+ return cmd_remote("reload", KNOT_RRTYPE_TXT, 0, NULL);
+}
- // Actions
- const char* action = argv[optind];
+static int cmd_refresh(int argc, char *argv[], unsigned flags, int jobs)
+{
+ return cmd_remote("refresh", KNOT_RRTYPE_CNAME, argc, argv);
+}
- // Execute action
- int rc = execute(action, argv + optind + 1, argc - optind - 1,
- pid, flags, jobs, pidfile);
+static int cmd_flush(int argc, char *argv[], unsigned flags, int jobs)
+{
+ return cmd_remote("flush", KNOT_RRTYPE_CNAME, argc, argv);
+}
- // Finish
- free(pidfile);
- log_close();
+static int cmd_status(int argc, char *argv[], unsigned flags, int jobs)
+{
+ return cmd_remote("status", KNOT_RRTYPE_TXT, 0, NULL);
+}
+
+static int cmd_checkconf(int argc, char *argv[], unsigned flags, int jobs)
+{
+ /* Check config. */
+ if (has_flag(flags, F_NOCONF)) {
+ log_server_error("Couldn't parse config file, refusing to "
+ "continue.\n");
+ return 1;
+ } else {
+ log_server_info("OK, configuration is valid.\n");
+ }
+
+ return 0;
+}
+
+static int cmd_checkzone(int argc, char *argv[], unsigned flags, int jobs)
+{
+ return cmd_compile(argc, argv, flags | F_DRYRUN, jobs);
+}
+
+static int cmd_compile(int argc, char *argv[], unsigned flags, int jobs)
+{
+ /* Print job count */
+ if (jobs > 1 && argc == 0) {
+ log_server_warning("Will attempt to compile %d zones "
+ "in parallel, this increases memory "
+ "consumption for large zones.\n", jobs);
+ }
+
+ /* Zone checking */
+ int rc = 0;
+ node *n = 0;
+ int running = 0;
+ int is_checkzone = has_flag(flags, F_DRYRUN);
+ knotc_zctask_t *tasks = zctask_create(jobs);
+
+ /* Generate databases for all zones */
+ WALK_LIST(n, conf()->zones) {
+ /* Fetch zone */
+ conf_zone_t *zone = (conf_zone_t *) n;
+ int zone_match = 0;
+ for (unsigned i = 0; i < argc; ++i) {
+ size_t len = strlen(zone->name);
+
+ /* All (except root) without final dot */
+ if (len > 1) {
+ len -= 1;
+ }
+ if (strncmp(zone->name, argv[i], len) == 0) {
+ zone_match = 1;
+ break;
+ }
+ }
+
+ if (!zone_match && argc > 0) {
+ continue;
+ }
+
+ /* Check source files and mtime */
+ int zone_status = check_zone(zone->db, zone->file);
+ if (zone_status == KNOT_EOK && !is_checkzone) {
+ log_zone_info("Zone '%s' is up-to-date.\n", zone->name);
+ if (has_flag(flags, F_FORCE)) {
+ log_zone_info("Forcing zone "
+ "recompilation.\n");
+ } else {
+ continue;
+ }
+ }
+
+ /* Check for not existing source */
+ if (zone_status == KNOT_ENOENT) {
+ continue;
+ }
+
+ /* Evaluate space for new task. */
+ if (running == jobs) {
+ rc |= zctask_wait(tasks, jobs, is_checkzone);
+ --running;
+ }
+
+ /* Build executable command. */
+ int ac = 0;
+ const char *args[7] = { NULL };
+ args[ac++] = ZONEPARSER_EXEC;
+ if (zone->enable_checks) {
+ args[ac++] = "-s";
+ }
+ if (has_flag(flags, F_VERBOSE)) {
+ args[ac++] = "-v";
+ }
+ if (!is_checkzone) {
+ args[ac++] = "-o";
+ args[ac++] = zone->db;
+ }
+ args[ac++] = zone->name;
+ args[ac++] = zone->file;
+
+ /* Execute command */
+ if (has_flag(flags, F_VERBOSE) && !is_checkzone) {
+ log_zone_info("Compiling '%s' as '%s'...\n",
+ zone->name, zone->db);
+ }
+ fflush(stdout);
+ fflush(stderr);
+ pid_t zcpid = pid_start(args, ac,
+ has_flag(flags, F_UNPRIVILEGED));
+ zctask_add(tasks, jobs, zcpid, zone);
+ ++running;
+ }
+
+ /* Wait for all running tasks. */
+ while (running > 0) {
+ rc |= zctask_wait(tasks, jobs, is_checkzone);
+ --running;
+ }
+
+ free(tasks);
return rc;
}
diff --git a/src/knot/ctl/process.c b/src/knot/ctl/process.c
index 5eb5c2d..94306c2 100644
--- a/src/knot/ctl/process.c
+++ b/src/knot/ctl/process.c
@@ -24,6 +24,7 @@
#include <grp.h>
#include <unistd.h>
#include <assert.h>
+#include <sys/wait.h>
#include "knot/common.h"
#include "knot/ctl/process.h"
@@ -169,3 +170,63 @@ void proc_update_privileges(int uid, int gid)
}
free(lfile);
}
+
+pid_t pid_wait(pid_t proc, int *rc)
+{
+ /* Wait for finish. */
+ sigset_t newset;
+ sigfillset(&newset);
+ sigprocmask(SIG_BLOCK, &newset, 0);
+ proc = waitpid(proc, rc, 0);
+ sigprocmask(SIG_UNBLOCK, &newset, 0);
+ return proc;
+}
+
+
+pid_t pid_start(const char *argv[], int argc, int drop_privs)
+{
+ pid_t chproc = fork();
+ if (chproc == 0) {
+
+ /* Alter privileges. */
+ if (drop_privs) {
+ proc_update_privileges(conf()->uid, conf()->gid);
+ }
+
+ /* Duplicate, it doesn't run from stack address anyway. */
+ char **args = malloc((argc + 1) * sizeof(char*));
+ memset(args, 0, (argc + 1) * sizeof(char*));
+ int ci = 0;
+ for (int i = 0; i < argc; ++i) {
+ if (strlen(argv[i]) > 0) {
+ args[ci++] = strdup(argv[i]);
+ }
+ }
+ args[ci] = 0;
+
+ /* Execute command. */
+ fflush(stdout);
+ fflush(stderr);
+ execvp(args[0], args);
+
+ /* Execute failed. */
+ log_server_error("Failed to run executable '%s'\n", args[0]);
+ for (int i = 0; i < argc; ++i) {
+ free(args[i]);
+ }
+ free(args);
+
+ exit(1);
+ return -1;
+ }
+
+ return chproc;
+}
+
+int cmd_exec(const char *argv[], int argc)
+{
+ int ret = 0;
+ pid_t proc = pid_start(argv, argc, 0);
+ pid_wait(proc, &ret);
+ return ret;
+}
diff --git a/src/knot/ctl/process.h b/src/knot/ctl/process.h
index b993f72..b80b968 100644
--- a/src/knot/ctl/process.h
+++ b/src/knot/ctl/process.h
@@ -92,6 +92,39 @@ int pid_running(pid_t pid);
*/
void proc_update_privileges(int uid, int gid);
+/*!
+ * \brief Wait for process to finish.
+ *
+ * \param proc Process ID.
+ * \param rc Destination for return code.
+ *
+ * \return PID of finished process.
+ */
+pid_t pid_wait(pid_t proc, int *rc);
+
+/*!
+ * \brief Start command with given parameters.
+ *
+ * Set drop_privs = 1 to change privileges according to conf().
+ *
+ * \param argv Parameter list.
+ * \param argc Parameter count.
+ * \param drop_privs Set to 1 to alter privileges.
+ *
+ * \return PID of started process.
+ */
+pid_t pid_start(const char *argv[], int argc, int drop_privs);
+
+/*!
+ * \brief Execute command and wait for finish.
+ *
+ * \param argv Parameter list.
+ * \param argc Parameter count.
+ *
+ * \return Return code.
+ */
+int cmd_exec(const char *argv[], int argc);
+
#endif // _KNOTD_PROCESS_H_
/*! @} */
diff --git a/src/knot/ctl/remote.c b/src/knot/ctl/remote.c
new file mode 100644
index 0000000..ac1e527
--- /dev/null
+++ b/src/knot/ctl/remote.c
@@ -0,0 +1,704 @@
+/* Copyright (C) 2011 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 "remote.h"
+#include "common/log.h"
+#include "common/fdset.h"
+#include "common/prng.h"
+#include "knot/common.h"
+#include "knot/conf/conf.h"
+#include "knot/server/socket.h"
+#include "knot/server/tcp-handler.h"
+#include "knot/server/zones.h"
+#include "libknot/util/wire.h"
+#include "libknot/packet/query.h"
+#include "libknot/packet/response.h"
+#include "libknot/nameserver/name-server.h"
+#include "libknot/tsig-op.h"
+
+#define KNOT_CTL_REALM "knot."
+#define KNOT_CTL_REALM_EXT ("." KNOT_CTL_REALM)
+#define KNOT_CTL_REALM_LEN 5
+
+/*! \brief Remote command structure. */
+typedef struct remote_cmdargs_t {
+ const knot_rrset_t **arg;
+ unsigned argc;
+ knot_rcode_t rc;
+} remote_cmdargs_t;
+
+/*! \brief Callback prototype for remote commands. */
+typedef int (*remote_cmdf_t)(server_t*, remote_cmdargs_t*);
+
+/*! \brief Callback prototype for per-zone operations. */
+typedef int (remote_zonef_t)(server_t*, knot_zone_t *);
+
+/*! \brief Remote command table item. */
+typedef struct remote_cmd_t {
+ const char *name;
+ remote_cmdf_t f;
+} remote_cmd_t;
+
+/* Forward decls. */
+static int remote_c_reload(server_t *s, remote_cmdargs_t* a);
+static int remote_c_refresh(server_t *s, remote_cmdargs_t* a);
+static int remote_c_status(server_t *s, remote_cmdargs_t* a);
+static int remote_c_flush(server_t *s, remote_cmdargs_t* a);
+
+/*! \brief Table of remote commands. */
+struct remote_cmd_t remote_cmd_tbl[] = {
+ { "reload", &remote_c_reload },
+ { "refresh", &remote_c_refresh },
+ { "status", &remote_c_status },
+ { "flush", &remote_c_flush },
+ { NULL, NULL }
+};
+
+/* Private APIs. */
+
+/*! \brief Apply callback to all zones specified by RDATA of CNAME RRs. */
+static int remote_rdata_apply(server_t *s, remote_cmdargs_t* a, remote_zonef_t *cb)
+{
+ if (!s || !a || !cb) {
+ return KNOT_EINVAL;
+ }
+
+ knot_nameserver_t *ns = s->nameserver;
+ knot_zone_t *zone = NULL;
+ int ret = KNOT_EOK;
+
+ /* Refresh specific zones. */
+ for (unsigned i = 0; i < a->argc; ++i) {
+ rcu_read_lock();
+
+ /* Process all zones in data section. */
+ const knot_rrset_t *rr = a->arg[i];
+ if (knot_rrset_type(rr) != KNOT_RRTYPE_CNAME) {
+ continue;
+ }
+
+ const knot_rdata_t *rd = knot_rrset_rdata(rr);
+ while (rd != NULL) {
+ /* Skip empty nodes. */
+ if (knot_rdata_item_count(rd) < 1) {
+ rd = knot_rrset_rdata_next(rr, rd);
+ continue;
+ }
+ /* Refresh zones. */
+ const knot_dname_t *dn = knot_rdata_item(rd, 0)->dname;
+ zone = knot_zonedb_find_zone(ns->zone_db, dn);
+ if (cb(s, zone) != KNOT_EOK) {
+ a->rc = KNOT_RCODE_SERVFAIL;
+ }
+ rd = knot_rrset_rdata_next(rr, rd);
+ }
+ rcu_read_unlock();
+ }
+
+ return ret;
+}
+
+/*! \brief Zone refresh callback. */
+static int remote_zone_refresh(server_t *s, knot_zone_t *z)
+{
+ if (!s || !z) {
+ return KNOT_EINVAL;
+ }
+
+ knot_nameserver_t *ns = s->nameserver;
+ evsched_t *sch = ((server_t *)knot_ns_get_data(ns))->sched;
+ zonedata_t *zd = (zonedata_t *)knot_zone_data(z);
+ if (!sch || !zd) {
+ return KNOT_EINVAL;
+ }
+
+ /* Expire REFRESH timer. */
+ if (zd->xfr_in.timer) {
+ evsched_cancel(sch, zd->xfr_in.timer);
+ evsched_schedule(sch, zd->xfr_in.timer,
+ tls_rand() * 1000);
+ }
+
+ return KNOT_EOK;
+}
+
+/*! \brief Zone flush callback. */
+static int remote_zone_flush(server_t *s, knot_zone_t *z)
+{
+ if (!s || !z) {
+ return KNOT_EINVAL;
+ }
+
+ knot_nameserver_t *ns = s->nameserver;
+ evsched_t *sch = ((server_t *)knot_ns_get_data(ns))->sched;
+ zonedata_t *zd = (zonedata_t *)knot_zone_data(z);
+ if (!sch || !zd) {
+ return KNOT_EINVAL;
+ }
+
+ /* Expire IXFR sync timer. */
+ if (zd->ixfr_dbsync) {
+ evsched_cancel(sch, zd->ixfr_dbsync);
+ evsched_schedule(sch, zd->ixfr_dbsync,
+ tls_rand() * 1000);
+ }
+
+ return KNOT_EOK;
+}
+
+/*! \brief Helper to build RDATA from RDATA item. */
+knot_rdata_t* remote_build_rdata(knot_rdata_item_t *i)
+{
+ /* Create RDATA. */
+ knot_rdata_t *rd = knot_rdata_new();
+ if (!rd) {
+ return NULL;
+ }
+
+ /* Set RDATA items. */
+ int ret = knot_rdata_set_items(rd, i, 1);
+ if (ret != KNOT_EOK) {
+ knot_rdata_free(&rd);
+ return NULL;
+ }
+
+ return rd;
+}
+
+/*!
+ * \brief Remote command 'reload' handler.
+ *
+ * QNAME: reload
+ * DATA: NULL
+ */
+static int remote_c_reload(server_t *s, remote_cmdargs_t* a)
+{
+ return server_reload(s, conf()->filename);
+}
+
+/*!
+ * \brief Remote command 'status' handler.
+ *
+ * QNAME: refresh
+ * DATA: NONE
+ */
+static int remote_c_status(server_t *s, remote_cmdargs_t* a)
+{
+ /*! \todo #2035 Add some TXT RRs with stats. */
+ dbg_server("remote: %s\n", __func__);
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Remote command 'refresh' handler.
+ *
+ * QNAME: refresh
+ * DATA: NONE for all zones
+ * CNAME RRs with zones in RDATA
+ */
+static int remote_c_refresh(server_t *s, remote_cmdargs_t* a)
+{
+ /* Refresh all. */
+ dbg_server("remote: %s\n", __func__);
+ if (a->argc == 0) {
+ dbg_server_verb("remote: refreshing all zones\n");
+ return server_refresh(s);
+ }
+
+ /* Refresh specific zones. */
+ return remote_rdata_apply(s, a, &remote_zone_refresh);
+}
+
+/*!
+ * \brief Remote command 'flush' handler.
+ *
+ * QNAME: flush
+ * DATA: NONE for all zones
+ * CNAME RRs with zones in RDATA
+ */
+static int remote_c_flush(server_t *s, remote_cmdargs_t* a)
+{
+ /* Flush all. */
+ dbg_server("remote: %s\n", __func__);
+ if (a->argc == 0) {
+ dbg_server_verb("remote: flushing all zones\n");
+ return KNOT_ENOTSUP;
+ }
+
+ /* Flush specific zones. */
+ return remote_rdata_apply(s, a, &remote_zone_flush);
+}
+
+/* Public APIs. */
+
+int remote_bind(conf_iface_t *desc)
+{
+ if (!desc) {
+ return -1;
+ }
+
+ /* Create new socket. */
+ int s = socket_create(desc->family, SOCK_STREAM);
+ if (s < 0) {
+ log_server_error("Couldn't create socket for remote "
+ "control interface - %s",
+ knot_strerror(s));
+ return KNOT_ERROR;
+ }
+
+ /* Bind to interface and start listening. */
+ int r = socket_bind(s, desc->family, desc->address, desc->port);
+ if (r == KNOT_EOK) {
+ r = socket_listen(s, TCP_BACKLOG_SIZE);
+ }
+ if (r != KNOT_EOK) {
+ socket_close(s);
+ log_server_error("Could not bind to "
+ "remote control interface %s port %d.\n",
+ desc->address, desc->port);
+ return r;
+ }
+
+ return s;
+}
+
+int remote_unbind(int r)
+{
+ if (r < 0) {
+ return KNOT_EINVAL;
+ }
+
+ return socket_close(r);
+}
+
+int remote_poll(int r)
+{
+ if (r < 0) {
+ return -1;
+ }
+
+ /* Wait for events. */
+ fd_set rfds;
+ FD_ZERO(&rfds);
+ FD_SET(r, &rfds);
+ return fdset_pselect(r + 1, &rfds, NULL, NULL, NULL, NULL);
+}
+
+int remote_recv(int r, sockaddr_t *a, uint8_t* buf, size_t *buflen)
+{
+ int c = tcp_accept(r);
+ if (c < 0) {
+ dbg_server("remote: couldn't accept incoming connection\n");
+ return c;
+ }
+
+ /* Receive data. */
+ int n = tcp_recv(c, buf, *buflen, a);
+ *buflen = n;
+ if (n <= 0) {
+ dbg_server("remote: failed to receive data\n");
+ socket_close(c);
+ return KNOT_ECONNREFUSED;
+ }
+
+ return c;
+}
+
+int remote_parse(knot_packet_t* pkt, const uint8_t* buf, size_t buflen)
+{
+ knot_packet_type_t qtype = KNOT_QUERY_NORMAL;
+ int ret = knot_ns_parse_packet(buf, buflen, pkt, &qtype);
+ if (ret != KNOT_EOK) {
+ dbg_server("remote: failed to parse packet\n");
+ return KNOT_EINVAL;
+ }
+ ret = knot_packet_parse_rest(pkt);
+ if (ret != KNOT_EOK) {
+ dbg_server("remote: failed to parse packet data\n");
+ return KNOT_EINVAL;
+ }
+
+ return ret;
+}
+
+int remote_answer(server_t *s, knot_packet_t *pkt, uint8_t* rwire, size_t *rlen)
+{
+ if (!s || !pkt || !rwire) {
+ *rlen = 0;
+ return KNOT_EINVAL;
+ }
+
+ /* Prerequisites:
+ * QCLASS: CH
+ * QNAME: <CMD>.KNOT_CTL_REALM.
+ */
+ const knot_dname_t *qname = knot_packet_qname(pkt);
+ if (knot_packet_qclass(pkt) != KNOT_CLASS_CH) {
+ dbg_server("remote: qclass != CH\n");
+ *rlen = 0;
+ return KNOT_EMALF;
+ }
+
+ knot_dname_t *realm = knot_dname_new_from_str(KNOT_CTL_REALM,
+ KNOT_CTL_REALM_LEN, NULL);
+ if (!knot_dname_is_subdomain(qname, realm) != 0) {
+ dbg_server("remote: qname != *%s\n", KNOT_CTL_REALM_EXT);
+ knot_dname_free(&realm);
+ *rlen = 0;
+ return KNOT_EMALF;
+ }
+ knot_dname_free(&realm);
+
+ /* Command:
+ * QNAME: leftmost label of QNAME
+ */
+ size_t cmd_len = knot_dname_label_size(qname, 0);
+ char *cmd = strndup((char*)qname->name + 1, cmd_len);
+
+ /* Data:
+ * NS: TSIG
+ * AR: data
+ */
+ int ret = KNOT_EOK;
+ remote_cmd_t *c = remote_cmd_tbl;
+ remote_cmdargs_t args;
+ args.arg = pkt->authority;
+ args.argc = knot_packet_authority_rrset_count(pkt);
+ args.rc = KNOT_RCODE_NOERROR;
+ while(c->name != NULL) {
+ if (strcmp(cmd, c->name) == 0) {
+ ret = c->f(s, &args);
+ break;
+ }
+ ++c;
+ }
+
+ /* Prepare response. */
+ size_t remaining = *rlen;
+ knot_packet_t *resp = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE);
+ if (!resp) {
+ free(cmd);
+ return ret;
+ }
+ uint8_t *wire = NULL;
+ size_t len = 0;
+ ret = knot_packet_set_max_size(resp, SOCKET_MTU_SZ);
+ if (ret != KNOT_EOK) {
+ free(cmd);
+ knot_packet_free(&resp);
+ return ret;
+ }
+ ret = knot_response_init_from_query(resp, pkt, 1);
+ if (ret != KNOT_EOK) {
+ free(cmd);
+ knot_packet_free(&resp);
+ return ret;
+ }
+ ret = knot_packet_to_wire(resp, &wire, &len);
+ if (ret != KNOT_EOK) {
+ free(cmd);
+ knot_packet_free(&resp);
+ return ret;
+ }
+ if (len > 0) {
+ memcpy(rwire, wire, len);
+ *rlen = len;
+ }
+ knot_packet_free(&resp);
+
+ /* Evaluate output. */
+ int rr_count = 0;
+ knot_rrset_t *rr = remote_build_rr("result.", KNOT_RRTYPE_TXT);
+ knot_rdata_t *rd = remote_create_txt(knot_strerror(ret));
+ knot_rrset_add_rdata(rr, rd);
+
+ remaining -= *rlen;
+ knot_rrset_to_wire(rr, rwire + *rlen, &remaining, &rr_count);
+ knot_wire_set_nscount(rwire, 1);
+ *rlen += remaining;
+ knot_rrset_deep_free(&rr, 1, 1, 1);
+
+ free(cmd);
+ return ret;
+}
+
+int remote_process(server_t *s, int r, uint8_t* buf, size_t buflen)
+{
+ knot_packet_t *pkt = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY);
+ if (!pkt) {
+ dbg_server("remote: not enough space to allocate query\n");
+ return KNOT_ENOMEM;
+ }
+
+ /* Initialize remote party address. */
+ rcu_read_lock();
+ sockaddr_t a;
+ conf_iface_t *ctl_if = conf()->ctl.iface;
+ if (ctl_if) {
+ sockaddr_init(&a, ctl_if->family);
+ }
+ rcu_read_unlock();
+
+ /* Accept incoming connection and read packet. */
+ size_t wire_len = buflen;
+ int c = remote_recv(r, &a, buf, &wire_len);
+ if (c < 0) {
+ dbg_server("remote: couldn't receive query = %d\n", c);
+ knot_packet_free(&pkt);
+ return c;
+ }
+
+ /* Parse packet and answer if OK. */
+ int ret = remote_parse(pkt, buf, wire_len);
+ if (ret == KNOT_EOK) {
+
+ /* Check ACL list. */
+ rcu_read_lock();
+ knot_key_t *k = NULL;
+ acl_key_t *m = NULL;
+ knot_rcode_t ts_rc = 0;
+ uint16_t ts_trc = 0;
+ uint64_t ts_tmsigned = 0;
+ const knot_rrset_t *tsig_rr = knot_packet_tsig(pkt);
+ if (acl_match(conf()->ctl.acl, &a, &m) == ACL_DENY) {
+ knot_packet_free(&pkt);
+ socket_close(c);
+ rcu_read_unlock();
+ return KNOT_EACCES;
+ }
+ if (m && m->val) {
+ k = ((conf_iface_t *)m->val)->key;
+ }
+ rcu_read_unlock();
+
+ /* Check TSIG. */
+ if (k) {
+ if (!tsig_rr) {
+ knot_packet_free(&pkt);
+ socket_close(c);
+ return KNOT_EACCES;
+ }
+ ret = zones_verify_tsig_query(pkt, k, &ts_rc,
+ &ts_trc, &ts_tmsigned);
+ if (ret != KNOT_EOK) {
+ dbg_server("remote: failed to verify TSIG, "
+ "RC: %u TSIG_RC: %u\n",
+ ts_rc, ts_trc);
+ knot_packet_free(&pkt);
+ socket_close(c);
+ return KNOT_EACCES;
+ }
+ }
+
+ /* Answer packet. */
+ wire_len = buflen;
+ remote_answer(s, pkt, buf, &wire_len);
+ if (wire_len > 0) {
+ tcp_send(c, buf, wire_len);
+ }
+ }
+
+ knot_packet_free(&pkt);
+ socket_close(c);
+ return ret;
+}
+
+knot_packet_t* remote_query(const char *query, const knot_key_t *key)
+{
+ if (!query) {
+ return NULL;
+ }
+
+ knot_packet_t *qr = knot_packet_new(KNOT_PACKET_PREALLOC_QUERY);
+ if (!qr) {
+ return NULL;
+ }
+
+ knot_packet_set_max_size(qr, 512);
+ knot_query_init(qr);
+ knot_packet_set_random_id(qr);
+
+ /* Reserve space for TSIG. */
+ if (key) {
+ knot_packet_set_tsig_size(qr, tsig_wire_maxsize(key));
+ }
+
+ /* Question section. */
+ knot_question_t q;
+ char *qname = strcdup(query, KNOT_CTL_REALM_EXT);
+ q.qname = knot_dname_new_from_str(qname, strlen(qname), 0);
+ if (!q.qname) {
+ knot_packet_free(&qr);
+ free(qname);
+ return NULL;
+ }
+ q.qtype = KNOT_RRTYPE_ANY;
+ q.qclass = KNOT_CLASS_CH;
+
+ /* Cannot return != KNOT_EOK, but still. */
+ if (knot_query_set_question(qr, &q) != KNOT_EOK) {
+ knot_packet_free(&qr);
+ free(qname);
+ return NULL;
+ }
+
+ knot_dname_release(q.qname);
+ free(qname);
+
+ return qr;
+}
+
+int remote_query_append(knot_packet_t *qry, knot_rrset_t *data)
+{
+ if (!qry || !data) {
+ return KNOT_EINVAL;
+ }
+
+ uint8_t *sp = qry->wireformat + qry->size;
+ uint8_t *np = qry->wireformat + qry->max_size;
+ uint8_t *p = sp;
+ const knot_rdata_t *rd = knot_rrset_rdata(data);
+ while (rd != NULL) {
+ int ret = knot_query_rr_to_wire(data, rd, &p, np);
+ if (ret == KNOT_EOK) {
+ qry->header.nscount += 1;
+ }
+ rd = knot_rrset_rdata_next(data, rd);
+ }
+
+ /* Finalize packet size. */
+ qry->size += (p - sp);
+ return KNOT_EOK;
+}
+
+
+int remote_query_sign(uint8_t *wire, size_t *size, size_t maxlen,
+ const knot_key_t *key)
+{
+ if (!wire || !size || !key) {
+ return KNOT_EINVAL;
+ }
+
+ size_t dlen = tsig_alg_digest_length(key->algorithm);
+ uint8_t *digest = malloc(dlen);
+ if (!digest) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = knot_tsig_sign(wire, size, maxlen, NULL, 0, digest, &dlen,
+ key, 0, 0);
+ free(digest);
+
+ return ret;
+}
+
+knot_rrset_t* remote_build_rr(const char *k, uint16_t t)
+{
+ if (!k) {
+ return NULL;
+ }
+
+ /* Assert K is FQDN. */
+ knot_dname_t *key = remote_dname_fqdn(k);
+ if (!key) {
+ return NULL;
+ }
+
+ /* Create RRSet. */
+ knot_rrset_t *rr = knot_rrset_new(key, t, KNOT_CLASS_CH, 0);
+ knot_dname_release(key);
+ return rr;
+}
+
+knot_rdata_t* remote_create_txt(const char *v)
+{
+ if (!v) {
+ return NULL;
+ }
+
+ /* Create raw_data item. */
+ size_t v_len = strlen(v);
+ knot_rdata_item_t i;
+ i.raw_data = malloc(v_len + 3);
+ if (!i.raw_data) {
+ return NULL;
+ }
+ *i.raw_data = v_len + 1;
+
+ /* Write TXT item. */
+ uint8_t *raw_item = (uint8_t*)(i.raw_data + 1);
+ *(raw_item++) = v_len;
+ memcpy(raw_item, v, v_len);
+
+ knot_rdata_t *rd = remote_build_rdata(&i);
+ if (!rd) {
+ free(i.raw_data);
+ }
+
+ return rd;
+}
+
+knot_rdata_t* remote_create_cname(const char *d)
+{
+ if (!d) {
+ return NULL;
+ }
+
+ /* Create dname item. */
+ knot_rdata_item_t i;
+ knot_dname_t *dn = remote_dname_fqdn(d);
+ i.dname = dn;
+
+ /* Build RDATA. */
+ knot_rdata_t *rd = remote_build_rdata(&i);
+ if (!rd) {
+ knot_dname_release(dn);
+ }
+
+ return rd;
+}
+
+char* remote_parse_txt(const knot_rdata_t *rd)
+{
+ if (!rd || knot_rdata_count(rd) < 1) {
+ return NULL;
+ }
+
+ const knot_rdata_item_t *ri = knot_rdata_item(rd, 0);
+ if (!ri) {
+ return NULL;
+ }
+
+ /* Packet parser should have already checked the packet validity. */
+ uint8_t item_len = ri->raw_data[1] & 0x00ff;
+ return strndup(((const char*)ri->raw_data) + 3, item_len);
+}
+
+knot_dname_t* remote_dname_fqdn(const char *k)
+{
+ /*! \todo #2035 knot_dname_new_from_str() should ensure final '.' */
+ knot_dname_t *key = NULL;
+ size_t key_len = strlen(k);
+ if (k[key_len - 1] != '.') {
+ char *fqdn = strcdup(k, ".");
+ key = knot_dname_new_from_str(fqdn, key_len + 1, NULL);
+ free(fqdn);
+ } else {
+ key = knot_dname_new_from_str(k, key_len, NULL);
+ }
+ return key;
+}
+
diff --git a/src/knot/ctl/remote.h b/src/knot/ctl/remote.h
new file mode 100644
index 0000000..eb3eb81
--- /dev/null
+++ b/src/knot/ctl/remote.h
@@ -0,0 +1,204 @@
+/* Copyright (C) 2011 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/>.
+ */
+/*!
+ * \file remote.h
+ *
+ * \author Marek Vavrusa <marek.vavrusa@nic.cz>
+ *
+ * \brief Functions for remote control interface.
+ *
+ * \addtogroup ctl
+ * @{
+ */
+
+#ifndef _KNOTD_REMOTE_H_
+#define _KNOTD_REMOTE_H_
+
+#include <config.h>
+#include "knot/conf/conf.h"
+#include "libknot/packet/packet.h"
+#include "libknot/rrset.h"
+#include "libknot/rdata.h"
+#include "knot/server/server.h"
+
+/*! \brief Default remote control tool port. */
+#define REMOTE_DPORT 5553
+
+/*!
+ * \brief Bind RC interface according to configuration.
+ * \param desc Interface descriptor (address, port).
+ *
+ * \retval socket if passed.
+ * \retval knot_error else.
+ */
+int remote_bind(conf_iface_t *desc);
+
+/*!
+ * \brief Unbind from RC interface and close socket.
+ *
+ * \note Breaks all pending connections.
+ *
+ * \param r RC interface socket
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_unbind(int r);
+
+/*!
+ * \brief Poll new events on RC socket.
+ * \param r RC interface socket.
+ *
+ * \return number of polled events or -1 on error.
+ */
+int remote_poll(int r);
+
+/*!
+ * \brief Start a RC connection with remote.
+ *
+ * \param r RC interface socket.
+ * \param a Destination for remote party address (or NULL if not interested).
+ * \param buf Buffer for RC command.
+ * \param buflen Maximum buffer size.
+ *
+ * \return client TCP socket if success.
+ * \return KNOT_ECONNREFUSED if fails to receive command.
+ */
+int remote_recv(int r, sockaddr_t *a, uint8_t* buf, size_t *buflen);
+
+/*!
+ * \brief Parse a RC command.
+ *
+ * \param pkt Dst structure for parsed command.
+ * \param buf Remote command in wire format.
+ * \param buflen Wire format length.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_parse(knot_packet_t* pkt, const uint8_t* buf, size_t buflen);
+
+/*!
+ * \brief Execute command and prepare answer for client.
+ *
+ * \param s Server instance.
+ * \param pkt Parsed RC command.
+ * \param rwire Buffer for response.
+ * \param rlen Maximum buffer size for response.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_answer(server_t *s, knot_packet_t *pkt, uint8_t* rwire, size_t *rlen);
+
+/*!
+ * \brief Accept new client, receive command, process it and send response.
+ *
+ * \note This should be used as a high-level API for workers.
+ *
+ * \param s Server instance.
+ * \param r RC interface socket.
+ * \param buf Buffer for commands/responses.
+ * \param buflen Maximum buffer size.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_process(server_t *s, int r, uint8_t* buf, size_t buflen);
+
+/* Functions for creating RC packets. */
+
+/*!
+ * \brief Build a RC command packet, TSIG key is optional.
+ *
+ * \note This doesn't sign packet, see remote_query_sign().
+ *
+ * \param query Command name, f.e. 'reload'.
+ * \param key TSIG key for space reservation (or NULL).
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+knot_packet_t* remote_query(const char *query, const knot_key_t *key);
+
+/*!
+ * \brief Append extra data to RC command packet.
+ *
+ * \param qry RC packet.
+ * \param data Extra data in form of a RR set.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_query_append(knot_packet_t *qry, knot_rrset_t *data);
+
+/*!
+ * \brief Sign a RC command packet using TSIG key.
+ *
+ * \param wire RC packet in wire format.
+ * \param size RC packet size.
+ * \param maxlen Maximum buffer size.
+ * \param key TSIG key.
+ *
+ * \retval KNOT_EOK on success.
+ * \retval knot_error else.
+ */
+int remote_query_sign(uint8_t *wire, size_t *size, size_t maxlen,
+ const knot_key_t *key);
+
+/*! \todo #1291 RR building should be a part of DNS library. */
+
+/*!
+ * \brief Create a RR of a given name and type.
+ *
+ * \param k RR set name.
+ * \param t RR set type.
+ *
+ * \return created RR set or NULL.
+ */
+knot_rrset_t* remote_build_rr(const char *k, uint16_t t);
+
+/*!
+ * \brief Create a TXT rdata.
+ * \param v Text as a string.
+ * \return Created rdata or NULL.
+ */
+knot_rdata_t* remote_create_txt(const char *v);
+
+/*!
+ * \brief Create a CNAME rdata.
+ * \param d Domain name as a string.
+ * \return Created rdata or NULL.
+ */
+knot_rdata_t* remote_create_cname(const char *d);
+
+/*!
+ * \brief Parse TXT rdata to string.
+ * \param rd TXT rdata.
+ * \return Parsed string or NULL.
+ */
+char* remote_parse_txt(const knot_rdata_t *rd);
+
+/*!
+ * \brief Create dname from str and make sure the name is FQDN.
+ * \param k Domain name as string.
+ * \return Created FQDN or NULL.
+ */
+knot_dname_t* remote_dname_fqdn(const char *k);
+
+#endif // _KNOTD_REMOTE_H_
+
+/*! @} */
diff --git a/src/knot/main.c b/src/knot/main.c
index de37de4..b3d8b8d 100644
--- a/src/knot/main.c
+++ b/src/knot/main.c
@@ -30,9 +30,11 @@
#include "knot/common.h"
#include "knot/server/server.h"
#include "knot/ctl/process.h"
+#include "knot/ctl/remote.h"
#include "knot/conf/conf.h"
#include "knot/conf/logconf.h"
#include "knot/server/zones.h"
+#include "knot/server/tcp-handler.h"
/*----------------------------------------------------------------------------*/
@@ -300,10 +302,25 @@ int main(int argc, char **argv)
sa.sa_flags = 0;
pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
+ /* Bind to control interface. */
+ uint8_t buf[65535]; /*! \todo #2035 should be on heap */
+ size_t buflen = sizeof(buf);
+ conf_iface_t *ctl_if = conf()->ctl.iface;
+ int remote = -1;
+ if (ctl_if != NULL) {
+ log_server_info("Binding remote control interface "
+ "to %s port %d.\n",
+ ctl_if->address, ctl_if->port);
+ remote = remote_bind(ctl_if);
+ } else {
+ remote = evqueue()->fds[EVQUEUE_READFD];
+ }
+
+
/* Run event loop. */
for(;;) {
pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, NULL);
- int ret = evqueue_poll(evqueue(), 0, 0);
+ int ret = remote_poll(remote);
pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
/* Interrupts. */
@@ -313,25 +330,8 @@ int main(int argc, char **argv)
break;
}
if (sig_req_reload) {
- log_server_info("Reloading configuration...\n");
sig_req_reload = 0;
- int cf_ret = conf_open(config_fn);
- switch (cf_ret) {
- case KNOT_EOK:
- log_server_info("Configuration "
- "reloaded.\n");
- break;
- case KNOT_ENOENT:
- log_server_error("Configuration "
- "file '%s' "
- "not found.\n",
- conf()->filename);
- break;
- default:
- log_server_error("Configuration "
- "reload failed.\n");
- break;
- }
+ server_reload(server, config_fn);
}
if (sig_req_refresh) {
log_server_info("Refreshing slave zones...\n");
@@ -347,17 +347,17 @@ int main(int argc, char **argv)
/* Events. */
if (ret > 0) {
- event_t ev;
- if (evqueue_get(evqueue(), &ev) == 0) {
- dbg_server_verb("Event: "
- "received new event.\n");
- if (ev.cb) {
- ev.cb(&ev);
- }
- }
+ remote_process(server, remote, buf, buflen);
+
}
}
pthread_sigmask(SIG_UNBLOCK, &sa.sa_mask, NULL);
+
+ /* Close remote control interface */
+ if (remote > -1) {
+ close(remote);
+ remote = -1;
+ }
if ((server_wait(server)) != KNOT_EOK) {
log_server_error("An error occured while "
diff --git a/src/knot/server/journal.c b/src/knot/server/journal.c
index ac393cf..1623bf5 100644
--- a/src/knot/server/journal.c
+++ b/src/knot/server/journal.c
@@ -715,16 +715,21 @@ int journal_read(journal_t *journal, uint64_t id, journal_cmp_t cf, char *dst)
return KNOT_ENOENT;
}
+ return journal_read_node(journal, n, dst);
+}
+
+int journal_read_node(journal_t *journal, journal_node_t *n, char *dst)
+{
+ dbg_journal("journal: reading node with id=%llu, data=<%u, %u>, flags=0x%hx\n",
+ n->id, n->pos, n->pos + n->len, n->flags);
+
/* Check valid flag. */
if (!(n->flags & JOURNAL_VALID)) {
dbg_journal("journal: node with id=%llu is invalid "
"(flags=0x%hx)\n", (unsigned long long)n->id, n->flags);
return KNOT_EINVAL;
}
-
- dbg_journal("journal: reading node with id=%llu, data=<%u, %u>, flags=0x%hx\n",
- (unsigned long long)id, n->pos, n->pos + n->len, n->flags);
-
+
/* Seek journal node. */
int seek_ret = lseek(journal->fd, n->pos, SEEK_SET);
@@ -732,7 +737,7 @@ int journal_read(journal_t *journal, uint64_t id, journal_cmp_t cf, char *dst)
if (seek_ret < 0 || !sfread(dst, n->len, journal->fd)) {
return KNOT_ERROR;
}
-
+
return KNOT_EOK;
}
diff --git a/src/knot/server/journal.h b/src/knot/server/journal.h
index f6716ae..9462809 100644
--- a/src/knot/server/journal.h
+++ b/src/knot/server/journal.h
@@ -129,7 +129,7 @@ typedef int (*journal_apply_t)(journal_t *j, journal_node_t *n);
#define JOURNAL_NCOUNT 1024 /*!< Default node count. */
/* HEADER = magic, crc, max_entries, qhead, qtail */
#define JOURNAL_HSIZE (MAGIC_LENGTH + sizeof(crc_t) + sizeof(uint16_t) * 3)
-#define JOURNAL_MAGIC {'k', 'n', 'o', 't', '1', '0', '4'}
+#define JOURNAL_MAGIC {'k', 'n', 'o', 't', '1', '0', '5'}
/*!
* \brief Create new journal.
@@ -186,6 +186,21 @@ int journal_fetch(journal_t *journal, uint64_t id,
int journal_read(journal_t *journal, uint64_t id, journal_cmp_t cf, char *dst);
/*!
+ * \brief Read journal entry data.
+ *
+ * \param journal Associated journal.
+ * \param n Entry.
+ * \param dst Pointer to destination memory.
+ *
+ * \retval KNOT_EOK if successful.
+ * \retval KNOT_ENOENT if the entry cannot be found.
+ * \retval KNOT_EINVAL if the entry is invalid.
+ * \retval KNOT_ERROR on I/O error.
+ */
+int journal_read_node(journal_t *journal, journal_node_t *n, char *dst);
+
+
+/*!
* \brief Write journal entry data.
*
* \param journal Associated journal.
diff --git a/src/knot/server/notify.c b/src/knot/server/notify.c
index 381b1b2..3eb99a0 100644
--- a/src/knot/server/notify.c
+++ b/src/knot/server/notify.c
@@ -123,7 +123,7 @@ int notify_create_response(knot_packet_t *request, uint8_t *buffer,
/* Set maximum packet size. */
int rc = knot_packet_set_max_size(response, *size);
if (rc == KNOT_EOK) {
- rc = knot_response_init_from_query(response, request);
+ rc = knot_response_init_from_query(response, request, 1);
}
/* Aggregated result check. */
diff --git a/src/knot/server/server.c b/src/knot/server/server.c
index 5a792c7..e42460f 100644
--- a/src/knot/server/server.c
+++ b/src/knot/server/server.c
@@ -635,7 +635,8 @@ int server_refresh(server_t *server)
if (zd->xfr_in.timer) {
evsched_cancel(sch, zd->xfr_in.timer);
evsched_schedule(sch, zd->xfr_in.timer,
- tls_rand() * 1000);
+ tls_rand() * 500 + i*200);
+ /* Cumulative delay. */
}
}
@@ -645,6 +646,35 @@ int server_refresh(server_t *server)
return KNOT_EOK;
}
+int server_reload(server_t *server, const char *cf)
+{
+ if (!server || !cf) {
+ return KNOT_EINVAL;
+ }
+
+ log_server_info("Reloading configuration...\n");
+ int cf_ret = conf_open(cf);
+ switch (cf_ret) {
+ case KNOT_EOK:
+ log_server_info("Configuration "
+ "reloaded.\n");
+ break;
+ case KNOT_ENOENT:
+ log_server_error("Configuration "
+ "file '%s' "
+ "not found.\n",
+ conf()->filename);
+ break;
+ default:
+ log_server_error("Configuration "
+ "reload failed.\n");
+ break;
+ }
+
+ /*! \todo Close and bind to new remote control. */
+ return cf_ret;
+}
+
void server_stop(server_t *server)
{
dbg_server("server: stopping server\n");
diff --git a/src/knot/server/server.h b/src/knot/server/server.h
index a28be63..fa7597f 100644
--- a/src/knot/server/server.h
+++ b/src/knot/server/server.h
@@ -189,6 +189,15 @@ int server_wait(server_t *server);
int server_refresh(server_t *server);
/*!
+ * \brief Reload server configuration.
+ *
+ * \param server Server instance.
+ * \param cf Config file path.
+ * \return
+ */
+int server_reload(server_t *server, const char *cf);
+
+/*!
* \brief Requests server to stop.
*
* \param server Server structure to be used for operation.
diff --git a/src/knot/server/socket.c b/src/knot/server/socket.c
index 5f4f936..6a32d4d 100644
--- a/src/knot/server/socket.c
+++ b/src/knot/server/socket.c
@@ -55,6 +55,7 @@ int socket_connect(int fd, const char *addr, unsigned short port)
/* Resolve address. */
int ret = KNOT_EOK;
struct addrinfo hints, *res;
+ memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((ret = getaddrinfo(addr, NULL, &hints, &res)) != 0) {
@@ -202,4 +203,3 @@ int socket_close(int socket)
return KNOT_EOK;
}
-
diff --git a/src/knot/server/socket.h b/src/knot/server/socket.h
index 1266c53..8bbd6cc 100644
--- a/src/knot/server/socket.h
+++ b/src/knot/server/socket.h
@@ -114,7 +114,6 @@ int socket_listen(int fd, int backlog_size);
*/
int socket_close(int fd);
-
#endif // _KNOTD_SOCKET_H_
/*! @} */
diff --git a/src/knot/server/tcp-handler.c b/src/knot/server/tcp-handler.c
index 11628bc..06f42c2 100644
--- a/src/knot/server/tcp-handler.c
+++ b/src/knot/server/tcp-handler.c
@@ -170,9 +170,11 @@ static int tcp_handle(tcp_worker_t *w, int fd, uint8_t *qbuf, size_t qbuf_maxlen
char r_addr[SOCKADDR_STRLEN];
sockaddr_tostr(&addr, r_addr, sizeof(r_addr));
int r_port = sockaddr_portnum(&addr);
+ rcu_read_lock();
log_server_warning("Couldn't receive query from '%s@%d'"
" within the time limit of %ds.\n",
- r_addr, r_port, TCP_ACTIVITY_WD);
+ r_addr, r_port, conf()->max_conn_idle);
+ rcu_read_unlock();
}
return KNOT_ECONNREFUSED;
}
@@ -250,10 +252,12 @@ static int tcp_handle(tcp_worker_t *w, int fd, uint8_t *qbuf, size_t qbuf_maxlen
return xfr_answer(ns, &xfr);
case KNOT_QUERY_UPDATE:
- knot_ns_error_response_from_query(ns, packet,
- KNOT_RCODE_NOTIMPL,
- qbuf, &resp_len);
- res = KNOT_EOK;
+// knot_ns_error_response_from_query(ns, packet,
+// KNOT_RCODE_NOTIMPL,
+// qbuf, &resp_len);
+ res = zones_process_update(ns, packet, &addr, qbuf, &resp_len,
+ fd, NS_TRANSPORT_TCP);
+// res = KNOT_EOK;
break;
case KNOT_QUERY_NOTIFY:
@@ -294,7 +298,7 @@ static int tcp_handle(tcp_worker_t *w, int fd, uint8_t *qbuf, size_t qbuf_maxlen
return res;
}
-static int tcp_accept(int fd)
+int tcp_accept(int fd)
{
/* Accept incoming connection. */
int incoming = accept(fd, 0, 0);
@@ -322,7 +326,9 @@ static int tcp_accept(int fd)
/* Set recv() timeout. */
#ifdef SO_RCVTIMEO
struct timeval tv;
- tv.tv_sec = TCP_ACTIVITY_WD;
+ rcu_read_lock();
+ tv.tv_sec = conf()->max_conn_idle;
+ rcu_read_unlock();
tv.tv_usec = 0;
if (setsockopt(incoming, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
log_server_warning("Couldn't set up TCP connection "
@@ -554,6 +560,12 @@ int tcp_loop_worker(dthread_t *thread)
if (nfds < 0) {
continue;
}
+
+ /* Establish timeouts. */
+ rcu_read_lock();
+ int max_idle = conf()->max_conn_idle;
+ int max_hs = conf()->max_conn_hs;
+ rcu_read_unlock();
/* Process incoming events. */
dbg_net_verb("tcp: worker %p registered %d events\n",
@@ -574,19 +586,19 @@ int tcp_loop_worker(dthread_t *thread)
w, client);
fdset_add(w->fdset, client, OS_EV_READ);
fdset_set_watchdog(w->fdset, client,
- TCP_HANDSHAKE_WD);
+ max_hs);
dbg_net("tcp: watchdog for fd=%d set to %ds\n",
- client, TCP_HANDSHAKE_WD);
+ client, max_hs);
} else {
/* Handle other events. */
int ret = tcp_handle(w, it.fd, qbuf,
TCP_BUFFER_SIZE);
if (ret == KNOT_EOK) {
fdset_set_watchdog(w->fdset, it.fd,
- TCP_ACTIVITY_WD);
+ max_idle);
dbg_net("tcp: watchdog for fd=%d "
"set to %ds\n",
- it.fd, TCP_ACTIVITY_WD);
+ it.fd, max_idle);
}
/*! \todo Refactor to allow erase on iterator.*/
if (ret == KNOT_ECONNREFUSED) {
diff --git a/src/knot/server/tcp-handler.h b/src/knot/server/tcp-handler.h
index 17a7293..6c38e0a 100644
--- a/src/knot/server/tcp-handler.h
+++ b/src/knot/server/tcp-handler.h
@@ -38,11 +38,18 @@
#include "knot/server/dthreads.h"
/* Constants */
-#define TCP_HANDSHAKE_WD 10 /* [secs] for connection to make a request.*/
-#define TCP_ACTIVITY_WD 60 /* [secs] of allowed inactivity between requests */
#define TCP_SWEEP_INTERVAL 2 /* [secs] granularity of connection sweeping */
/*!
+ * \brief Accept a TCP connection.
+ * \param fd Associated socket.
+ *
+ * \retval Created connection fd if success.
+ * \retval <0 on error.
+ */
+int tcp_accept(int fd);
+
+/*!
* \brief Send TCP message.
*
* \param fd Associated socket.
diff --git a/src/knot/server/udp-handler.c b/src/knot/server/udp-handler.c
index d0a21bb..e34c710 100644
--- a/src/knot/server/udp-handler.c
+++ b/src/knot/server/udp-handler.c
@@ -162,11 +162,12 @@ int udp_handle(int fd, uint8_t *qbuf, size_t qbuflen, size_t *resp_len,
break;
case KNOT_QUERY_UPDATE:
- dbg_net("udp: UPDATE query on fd=%d not implemented\n", fd);
- knot_ns_error_response_from_query(ns, packet,
- KNOT_RCODE_NOTIMPL, qbuf,
- resp_len);
- res = KNOT_EOK;
+// dbg_net("udp: UPDATE query on fd=%d not implemented\n", fd);
+// knot_ns_error_response_from_query(ns, packet,
+// KNOT_RCODE_NOTIMPL, qbuf,
+// resp_len);
+ res = zones_process_update(ns, packet, addr, qbuf, resp_len,
+ fd, NS_TRANSPORT_UDP);
break;
/* Unhandled opcodes. */
diff --git a/src/knot/server/xfr-handler.c b/src/knot/server/xfr-handler.c
index 5e8c31a..6a9a249 100644
--- a/src/knot/server/xfr-handler.c
+++ b/src/knot/server/xfr-handler.c
@@ -34,13 +34,13 @@
#include "knot/server/udp-handler.h"
#include "knot/server/tcp-handler.h"
#include "libknot/updates/xfr-in.h"
+#include "libknot/util/wire.h"
#include "knot/server/zones.h"
#include "libknot/tsig-op.h"
#include "common/evsched.h"
#include "common/prng.h"
/* Constants */
-#define XFR_QUERY_WD 10 /*!< SOA/NOTIFY query timeout [s]. */
#define XFR_SWEEP_INTERVAL 2 /*! [seconds] between sweeps. */
#define XFR_BUFFER_SIZE 65535 /*! Do not change this - maximum value for UDP packet length. */
#define XFR_MSG_DLTTR 9 /*! Index of letter differentiating IXFR/AXFR in log msg. */
@@ -251,13 +251,19 @@ static int xfr_process_udp_resp(xfrworker_t *w, int fd, knot_ns_xfr_t *data)
rcu_read_unlock();
/* Receive msg. */
- ssize_t n = recvfrom(data->session, data->wire, data->wire_size,
- 0, data->addr.ptr, &data->addr.len);
+ ssize_t n = -1;
size_t resp_len = data->wire_size;
+ if (data->flags & XFR_FLAG_TCP) {
+ n = tcp_recv(data->session, data->wire, resp_len, &data->addr);
+ } else {
+ n = recvfrom(data->session, data->wire, resp_len,
+ 0, data->addr.ptr, &data->addr.len);
+ }
+
if (n <= 0) {
return ret;
}
-
+
// parse packet
knot_packet_t *re = knot_packet_new(KNOT_PACKET_PREALLOC_RESPONSE);
if (re == NULL) {
@@ -270,12 +276,18 @@ static int xfr_process_udp_resp(xfrworker_t *w, int fd, knot_ns_xfr_t *data)
knot_packet_free(&re);
return KNOT_EOK; /* Ignore */
}
-
+
/* Ignore other packets. */
- if (rt != KNOT_RESPONSE_NORMAL && rt != KNOT_RESPONSE_NOTIFY) {
+ switch(rt) {
+ case KNOT_RESPONSE_NORMAL:
+ case KNOT_RESPONSE_NOTIFY:
+ case KNOT_RESPONSE_UPDATE:
+ break;
+ default:
knot_packet_free(&re);
return KNOT_EOK; /* Ignore */
}
+
ret = knot_packet_parse_rest(re);
if (ret != KNOT_EOK) {
knot_packet_free(&re);
@@ -302,6 +314,7 @@ static int xfr_process_udp_resp(xfrworker_t *w, int fd, knot_ns_xfr_t *data)
}
// process response
+ size_t qlen = n;
switch(rt) {
case KNOT_RESPONSE_NORMAL:
ret = zones_process_response(w->ns, &data->addr, re,
@@ -311,10 +324,17 @@ static int xfr_process_udp_resp(xfrworker_t *w, int fd, knot_ns_xfr_t *data)
ret = notify_process_response(w->ns, re, &data->addr,
data->wire, &resp_len);
break;
+ case KNOT_RESPONSE_UPDATE:
+ ret = zones_process_update_response(data, data->wire, &qlen);
+ if (ret == KNOT_EOK) {
+ log_server_info("%s Forwarded response.\n",
+ data->msgpref);
+ }
+ break;
default:
break;
}
-
+
knot_packet_free(&re);
/* Check up-to-date zone. */
@@ -349,6 +369,7 @@ static void xfr_sweep(fdset_t *set, int fd, void *data)
switch(t->type) {
case XFR_TYPE_SOA:
case XFR_TYPE_NOTIFY:
+ case XFR_TYPE_FORWARD:
xfr_udp_timeout(t);
break;
default:
@@ -420,10 +441,10 @@ static int xfr_xfrin_finalize(xfrworker_t *w, knot_ns_xfr_t *data)
{
int ret = KNOT_EOK;
- int apply_ret = KNOT_EOK;
+// int apply_ret = KNOT_EOK;
int switch_ret = KNOT_EOK;
knot_changesets_t *chs = NULL;
- journal_t *transaction = NULL;
+// journal_t *transaction = NULL;
switch(data->type) {
case XFR_TYPE_AIN:
@@ -450,82 +471,11 @@ static int xfr_xfrin_finalize(xfrworker_t *w, knot_ns_xfr_t *data)
break;
case XFR_TYPE_IIN:
chs = (knot_changesets_t *)data->data;
-
- /* Serialize and store changesets. */
- dbg_xfr("xfr: IXFR/IN serializing and saving changesets\n");
- transaction = zones_store_changesets_begin(data);
- if (transaction != NULL) {
- ret = zones_store_changesets(data);
- } else {
- ret = KNOT_ERROR;
- }
- if (ret != KNOT_EOK) {
- log_zone_error("%s Failed to serialize and store "
- "changesets - %s\n", data->msgpref,
- knot_strerror(ret));
- /* Free changesets, but not the data. */
- knot_free_changesets(&chs);
- data->data = NULL;
- break;
- }
-
- /* Now, try to apply the changesets to the zone. */
- apply_ret = xfrin_apply_changesets(data->zone, chs,
- &data->new_contents);
-
- if (apply_ret != KNOT_EOK) {
- zones_store_changesets_rollback(transaction);
- log_zone_error("%s Failed to apply changesets - %s\n",
- data->msgpref,
- knot_strerror(apply_ret));
-
- /* Free changesets, but not the data. */
- knot_free_changesets(&chs);
- data->data = NULL;
- ret = KNOT_ERROR;
- break;
- }
-
- /* Commit transaction. */
- ret = zones_store_changesets_commit(transaction);
- if (ret != KNOT_EOK) {
- log_zone_error("%s Failed to commit stored changesets "
- "- %s\n",
- data->msgpref,
- knot_strerror(apply_ret));
- knot_free_changesets(&chs);
- data->data = NULL;
- break;
- }
-
- /* Switch zone contents. */
- switch_ret = xfrin_switch_zone(data->zone, data->new_contents,
- data->type);
-
- if (switch_ret != KNOT_EOK) {
- log_zone_error("%s Failed to replace "
- "current zone - %s\n",
- data->msgpref,
- knot_strerror(switch_ret));
- // Cleanup old and new contents
- xfrin_rollback_update(data->zone->contents,
- &data->new_contents,
- &chs->changes);
-
- /* Free changesets, but not the data. */
- knot_free_changesets(&chs);
- data->data = NULL;
- ret = KNOT_ERROR;
- break;
- }
-
- xfrin_cleanup_successful_update(&chs->changes);
-
- /* Free changesets, but not the data. */
- knot_free_changesets(&chs);
+ ret = zones_store_and_apply_chgsets(chs, data->zone,
+ &data->new_contents,
+ data->msgpref,
+ XFR_TYPE_IIN);
data->data = NULL;
- assert(ret == KNOT_EOK);
- log_zone_info("%s Finished.\n", data->msgpref);
break;
default:
ret = KNOT_EINVAL;
@@ -687,8 +637,13 @@ int xfr_process_event(xfrworker_t *w, int fd, knot_ns_xfr_t *data, uint8_t *buf,
data->wire_size = buflen;
/* Handle SOA/NOTIFY responses. */
- if (data->type == XFR_TYPE_NOTIFY || data->type == XFR_TYPE_SOA) {
+ switch(data->type) {
+ case XFR_TYPE_NOTIFY:
+ case XFR_TYPE_SOA:
+ case XFR_TYPE_FORWARD:
return xfr_process_udp_resp(w, fd, data);
+ default:
+ break;
}
/* Read DNS/TCP packet. */
@@ -890,9 +845,15 @@ static int xfr_client_start(xfrworker_t *w, knot_ns_xfr_t *data)
data->msgpref);
} else {
ret = connect(fd, data->addr.ptr, data->addr.len);
+ if (ret < 0) {
+ dbg_xfr("%s: couldn't connect to "
+ "remote host\n", data->msgpref);
+ }
}
} else {
ret = -1;
+ dbg_xfr("%s: couldn't create socket err=%d\n",
+ data->msgpref, errno);
}
if (ret < 0) {
@@ -1085,31 +1046,17 @@ static int xfr_update_msgpref(knot_ns_xfr_t *req, const char *keytag)
if (req == NULL) {
return KNOT_EINVAL;
}
-
+
rcu_read_lock();
- char r_addr[SOCKADDR_STRLEN];
char *r_key = NULL;
- int r_port = sockaddr_portnum(&req->addr);
- sockaddr_tostr(&req->addr, r_addr, sizeof(r_addr));
- char *tag = NULL;
if (keytag) {
- tag = strdup(keytag);
+ r_key = xfr_remote_str(&req->addr, keytag);
} else if (req->tsig_key) {
- tag = knot_dname_to_str(req->tsig_key->name);
- }
- if (tag) {
- /* Allocate: " key '$key' " (7 extra bytes + \0) */
- size_t dnlen = strlen(tag);
- r_key = malloc(dnlen + 7 + 1);
- if (r_key) {
- char *kp = r_key;
- memcpy(kp, " key '", 6); kp += 6;
- /* Trim trailing '.' */
- memcpy(kp, tag, dnlen); kp += dnlen - 1;
- memcpy(kp, "'", 2); /* 1 + '\0' */
- }
+ char *tag = knot_dname_to_str(req->tsig_key->name);
+ r_key = xfr_remote_str(&req->addr, tag);
free(tag);
- tag = NULL;
+ } else {
+ r_key = xfr_remote_str(&req->addr, NULL);
}
/* Prepare log message. */
@@ -1127,40 +1074,33 @@ static int xfr_update_msgpref(knot_ns_xfr_t *req, const char *keytag)
const char *pformat = NULL;
switch (req->type) {
case XFR_TYPE_AIN:
- pformat = "Incoming AXFR transfer of '%s' with '%s@%d'%s:";
+ pformat = "Incoming AXFR transfer of '%s' with %s:";
break;
case XFR_TYPE_IIN:
- pformat = "Incoming IXFR transfer of '%s' with '%s@%d'%s:";
+ pformat = "Incoming IXFR transfer of '%s' with %s:";
break;
case XFR_TYPE_AOUT:
- pformat = "Outgoing AXFR transfer of '%s' to '%s@%d'%s:";
+ pformat = "Outgoing AXFR transfer of '%s' to %s:";
break;
case XFR_TYPE_IOUT:
- pformat = "Outgoing IXFR transfer of '%s' to '%s@%d'%s:";
+ pformat = "Outgoing IXFR transfer of '%s' to %s:";
break;
case XFR_TYPE_NOTIFY:
- pformat = "NOTIFY query of '%s' to '%s@%d'%s:";
+ pformat = "NOTIFY query of '%s' to %s:";
break;
case XFR_TYPE_SOA:
- pformat = "SOA query of '%s' to '%s@%d'%s:";
+ pformat = "SOA query of '%s' to %s:";
+ break;
+ case XFR_TYPE_FORWARD:
+ pformat = "UPDATE forwarded query of '%s' to %s:";
break;
default:
- pformat = "";
+ pformat = "UNKNOWN query '%s' from %s:";
break;
}
- int len = 512;
- char *msg = malloc(len + 1);
+ char *msg = sprintf_alloc(pformat, zname, r_key ? r_key : "'unknown'");
if (msg) {
- memset(msg, 0, len + 1);
- len = snprintf(msg, len + 1, pformat, zname, r_addr, r_port,
- r_key ? r_key : "");
- /* Shorten as some implementations (<C99) don't allow
- * printing to NULL to estimate size. */
- if (len > 0) {
- msg = realloc(msg, len + 1);
- }
-
req->msgpref = msg;
}
@@ -1441,7 +1381,6 @@ int xfr_answer(knot_nameserver_t *ns, knot_ns_xfr_t *xfr)
}
/* Finally, answer AXFR/IXFR. */
- int io_error = 0;
if (!xfr_failed) {
switch(xfr->type) {
case XFR_TYPE_AOUT:
@@ -1457,14 +1396,15 @@ int xfr_answer(knot_nameserver_t *ns, knot_ns_xfr_t *xfr)
xfr_failed = (ret != KNOT_EOK);
errstr = knot_strerror(ret);
- io_error = (ret == KNOT_ECONN);
+ } else {
+ knot_ns_error_response_from_query(ns, xfr->query, xfr->rcode,
+ xfr->wire, &xfr->wire_size);
+ /*! \todo Sign with TSIG for some errors. */
+ ret = xfr->send(xfr->session, &xfr->addr, xfr->wire, xfr->wire_size);
}
/* Check results. */
if (xfr_failed) {
- if (!io_error) {
- knot_ns_xfr_send_error(ns, xfr, xfr->rcode);
- }
log_server_notice("%s %s\n", xfr->msgpref, errstr);
} else {
log_server_info("%s Finished.\n", xfr->msgpref);
@@ -1532,9 +1472,9 @@ static int xfr_process_request(xfrworker_t *w, uint8_t *buf, size_t buflen)
/* Report. */
if (ret != KNOT_EOK && ret != KNOT_EACCES) {
if (zd != NULL && !knot_zone_contents(xfr.zone)) {
- /* Reschedule request (120 - 240s random delay). */
- int tmr_s = AXFR_BOOTSTRAP_RETRY * 2; /* Malus x2 */
- tmr_s += (int)((120.0 * 1000) * tls_rand());
+ /* Reschedule request delay. */
+ int tmr_s = AXFR_BOOTSTRAP_RETRY;
+ tmr_s += (int)((tmr_s) * tls_rand());
event_t *ev = zd->xfr_in.timer;
if (ev) {
evsched_cancel(ev->parent, ev);
@@ -1553,6 +1493,7 @@ static int xfr_process_request(xfrworker_t *w, uint8_t *buf, size_t buflen)
break;
case XFR_TYPE_SOA:
case XFR_TYPE_NOTIFY:
+ case XFR_TYPE_FORWARD:
/* Register task. */
task = xfr_register_task(w, &xfr);
if (!task) {
@@ -1560,8 +1501,17 @@ static int xfr_process_request(xfrworker_t *w, uint8_t *buf, size_t buflen)
ret = KNOT_ENOMEM;
} else {
/* Add timeout. */
- fdset_set_watchdog(w->fdset, task->session, XFR_QUERY_WD);
- log_server_info("%s Query issued.\n", xfr.msgpref);
+ rcu_read_lock();
+ fdset_set_watchdog(w->fdset, task->session,
+ conf()->max_conn_reply);
+ rcu_read_unlock();
+ if (xfr.type == XFR_TYPE_FORWARD) {
+ log_server_info("%s Forwarded query.\n",
+ xfr.msgpref);
+ } else {
+ log_server_info("%s Query issued.\n",
+ xfr.msgpref);
+ }
ret = KNOT_EOK;
}
break;
@@ -1715,3 +1665,27 @@ int xfr_prepare_tsig(knot_ns_xfr_t *xfr, knot_key_t *key)
return ret;
}
+
+char *xfr_remote_str(const sockaddr_t *addr, const char *key)
+{
+ if (!addr) {
+ return NULL;
+ }
+
+ /* Prepare address strings. */
+ char r_addr[SOCKADDR_STRLEN];
+ int r_port = sockaddr_portnum(addr);
+ sockaddr_tostr(addr, r_addr, sizeof(r_addr));
+
+ /* Prepare key strings. */
+ char *tag = "";
+ char *q = "'";
+ if (key) {
+ tag = " key "; /* Prefix */
+ } else {
+ key = tag; /* Both empty. */
+ q = tag;
+ }
+
+ return sprintf_alloc("'%s@%d'%s%s%s%s", r_addr, r_port, tag, q, key, q);
+}
diff --git a/src/knot/server/xfr-handler.h b/src/knot/server/xfr-handler.h
index 581f47f..9f52752 100644
--- a/src/knot/server/xfr-handler.h
+++ b/src/knot/server/xfr-handler.h
@@ -181,6 +181,16 @@ int xfr_worker(dthread_t *thread);
*/
int xfr_prepare_tsig(knot_ns_xfr_t *xfr, knot_key_t *key);
+/*!
+ * \brief Return formatted string of the remote as 'ip@port key $key'.
+ *
+ * \param addr Remote address.
+ * \param keytag Used TSIG key name (or NULL).
+ *
+ * \return formatted string or NULL.
+ */
+char *xfr_remote_str(const sockaddr_t *addr, const char *keytag);
+
#endif // _KNOTD_XFRHANDLER_H_
/*! @} */
diff --git a/src/knot/server/zones.c b/src/knot/server/zones.c
index 930f611..935f3a4 100644
--- a/src/knot/server/zones.c
+++ b/src/knot/server/zones.c
@@ -38,6 +38,7 @@
#include "libknot/tsig-op.h"
#include "libknot/packet/response.h"
#include "libknot/zone/zone-diff.h"
+#include "libknot/updates/ddns.h"
static const size_t XFRIN_CHANGESET_BINARY_SIZE = 100;
static const size_t XFRIN_CHANGESET_BINARY_STEP = 100;
@@ -59,6 +60,11 @@ static int zones_send_cb(int fd, sockaddr_t *addr, uint8_t *msg, size_t msglen)
return tcp_send(fd, msg, msglen);
}
+static int zones_send_udp(int fd, sockaddr_t *addr, uint8_t *msg, size_t msglen)
+{
+ return sendto(fd, msg, msglen, 0, addr->ptr, addr->len);
+}
+
/*----------------------------------------------------------------------------*/
/*! \brief Zone data destructor function. */
@@ -90,7 +96,7 @@ static int zonedata_destroy(knot_zone_t *zone)
evsched_event_free(sch, zd->xfr_in.expire);
zd->xfr_in.expire = 0;
}
-
+
/* Remove list of pending NOTIFYs. */
pthread_mutex_lock(&zd->lock);
notify_ev_t *ev = 0, *evn = 0;
@@ -115,6 +121,7 @@ static int zonedata_destroy(knot_zone_t *zone)
acl_delete(&zd->xfr_out);
acl_delete(&zd->notify_in);
acl_delete(&zd->notify_out);
+ acl_delete(&zd->update_in);
/* Close IXFR db. */
journal_release(zd->ixfr_db);
@@ -150,9 +157,10 @@ static int zonedata_init(conf_zone_t *cfg, knot_zone_t *zone)
pthread_mutex_init(&zd->lock, 0);
/* Initialize ACLs. */
- zd->xfr_out = 0;
- zd->notify_in = 0;
- zd->notify_out = 0;
+ zd->xfr_out = NULL;
+ zd->notify_in = NULL;
+ zd->notify_out = NULL;
+ zd->update_in = NULL;
/* Initialize XFR-IN. */
sockaddr_init(&zd->xfr_in.master, -1);
@@ -1049,6 +1057,8 @@ static inline uint64_t ixfrdb_key_make(uint32_t from, uint32_t to)
int zones_changesets_from_binary(knot_changesets_t *chgsets)
{
+ /*! \todo #1291 Why doesn't this just increment stream ptr? */
+
assert(chgsets != NULL);
assert(chgsets->allocated >= chgsets->count);
/*
@@ -1059,11 +1069,16 @@ int zones_changesets_from_binary(knot_changesets_t *chgsets)
int ret = 0;
for (int i = 0; i < chgsets->count; ++i) {
-
- /* Read initial changeset RRSet - SOA. */
+
+ /* Read changeset flags. */
knot_changeset_t* chs = chgsets->sets + i;
size_t remaining = chs->size;
- ret = knot_zload_rrset_deserialize(&rrset, chs->data, &remaining);
+ memcpy(&chs->flags, chs->data, sizeof(uint32_t));
+ remaining -= sizeof(uint32_t);
+
+ /* Read initial changeset RRSet - SOA. */
+ uint8_t *stream = chs->data + (chs->size - remaining);
+ ret = knot_zload_rrset_deserialize(&rrset, stream, &remaining);
if (ret != KNOT_EOK) {
dbg_xfr("xfr: SOA: failed to deserialize data "
"from changeset, %s\n", knot_strerror(ret));
@@ -1088,7 +1103,7 @@ int zones_changesets_from_binary(knot_changesets_t *chgsets)
/* Parse next RRSet. */
rrset = 0;
- uint8_t *stream = chs->data + (chs->size - remaining);
+ stream = chs->data + (chs->size - remaining);
ret = knot_zload_rrset_deserialize(&rrset, stream, &remaining);
if (ret != KNOT_EOK) {
dbg_xfr("xfr: failed to deserialize data "
@@ -1213,6 +1228,12 @@ static int zones_load_changesets(const knot_zone_t *zone,
journal_release(j);
return KNOT_ERROR;
}
+
+ /* Skip wrong changesets. */
+ if (!(n->flags & JOURNAL_VALID) || n->flags & JOURNAL_TRANS) {
+ ++n;
+ continue;
+ }
/* Initialize changeset. */
dbg_xfr_detail("xfr: reading entry #%zu id=%llu\n",
@@ -1227,7 +1248,7 @@ static int zones_load_changesets(const knot_zone_t *zone,
}
/* Read journal entry. */
- ret = journal_read(j, n->id, 0, (char*)chs->data);
+ ret = journal_read_node(j, n, (char*)chs->data);
if (ret != KNOT_EOK) {
dbg_xfr("xfr: failed to read data from journal\n");
free(chs->data);
@@ -1517,11 +1538,13 @@ static int zones_insert_zone(conf_zone_t *z, knot_zone_t **dst,
zones_set_acl(&zd->xfr_out, &z->acl.xfr_out);
zones_set_acl(&zd->notify_in, &z->acl.notify_in);
zones_set_acl(&zd->notify_out, &z->acl.notify_out);
+ zones_set_acl(&zd->update_in, &z->acl.update_in);
/* Update server pointer. */
zd->server = (server_t *)knot_ns_get_data(ns);
/* Update master server address. */
+ zd->xfr_in.has_master = 0;
memset(&zd->xfr_in.tsig_key, 0, sizeof(knot_key_t));
sockaddr_init(&zd->xfr_in.master, -1);
sockaddr_init(&zd->xfr_in.via, -1);
@@ -1536,6 +1559,7 @@ static int zones_insert_zone(conf_zone_t *z, knot_zone_t **dst,
sockaddr_copy(&zd->xfr_in.via,
&cfg_if->via);
}
+ zd->xfr_in.has_master = 1;
if (cfg_if->key) {
memcpy(&zd->xfr_in.tsig_key,
@@ -1822,6 +1846,7 @@ dbg_zones_exec(
WALK_LIST_FREE(zconf->acl.xfr_out);
WALK_LIST_FREE(zconf->acl.notify_in);
WALK_LIST_FREE(zconf->acl.notify_out);
+ WALK_LIST_FREE(zconf->acl.update_in);
/* Remove from zone db. */
knot_zone_t * rm = knot_zonedb_remove_zone(db_old,
@@ -1837,117 +1862,6 @@ dbg_zones_exec(
/*----------------------------------------------------------------------------*/
-static int zones_verify_tsig_query(const knot_packet_t *query,
- const knot_rrset_t *tsig_rr,
- const knot_key_t *key,
- knot_rcode_t *rcode, uint16_t *tsig_rcode,
- uint64_t *tsig_prev_time_signed)
-{
- assert(tsig_rr != NULL);
- assert(key != NULL);
- assert(rcode != NULL);
- assert(tsig_rcode != NULL);
-
- /*
- * 1) Check if we support the requested algorithm.
- */
- tsig_algorithm_t alg = tsig_rdata_alg(tsig_rr);
- if (tsig_alg_digest_length(alg) == 0) {
- log_answer_info("Unsupported digest algorithm "
- "requested, treating as bad key\n");
- /*! \todo [TSIG] It is unclear from RFC if I
- * should treat is as a bad key
- * or some other error.
- */
- *rcode = KNOT_RCODE_NOTAUTH;
- *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
- return KNOT_TSIG_EBADKEY;
- }
-
- const knot_dname_t *kname = knot_rrset_owner(tsig_rr);
- assert(kname != NULL);
-
- /*
- * 2) Find the particular key used by the TSIG.
- * Check not only name, but also the algorithm.
- */
- if (key && kname && knot_dname_compare(key->name, kname) == 0
- && key->algorithm == alg) {
- dbg_zones_verb("Found claimed TSIG key for comparison\n");
- } else {
- *rcode = KNOT_RCODE_NOTAUTH;
- *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
- return KNOT_TSIG_EBADKEY;
- }
-
- /*
- * 3) Validate the query with TSIG.
- */
- /* Prepare variables for TSIG */
- /*! \todo These need to be saved to the response somehow. */
- //size_t tsig_size = tsig_wire_maxsize(key);
- size_t digest_max_size = tsig_alg_digest_length(key->algorithm);
- //size_t digest_size = 0;
- //uint64_t tsig_prev_time_signed = 0;
- //uint8_t *digest = (uint8_t *)malloc(digest_max_size);
- //memset(digest, 0 , digest_max_size);
-
- /* Copy MAC from query. */
- dbg_zones_verb("Validating TSIG from query\n");
-
- //const uint8_t* mac = tsig_rdata_mac(tsig_rr);
- size_t mac_len = tsig_rdata_mac_length(tsig_rr);
-
- int ret = KNOT_EOK;
-
- if (mac_len > digest_max_size) {
- *rcode = KNOT_RCODE_FORMERR;
- dbg_zones("MAC length %zu exceeds digest "
- "maximum size %zu\n", mac_len, digest_max_size);
- return KNOT_EMALF;
- } else {
- //memcpy(digest, mac, mac_len);
- //digest_size = mac_len;
-
- /* Check query TSIG. */
- ret = knot_tsig_server_check(tsig_rr,
- knot_packet_wireformat(query),
- knot_packet_size(query), key);
- dbg_zones_verb("knot_tsig_server_check() returned %s\n",
- knot_strerror(ret));
-
- /* Evaluate TSIG check results. */
- switch(ret) {
- case KNOT_EOK:
- *rcode = KNOT_RCODE_NOERROR;
- break;
- case KNOT_TSIG_EBADKEY:
- *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
- *rcode = KNOT_RCODE_NOTAUTH;
- break;
- case KNOT_TSIG_EBADSIG:
- *tsig_rcode = KNOT_TSIG_RCODE_BADSIG;
- *rcode = KNOT_RCODE_NOTAUTH;
- break;
- case KNOT_TSIG_EBADTIME:
- *tsig_rcode = KNOT_TSIG_RCODE_BADTIME;
- // store the time signed from the query
- *tsig_prev_time_signed = tsig_rdata_time_signed(tsig_rr);
- *rcode = KNOT_RCODE_NOTAUTH;
- break;
- case KNOT_EMALF:
- *rcode = KNOT_RCODE_FORMERR;
- break;
- default:
- *rcode = KNOT_RCODE_SERVFAIL;
- }
- }
-
- return ret;
-}
-
-/*----------------------------------------------------------------------------*/
-
static int zones_check_tsig_query(const knot_zone_t *zone,
knot_packet_t *query,
const sockaddr_t *addr,
@@ -1961,34 +1875,11 @@ static int zones_check_tsig_query(const knot_zone_t *zone,
assert(rcode != NULL);
assert(tsig_key_zone != NULL);
- const knot_rrset_t *tsig = knot_packet_tsig(query);
-
- // not required, TSIG is already found
-// if (knot_packet_additional_rrset_count(query) > 0) {
-// /*! \todo warning */
-// tsig = knot_packet_additional_rrset(query,
-// knot_packet_additional_rrset_count(query) - 1);
-// if (knot_rrset_type(tsig) == KNOT_RRTYPE_TSIG) {
-// dbg_zones_verb("found TSIG in normal query\n");
-// } else {
-// tsig = NULL; /* Invalidate if not TSIG RRTYPE. */
-// }
-// }
-
- if (tsig == NULL) {
- // no TSIG, this is completely valid
- /*! \note This function is (should be) called only in case of
- normal query, i.e. we do not have to check ACL.
- */
- *tsig_rcode = 0;
- return KNOT_EOK;
- }
-
// if there is some TSIG in the query, find the TSIG associated with
// the zone
dbg_zones_verb("Checking zone and ACL.\n");
- int ret = zones_query_check_zone(zone, addr, tsig_key_zone, rcode,
- knot_packet_opcode(query));
+ int ret = zones_query_check_zone(zone, knot_packet_opcode(query),
+ addr, tsig_key_zone, rcode);
/* Accept found OR unknown key results. */
@@ -1996,15 +1887,19 @@ static int zones_check_tsig_query(const knot_zone_t *zone,
if (*tsig_key_zone != NULL) {
// everything OK, so check TSIG
dbg_zones_verb("Verifying TSIG.\n");
- ret = zones_verify_tsig_query(query, tsig, *tsig_key_zone,
+ ret = zones_verify_tsig_query(query, *tsig_key_zone,
rcode, tsig_rcode,
tsig_prev_time_signed);
} else {
dbg_zones_verb("No key configured for zone.\n");
- // no key configured for zone, return BADKEY
- *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
- *rcode = KNOT_RCODE_NOTAUTH;
- ret = KNOT_TSIG_EBADKEY;
+ if (knot_packet_tsig(query)) {
+ // no key configured for zone, return BADKEY
+ dbg_zones_verb("TSIG used, but not configured "
+ "for this zone, ret=BADKEY.\n");
+ *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ *rcode = KNOT_RCODE_NOTAUTH;
+ ret = KNOT_TSIG_EBADKEY;
+ }
}
}
@@ -2014,6 +1909,355 @@ static int zones_check_tsig_query(const knot_zone_t *zone,
return ret;
}
+static int zones_update_forward(int fd, knot_ns_transport_t ttype,
+ knot_zone_t *zone, const sockaddr_t *from,
+ knot_packet_t *query, size_t qsize)
+{
+ /*! \todo #1291 #1999 This is really the same as for NOTIFY+SOA, should
+ * use common API. */
+
+ int ret = KNOT_EOK;
+ int orig_id = (int)knot_packet_id(query);
+ rcu_read_lock();
+
+ zonedata_t *zd = (zonedata_t *)knot_zone_data(zone);
+
+ /* Create socket on random port. */
+ sockaddr_t *master = &zd->xfr_in.master;
+ int stype = SOCK_DGRAM;
+ if (ttype == NS_TRANSPORT_TCP) {
+ stype = SOCK_STREAM;
+ }
+ int nfd = socket_create(master->family, stype);
+
+ /* Check requested source. */
+ char strbuf[256] = "Generic error.";
+ sockaddr_t *via = &zd->xfr_in.via;
+ if (via->len > 0) {
+ if (bind(nfd, via->ptr, via->len) < 0) {
+ socket_close(nfd);
+ nfd = -1;
+ char r_addr[SOCKADDR_STRLEN];
+ sockaddr_tostr(via, r_addr, sizeof(r_addr));
+ snprintf(strbuf, sizeof(strbuf),
+ "Couldn't bind to \'%s\'", r_addr);
+ }
+ }
+
+ /* Store query as pending. */
+ knot_ns_xfr_t req;
+ memset(&req, 0, sizeof(knot_ns_xfr_t));
+ req.session = nfd;
+ req.fwd_src_fd = fd;
+ req.type = XFR_TYPE_FORWARD;
+ if (ttype == NS_TRANSPORT_TCP) {
+ req.flags |= XFR_FLAG_TCP;
+ req.send = zones_send_cb;
+ } else {
+ req.flags |= XFR_FLAG_UDP;
+ req.send = zones_send_udp;
+ }
+ req.zone = zone;
+
+ /* Create FORWARD query and send to primary. */
+ uint8_t *rwire = malloc(qsize);
+ if (rwire) {
+ ret = knot_ns_create_forward_query(query, rwire, &qsize);
+ } else {
+ ret = KNOT_ENOMEM;
+ }
+ if (nfd > -1) {
+ /* Connect on TCP. */
+ if (ttype == NS_TRANSPORT_TCP) {
+ if (connect(nfd, master->ptr, master->len) < 0) {
+ ret = KNOT_ECONNREFUSED;
+ }
+ }
+
+ int sent = 0;
+ if (ret == KNOT_EOK) {
+ sent = req.send(nfd, master, rwire, qsize);
+ }
+
+ /* Store ID of the awaited response. */
+ if (sent == qsize) {
+ ret = KNOT_EOK;
+ } else {
+ strbuf[0] = '\0';
+ ret = KNOT_ECONNREFUSED;
+ }
+ }
+ if (ret != KNOT_EOK) {
+ if (nfd > -1) {
+ socket_close(nfd);
+ }
+ dbg_zones("update: failed to create FORWARD qry '%s'\n",
+ knot_strerror(ret));
+ rcu_read_unlock();
+ free(rwire);
+ return KNOT_ENOMEM;
+ }
+ free(rwire);
+
+ req.packet_nr = orig_id;
+ memcpy(&req.addr, master, sizeof(sockaddr_t));
+ memcpy(&req.saddr, from, sizeof(sockaddr_t));
+ sockaddr_update(&req.addr);
+ sockaddr_update(&req.saddr);
+
+ /* Retain pointer to zone and issue. */
+ knot_zone_retain(req.zone);
+ if (ret == KNOT_EOK) {
+ ret = xfr_request(zd->server->xfr_h, &req);
+ }
+ if (ret != KNOT_EOK) {
+ knot_zone_release(req.zone); /* Discard */
+ log_server_warning("Failed to forward UPDATE query for zone '%s' (%s).\n",
+ zd->conf->name, strbuf);
+ }
+
+ rcu_read_unlock();
+ return KNOT_EOK;
+}
+
+
+
+/*----------------------------------------------------------------------------*/
+
+static int zones_store_changesets_to_disk(knot_zone_t *zone,
+ knot_changesets_t *chgsets)
+{
+ journal_t *journal = zones_store_changesets_begin(zone);
+ if (journal == NULL) {
+ dbg_zones("zones: create_changesets: "
+ "Could not start journal operation.\n");
+ return KNOT_ERROR;
+ }
+
+ int ret = zones_store_changesets(zone, chgsets);
+ if (ret != KNOT_EOK) {
+ zones_store_changesets_rollback(journal);
+ dbg_zones("zones: create_changesets: "
+ "Could not store in the journal. Reason: %s.\n",
+ knot_strerror(ret));
+
+ return ret;
+ }
+
+ ret = zones_store_changesets_commit(journal);
+ if (ret != KNOT_EOK) {
+ dbg_zones("zones: create_changesets: "
+ "Could not commit to journal. Reason: %s.\n",
+ knot_strerror(ret));
+
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*! \brief Process UPDATE query.
+ *
+ * Functions expects that the query is already authenticated
+ * and TSIG signature is verified.
+ *
+ * \note Set parameter 'rcode' according to answering procedure.
+ * \note Function expects RCU to be locked.
+ *
+ * \retval KNOT_EOK if successful.
+ * \retval error if not.
+ */
+static int zones_process_update_auth(knot_zone_t *zone,
+ knot_packet_t *resp,
+ uint8_t *resp_wire, size_t *rsize,
+ knot_rcode_t *rcode,
+ const sockaddr_t *addr,
+ knot_key_t *tsig_key)
+{
+ int ret = KNOT_EOK;
+ dbg_zones_verb("TSIG check successful. Answering query.\n");
+
+ /* Create log message prefix. */
+ char *keytag = NULL;
+ if (tsig_key) {
+ keytag = knot_dname_to_str(tsig_key->name);
+ }
+ char *r_str = xfr_remote_str(addr, keytag);
+ const char *zone_name = ((zonedata_t*)knot_zone_data(zone))->conf->name;
+ char *msg = sprintf_alloc("UPDATE of '%s' from %s:",
+ zone_name, r_str ? r_str : "'unknown'");
+ free(r_str);
+ free(keytag);
+ log_zone_info("%s Started.\n", msg);
+
+
+ /* Reserve place for the TSIG */
+ if (tsig_key != NULL) {
+ size_t tsig_max_size = tsig_wire_maxsize(tsig_key);
+ knot_packet_set_tsig_size(resp, tsig_max_size);
+ }
+
+ /* We must prepare a changesets_t structure even if
+ * there is only one changeset - because of the API. */
+ knot_changesets_t *chgsets = NULL;
+ ret = knot_changeset_allocate(&chgsets, KNOT_CHANGESET_TYPE_DDNS);
+ if (ret != KNOT_EOK) {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ log_zone_error("%s %s\n", msg, knot_strerror(ret));
+ free(msg);
+ return ret;
+ }
+
+ assert(chgsets->allocated >= 1);
+
+ /*
+ * NEW DDNS PROCESSING -------------------------------------------------
+ */
+ /* 1) Process the UPDATE packet, apply to zone, create changesets. */
+
+ dbg_zones_verb("Processing UPDATE packet.\n");
+ chgsets->count = 1; /* DU is represented by a single chset. */
+
+ knot_zone_contents_t *new_contents = NULL;
+ ret = knot_ns_process_update2(knot_packet_query(resp),
+ knot_zone_get_contents(zone),
+ &new_contents,
+ chgsets, rcode);
+
+ if (ret != KNOT_EOK) {
+ if (ret < 0) {
+ log_zone_error("%s %s\n", msg, knot_strerror(ret));
+ } else {
+ log_zone_notice("%s: No change to zone made.\n", msg);
+ knot_response_set_rcode(resp, KNOT_RCODE_NOERROR);
+ uint8_t *tmp_wire = NULL;
+ ret = knot_packet_to_wire(resp, &tmp_wire, rsize);
+ if (ret != KNOT_EOK) {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ } else {
+ memcpy(resp_wire, tmp_wire, *rsize);
+ *rcode = KNOT_RCODE_NOERROR;
+ }
+ }
+
+ knot_free_changesets(&chgsets);
+ free(msg);
+ return (ret < 0) ? ret : KNOT_EOK;
+ }
+
+ /* 2) Store changesets, (TODO: but do not commit???). */
+ ret = zones_store_changesets_to_disk(zone, chgsets);
+ if (ret != KNOT_EOK) {
+ log_zone_error("%s %s\n", msg, knot_strerror(ret));
+ xfrin_rollback_update(zone->contents, &new_contents,
+ &chgsets->changes);
+ knot_free_changesets(&chgsets);
+ free(msg);
+ return ret;
+ }
+
+ /* 3) Switch zone contents. */
+ knot_zone_retain(zone); /* Retain pointer for safe RCU unlock. */
+ rcu_read_unlock(); /* Unlock for switch. */
+ ret = xfrin_switch_zone(zone, new_contents, XFR_TYPE_UPDATE);
+ rcu_read_lock(); /* Relock */
+ knot_zone_release(zone);/* Release held pointer. */
+
+ if (ret != KNOT_EOK) {
+ log_zone_error("%s: Failed to replace current zone - %s\n",
+ msg, knot_strerror(ret));
+ // Cleanup old and new contents
+ xfrin_rollback_update(zone->contents, &new_contents,
+ &chgsets->changes);
+
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chgsets);
+ return KNOT_ERROR;
+ }
+
+ /* 4) Cleanup. */
+
+ xfrin_cleanup_successful_update(&chgsets->changes);
+
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chgsets);
+ assert(ret == KNOT_EOK);
+ log_zone_info("%s: Finished.\n", msg);
+
+ free(msg);
+ msg = NULL;
+
+ /*
+ * \NEW DDNS PROCESSING ------------------------------------------------
+ */
+
+
+// /* 1) Process the incoming packet, prepare
+// * prerequisities and changeset.
+// */
+// dbg_zones_verb("Processing UPDATE packet.\n");
+// chgsets->count = 1; /* DU is represented by a single chset. */
+// ret = knot_ns_process_update(knot_packet_query(resp),
+// knot_zone_contents(zone),
+// &chgsets->sets[0], rcode);
+
+// if (ret != KNOT_EOK) {
+// log_zone_error("%s %s\n", msg, knot_strerror(ret));
+// knot_free_changesets(&chgsets);
+// free(msg);
+// return ret;
+// }
+
+// /* 2) Save changeset to journal.
+// * Apply changeset to zone.
+// * Commit changeset to journal.
+// * Switch the zone.
+// */
+// knot_zone_contents_t *contents_new = NULL;
+// knot_zone_retain(zone); /* Retain pointer for safe RCU unlock. */
+// rcu_read_unlock(); /* Unlock for switch. */
+// dbg_zones_verb("Storing and applying changesets.\n");
+// ret = zones_store_and_apply_chgsets(chgsets, zone, &contents_new, msg,
+// XFR_TYPE_UPDATE);
+// rcu_read_lock(); /* Relock */
+// knot_zone_release(zone);/* Release held pointer. */
+// free(msg);
+// msg = NULL;
+
+// /* Changesets should be freed by now. */
+// if (ret != KNOT_EOK) {
+// dbg_zones_verb("Storing and applying changesets failed: %s.\n",
+// knot_strerror(ret));
+// *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
+// : KNOT_RCODE_SERVFAIL;
+// return ret;
+// }
+
+ /* 3) Prepare DDNS response. */
+ assert(*rcode == KNOT_RCODE_NOERROR);
+ dbg_zones_verb("Preparing NOERROR UPDATE response RCODE=%u "
+ "pkt=%p resp_wire=%p\n", *rcode, resp, resp_wire);
+ knot_response_set_rcode(resp, KNOT_RCODE_NOERROR);
+ uint8_t *tmp_wire = NULL;
+ ret = knot_packet_to_wire(resp, &tmp_wire, rsize);
+ if (ret != KNOT_EOK) {
+ dbg_zones("DDNS failed to write pkt to wire (%s). Size %zu\n",
+ knot_strerror(ret), *rsize);
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ } else {
+ /* This is strange, but the knot_packet_to_wire() can't write
+ * to already existing buffer. */
+ memcpy(resp_wire, tmp_wire, *rsize);
+ }
+
+ dbg_zones("DDNS reply rsize = %zu\n", *rsize);
+
+
+ return ret;
+}
+
/*----------------------------------------------------------------------------*/
/* API functions */
/*----------------------------------------------------------------------------*/
@@ -2191,9 +2435,9 @@ int zones_zonefile_sync(knot_zone_t *zone, journal_t *journal)
/*----------------------------------------------------------------------------*/
-int zones_query_check_zone(const knot_zone_t *zone, const sockaddr_t *addr,
- knot_key_t **tsig_key, knot_rcode_t *rcode,
- uint8_t q_opcode)
+int zones_query_check_zone(const knot_zone_t *zone, uint8_t q_opcode,
+ const sockaddr_t *addr, knot_key_t **tsig_key,
+ knot_rcode_t *rcode)
{
if (addr == NULL || tsig_key == NULL || rcode == NULL) {
dbg_zones_verb("Wrong arguments.\n");
@@ -2212,14 +2456,20 @@ int zones_query_check_zone(const knot_zone_t *zone, const sockaddr_t *addr,
return KNOT_ERROR;
}
- /* Check xfr-out ACL */
+ /* Check ACL (xfr-out for xfers, update-in for DDNS) */
+ acl_t *acl_used = zd->xfr_out;
+ if (q_opcode == KNOT_OPCODE_UPDATE) {
+ acl_used = zd->update_in;
+ }
acl_key_t *match = NULL;
- if (acl_match(zd->xfr_out, addr, &match) == ACL_DENY) {
+ if (acl_match(acl_used, addr, &match) == ACL_DENY) {
*rcode = KNOT_RCODE_REFUSED;
return KNOT_EACCES;
} else {
dbg_zones("zones: authorized query or request for "
- "'%s %s'. match=%p\n", zd->conf->name, "XFR/OUT", match);
+ "'%s %s'. match=%p\n", zd->conf->name,
+ q_opcode == KNOT_OPCODE_UPDATE ? "UPDATE":"XFR/OUT",
+ match);
if (match) {
/* Save configured TSIG key for comparison. */
conf_iface_t *iface = (conf_iface_t*)(match->val);
@@ -2253,12 +2503,16 @@ int zones_xfr_check_zone(knot_ns_xfr_t *xfr, knot_rcode_t *rcode)
return KNOT_EEXPIRED;
}
- return zones_query_check_zone(xfr->zone, &xfr->addr, &xfr->tsig_key,
- rcode, knot_packet_opcode(xfr->query));
+ return zones_query_check_zone(xfr->zone, KNOT_OPCODE_QUERY,
+ &xfr->addr, &xfr->tsig_key,
+ rcode);
}
/*----------------------------------------------------------------------------*/
-
+/*! \todo This function is here only because TSIG key is associated with the
+ * zone via zonedata. If it was in the zone structure (which would be
+ * IMHO ok, this whole function could be moved to nameserver.c.
+ */
int zones_normal_query_answer(knot_nameserver_t *nameserver,
knot_packet_t *query, const sockaddr_t *addr,
uint8_t *resp_wire, size_t *rsize,
@@ -2347,10 +2601,17 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
ret = KNOT_TSIG_EBADKEY;
} else {
dbg_zones_verb("Checking TSIG in query.\n");
- ret = zones_check_tsig_query(zone, query, addr,
- &rcode, &tsig_rcode,
- &tsig_key_zone,
- &tsig_prev_time_signed);
+ const knot_rrset_t *tsig = knot_packet_tsig(query);
+ if (tsig == NULL) {
+ // no TSIG, this is completely valid
+ tsig_rcode = 0;
+ ret = KNOT_EOK;
+ } else {
+ ret = zones_check_tsig_query(zone, query, addr,
+ &rcode, &tsig_rcode,
+ &tsig_key_zone,
+ &tsig_prev_time_signed);
+ }
}
if (ret == KNOT_EOK) {
@@ -2511,6 +2772,163 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
/*----------------------------------------------------------------------------*/
+int zones_process_update(knot_nameserver_t *nameserver,
+ knot_packet_t *query, const sockaddr_t *addr,
+ uint8_t *resp_wire, size_t *rsize,
+ int fd, knot_ns_transport_t transport)
+{
+ rcu_read_lock();
+
+ knot_packet_t *resp = NULL;
+ knot_zone_t *zone = NULL;
+ knot_rcode_t rcode = KNOT_RCODE_NOERROR;
+ size_t rsize_max = *rsize;
+ knot_key_t *tsig_key_zone = NULL;
+ uint16_t tsig_rcode = 0;
+ uint64_t tsig_prev_time_signed = 0;
+ const knot_rrset_t *tsig_rr = NULL;
+
+ // Parse rest of the query, prepare response, find zone
+ int ret = knot_ns_prep_update_response(nameserver, query, &resp, &zone,
+ (transport == NS_TRANSPORT_TCP)
+ ? *rsize : 0);
+ dbg_zones_verb("Preparing response structure = %s\n", knot_strerror(ret));
+ switch (ret) {
+ case KNOT_EOK: break;
+ case KNOT_EMALF: /* No TSIG signing in this case. */
+ rcode = KNOT_RCODE_FORMERR;
+ break;
+ default:
+ rcode = KNOT_RCODE_SERVFAIL;
+ break;
+ }
+
+ /* Check if zone is valid. */
+ const knot_zone_contents_t *contents = knot_zone_contents(zone);
+ if (zone && (knot_zone_flags(zone) & KNOT_ZONE_DISCARDED)) {
+ rcode = KNOT_RCODE_SERVFAIL; /* It's ok, temporarily. */
+ tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ ret = KNOT_ENOZONE;
+ } else if (!zone || !contents) { /* Treat as BADKEY. */
+ rcode = KNOT_RCODE_NOTAUTH;
+ tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ ret = KNOT_TSIG_EBADKEY;
+ dbg_zones_verb("No zone or empty, refusing UPDATE.\n");
+ }
+
+ /* Verify TSIG if it is in the packet. */
+ tsig_rr = knot_packet_tsig(query);
+ if (ret == KNOT_EOK) { /* Have valid zone to check ACLs against. */
+ dbg_zones_verb("Checking TSIG in query.\n");
+ ret = zones_check_tsig_query(zone, query, addr,
+ &rcode, &tsig_rcode,
+ &tsig_key_zone,
+ &tsig_prev_time_signed);
+ }
+
+ /* Allow pass-through of an unknown TSIG in DDNS forwarding (must have zone). */
+ if (zone && (ret == KNOT_EOK || (ret == KNOT_TSIG_EBADKEY && !tsig_key_zone))) {
+ /* Transaction is authenticated (or unprotected)
+ * and zone has primary master set,
+ * proceed to forward the query to the next hop.
+ */
+ zonedata_t *zd = (zonedata_t *)knot_zone_data(zone);
+ if (zd->xfr_in.has_master) {
+ ret = zones_update_forward(fd, transport, zone, addr,
+ query, *rsize);
+ *rsize = 0; /* Do not send reply immediately. */
+ knot_packet_free(&resp);
+ rcu_read_unlock();
+ return ret;
+ }
+ }
+
+ /*
+ * 1) DDNS Zone Section check (RFC2136, Section 3.1).
+ */
+ if (ret == KNOT_EOK) {
+ ret = knot_ddns_check_zone(contents, query, &rcode);
+ dbg_zones_verb("Checking zone = %s\n", knot_strerror(ret));
+ }
+
+ /*
+ * 2) DDNS Prerequisities Section processing (RFC2136, Section 3.2).
+ *
+ * \note Permissions section means probably policies and fine grained
+ * access control, not transaction security.
+ */
+ knot_ddns_prereq_t *prereqs = NULL;
+ if (ret == KNOT_EOK) {
+ ret = knot_ddns_process_prereqs(query, &prereqs, &rcode);
+ dbg_zones_verb("Processing prereq = %s\n", knot_strerror(ret));
+ }
+ if (ret == KNOT_EOK) {
+ assert(prereqs != NULL);
+ ret = knot_ddns_check_prereqs(contents, &prereqs, &rcode);
+ dbg_zones_verb("Checking prereq = %s\n", knot_strerror(ret));
+ knot_ddns_prereqs_free(&prereqs);
+ }
+
+ /*
+ * 3) Process query.
+ */
+ if (ret == KNOT_EOK) {
+ zonedata_t *zd = (zonedata_t *)knot_zone_data(zone);
+ pthread_mutex_lock(&zd->xfr_in.lock);
+
+ /*! \note This function expects RCU locked. */
+ ret = zones_process_update_auth(zone, resp, resp_wire, rsize,
+ &rcode, addr, tsig_key_zone);
+ dbg_zones_verb("Auth, update_proc = %s\n", knot_strerror(ret));
+
+ pthread_mutex_unlock(&zd->xfr_in.lock);
+ }
+
+ /* Create error query if processing failed. */
+ if (ret != KNOT_EOK) {
+ ret = knot_ns_error_response_from_query(nameserver,
+ query, rcode,
+ resp_wire, rsize);
+ }
+
+ /* No response, no signing required or FORMERR. */
+ if (*rsize == 0 || !tsig_rr || rcode == KNOT_RCODE_FORMERR) {
+ knot_packet_free(&resp);
+ rcu_read_unlock();
+ return ret;
+ }
+
+ /* Just add TSIG RR on most errors. */
+ if (tsig_rcode != 0 && tsig_rcode != KNOT_TSIG_RCODE_BADTIME) {
+ ret = knot_tsig_add(resp_wire, rsize, rsize_max,
+ tsig_rcode, tsig_rr);
+ dbg_zones_verb("Adding TSIG = %s\n", knot_strerror(ret));
+ } else if (tsig_key_zone) {
+ dbg_zones_verb("Signing message with TSIG.\n");
+ size_t digest_len = tsig_alg_digest_length(tsig_key_zone->algorithm);
+ uint8_t *digest = (uint8_t *)malloc(digest_len);
+ if (digest == NULL) {
+ knot_packet_free(&resp);
+ rcu_read_unlock();
+ return KNOT_ENOMEM;
+ }
+ ret = knot_tsig_sign(resp_wire,
+ rsize, rsize_max,
+ tsig_rdata_mac(tsig_rr),
+ tsig_rdata_mac_length(tsig_rr),
+ digest, &digest_len, tsig_key_zone,
+ tsig_rcode, tsig_prev_time_signed);
+ free(digest);
+ }
+
+ knot_packet_free(&resp);
+ rcu_read_unlock();
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
int zones_process_response(knot_nameserver_t *nameserver,
sockaddr_t *from,
knot_packet_t *packet, uint8_t *response_wire,
@@ -2988,7 +3406,9 @@ int zones_changeset_binary_size(const knot_changeset_t *chgset, size_t *size)
}
/*! \todo How is the changeset serialized? Any other parts? */
- *size += soa_from_size + soa_to_size + remove_size + add_size;
+ *size = soa_from_size + soa_to_size + remove_size + add_size;
+ /* + Changeset flags. */
+ *size += sizeof(uint32_t);
return KNOT_EOK;
}
@@ -3014,6 +3434,11 @@ static int zones_rrset_write_to_mem(const knot_rrset_t *rr, char **entry,
static int zones_serialize_and_store_chgset(const knot_changeset_t *chs,
char *entry, size_t max_size)
{
+ /* Write changeset flags. */
+ memcpy(entry, (char*)&chs->flags, sizeof(uint32_t));
+ entry += sizeof(uint32_t);
+ max_size -= sizeof(uint32_t);
+
/* Serialize SOA 'from'. */
int ret = zones_rrset_write_to_mem(chs->soa_from, &entry, &max_size);
if (ret != KNOT_EOK) {
@@ -3146,14 +3571,14 @@ static int zones_store_changeset(const knot_changeset_t *chs, journal_t *j,
/*----------------------------------------------------------------------------*/
-journal_t *zones_store_changesets_begin(knot_ns_xfr_t *xfr)
+journal_t *zones_store_changesets_begin(knot_zone_t *zone)
{
- if (xfr == NULL || xfr->data == NULL || xfr->zone == NULL) {
+ if (zone == NULL) {
return NULL;
}
/* Fetch zone-specific data. */
- knot_zone_t *zone = xfr->zone;
+ //knot_zone_t *zone = xfr->zone;
zonedata_t *zd = (zonedata_t *)zone->data;
if (!zd->ixfr_db) {
return NULL;
@@ -3197,14 +3622,14 @@ int zones_store_changesets_rollback(journal_t *j)
/*----------------------------------------------------------------------------*/
-int zones_store_changesets(knot_ns_xfr_t *xfr)
+int zones_store_changesets(knot_zone_t *zone, knot_changesets_t *src)
{
- if (xfr == NULL || xfr->data == NULL || xfr->zone == NULL) {
+ if (zone == NULL || src == NULL) {
return KNOT_EINVAL;
}
- knot_zone_t *zone = xfr->zone;
- knot_changesets_t *src = (knot_changesets_t *)xfr->data;
+// knot_zone_t *zone = xfr->zone;
+// knot_changesets_t *src = (knot_changesets_t *)xfr->data;
/* Fetch zone-specific data. */
zonedata_t *zd = (zonedata_t *)zone->data;
@@ -3318,14 +3743,14 @@ int zones_create_and_save_changesets(const knot_zone_t *old_zone,
}
xfr.data = changesets;
- journal_t *journal = zones_store_changesets_begin(&xfr);
+ journal_t *journal = zones_store_changesets_begin(xfr.zone);
if (journal == NULL) {
dbg_zones("zones: create_changesets: "
"Could not start journal operation.\n");
return KNOT_ERROR;
}
- ret = zones_store_changesets(&xfr);
+ ret = zones_store_changesets(xfr.zone, (knot_changesets_t *)xfr.data);
if (ret != KNOT_EOK) {
zones_store_changesets_rollback(journal);
dbg_zones("zones: create_changesets: "
@@ -3351,6 +3776,81 @@ int zones_create_and_save_changesets(const knot_zone_t *old_zone,
/*----------------------------------------------------------------------------*/
+int zones_store_and_apply_chgsets(knot_changesets_t *chs,
+ knot_zone_t *zone,
+ knot_zone_contents_t **new_contents,
+ const char *msgpref, int type)
+{
+ int ret = KNOT_EOK;
+ int apply_ret = KNOT_EOK;
+ int switch_ret = KNOT_EOK;
+
+ /* Serialize and store changesets. */
+ dbg_xfr("xfr: IXFR/IN serializing and saving changesets\n");
+ journal_t *transaction = zones_store_changesets_begin(zone);
+ if (transaction != NULL) {
+ ret = zones_store_changesets(zone, chs);
+ } else {
+ ret = KNOT_ERROR;
+ }
+ if (ret != KNOT_EOK) {
+ log_zone_error("%s Failed to serialize and store "
+ "changesets - %s\n", msgpref,
+ knot_strerror(ret));
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chs);
+ return ret;
+ }
+
+ /* Now, try to apply the changesets to the zone. */
+ apply_ret = xfrin_apply_changesets(zone, chs, new_contents);
+
+ if (apply_ret != KNOT_EOK) {
+ zones_store_changesets_rollback(transaction);
+ log_zone_error("%s Failed to apply changesets - %s\n",
+ msgpref, knot_strerror(apply_ret));
+
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chs);
+ return apply_ret; // propagate the error above
+ }
+
+ /* Commit transaction. */
+ ret = zones_store_changesets_commit(transaction);
+ if (ret != KNOT_EOK) {
+ /*! \todo THIS WILL LEAK!! xfrin_rollback_update() needed. */
+ log_zone_error("%s Failed to commit stored changesets "
+ "- %s\n", msgpref, knot_strerror(apply_ret));
+ knot_free_changesets(&chs);
+ return ret;
+ }
+
+ /* Switch zone contents. */
+ switch_ret = xfrin_switch_zone(zone, *new_contents, type);
+
+ if (switch_ret != KNOT_EOK) {
+ log_zone_error("%s Failed to replace current zone - %s\n",
+ msgpref, knot_strerror(switch_ret));
+ // Cleanup old and new contents
+ xfrin_rollback_update(zone->contents, new_contents,
+ &chs->changes);
+
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chs);
+ return KNOT_ERROR;
+ }
+
+ xfrin_cleanup_successful_update(&chs->changes);
+
+ /* Free changesets, but not the data. */
+ knot_free_changesets(&chs);
+ assert(ret == KNOT_EOK);
+ log_zone_info("%s Finished.\n", msgpref);
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
int zones_timers_update(knot_zone_t *zone, conf_zone_t *cfzone, evsched_t *sch)
{
if (!sch || !zone) {
@@ -3387,7 +3887,7 @@ int zones_timers_update(knot_zone_t *zone, conf_zone_t *cfzone, evsched_t *sch)
/* Check XFR/IN master server. */
rcu_read_lock();
- if (zd->xfr_in.master.ptr) {
+ if (zd->xfr_in.has_master) {
/* Schedule REFRESH timer. */
uint32_t refresh_tmr = 0;
@@ -3513,3 +4013,139 @@ int zones_cancel_notify(zonedata_t *zd, notify_ev_t *ev)
free(ev);
return KNOT_EOK;
}
+
+int zones_process_update_response(knot_ns_xfr_t *data, uint8_t *rwire, size_t *rsize)
+{
+ /* Processing of a forwarded response:
+ * change packet id
+ */
+ int ret = KNOT_EOK;
+ knot_wire_set_id(rwire, (uint16_t)data->packet_nr);
+
+ /* Forward the response. */
+ ret = data->send(data->fwd_src_fd, &data->saddr, rwire, *rsize);
+ if (ret != *rsize) {
+ ret = KNOT_ECONN;
+ } else {
+ ret = KNOT_EOK;
+ }
+
+ /* As it is a response, do not reply back. */
+ *rsize = 0;
+ return ret;
+}
+
+
+int zones_verify_tsig_query(const knot_packet_t *query,
+ const knot_key_t *key,
+ knot_rcode_t *rcode, uint16_t *tsig_rcode,
+ uint64_t *tsig_prev_time_signed)
+{
+ assert(key != NULL);
+ assert(rcode != NULL);
+ assert(tsig_rcode != NULL);
+
+ const knot_rrset_t *tsig_rr = knot_packet_tsig(query);
+ if (tsig_rr == NULL) {
+ dbg_zones("TSIG key required, but not in query - REFUSED.\n");
+ *rcode = KNOT_RCODE_REFUSED;
+ return KNOT_TSIG_EBADKEY;
+ }
+
+ /*
+ * 1) Check if we support the requested algorithm.
+ */
+ tsig_algorithm_t alg = tsig_rdata_alg(tsig_rr);
+ if (tsig_alg_digest_length(alg) == 0) {
+ log_answer_info("Unsupported digest algorithm "
+ "requested, treating as bad key\n");
+ /*! \todo [TSIG] It is unclear from RFC if I
+ * should treat is as a bad key
+ * or some other error.
+ */
+ *rcode = KNOT_RCODE_NOTAUTH;
+ *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ return KNOT_TSIG_EBADKEY;
+ }
+
+ const knot_dname_t *kname = knot_rrset_owner(tsig_rr);
+ assert(kname != NULL);
+
+ /*
+ * 2) Find the particular key used by the TSIG.
+ * Check not only name, but also the algorithm.
+ */
+ if (key && kname && knot_dname_compare(key->name, kname) == 0
+ && key->algorithm == alg) {
+ dbg_zones_verb("Found claimed TSIG key for comparison\n");
+ } else {
+ *rcode = KNOT_RCODE_NOTAUTH;
+ *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ return KNOT_TSIG_EBADKEY;
+ }
+
+ /*
+ * 3) Validate the query with TSIG.
+ */
+ /* Prepare variables for TSIG */
+ /*! \todo These need to be saved to the response somehow. */
+ //size_t tsig_size = tsig_wire_maxsize(key);
+ size_t digest_max_size = tsig_alg_digest_length(key->algorithm);
+ //size_t digest_size = 0;
+ //uint64_t tsig_prev_time_signed = 0;
+ //uint8_t *digest = (uint8_t *)malloc(digest_max_size);
+ //memset(digest, 0 , digest_max_size);
+
+ /* Copy MAC from query. */
+ dbg_zones_verb("Validating TSIG from query\n");
+
+ //const uint8_t* mac = tsig_rdata_mac(tsig_rr);
+ size_t mac_len = tsig_rdata_mac_length(tsig_rr);
+
+ int ret = KNOT_EOK;
+
+ if (mac_len > digest_max_size) {
+ *rcode = KNOT_RCODE_FORMERR;
+ dbg_zones("MAC length %zu exceeds digest "
+ "maximum size %zu\n", mac_len, digest_max_size);
+ return KNOT_EMALF;
+ } else {
+ //memcpy(digest, mac, mac_len);
+ //digest_size = mac_len;
+
+ /* Check query TSIG. */
+ ret = knot_tsig_server_check(tsig_rr,
+ knot_packet_wireformat(query),
+ knot_packet_size(query), key);
+ dbg_zones_verb("knot_tsig_server_check() returned %s\n",
+ knot_strerror(ret));
+
+ /* Evaluate TSIG check results. */
+ switch(ret) {
+ case KNOT_EOK:
+ *rcode = KNOT_RCODE_NOERROR;
+ break;
+ case KNOT_TSIG_EBADKEY:
+ *tsig_rcode = KNOT_TSIG_RCODE_BADKEY;
+ *rcode = KNOT_RCODE_NOTAUTH;
+ break;
+ case KNOT_TSIG_EBADSIG:
+ *tsig_rcode = KNOT_TSIG_RCODE_BADSIG;
+ *rcode = KNOT_RCODE_NOTAUTH;
+ break;
+ case KNOT_TSIG_EBADTIME:
+ *tsig_rcode = KNOT_TSIG_RCODE_BADTIME;
+ // store the time signed from the query
+ *tsig_prev_time_signed = tsig_rdata_time_signed(tsig_rr);
+ *rcode = KNOT_RCODE_NOTAUTH;
+ break;
+ case KNOT_EMALF:
+ *rcode = KNOT_RCODE_FORMERR;
+ break;
+ default:
+ *rcode = KNOT_RCODE_SERVFAIL;
+ }
+ }
+
+ return ret;
+}
diff --git a/src/knot/server/zones.h b/src/knot/server/zones.h
index 1ed724c..89834a6 100644
--- a/src/knot/server/zones.h
+++ b/src/knot/server/zones.h
@@ -64,6 +64,7 @@ typedef struct zonedata_t
acl_t *xfr_out; /*!< ACL for xfr-out.*/
acl_t *notify_in; /*!< ACL for notify-in.*/
acl_t *notify_out; /*!< ACL for notify-out.*/
+ acl_t *update_in; /*!< ACL for notify-out.*/
/*! \brief XFR-IN scheduler. */
struct {
@@ -78,13 +79,11 @@ typedef struct zonedata_t
int next_id; /*!< ID of the next awaited SOA resp.*/
uint32_t bootstrap_retry;/*!< AXFR/IN bootstrap retry. */
unsigned scheduled;/*!< Scheduled operations. */
+ int has_master; /*!< True if it has master set. */
} xfr_in;
/*! \brief List of pending NOTIFY events. */
list notify_pending;
-
- /*! \brief List of fds with pending SOA queries. */
- int soa_pending;
/*! \brief Zone IXFR history. */
journal_t *ixfr_db;
@@ -138,9 +137,9 @@ int zones_zonefile_sync(knot_zone_t *zone, journal_t *journal);
/*!
* \todo Document me.
*/
-int zones_query_check_zone(const knot_zone_t *zone, const sockaddr_t *addr,
- knot_key_t **tsig_key, knot_rcode_t *rcode,
- uint8_t q_opcode);
+int zones_query_check_zone(const knot_zone_t *zone, uint8_t q_opcode,
+ const sockaddr_t *addr, knot_key_t **tsig_key,
+ knot_rcode_t *rcode);
/*!
* \todo Document me.
@@ -156,6 +155,14 @@ int zones_normal_query_answer(knot_nameserver_t *nameserver,
knot_ns_transport_t transport);
/*!
+ * \todo Document me.
+ */
+int zones_process_update(knot_nameserver_t *nameserver,
+ knot_packet_t *query, const sockaddr_t *addr,
+ uint8_t *resp_wire, size_t *rsize,
+ int fd, knot_ns_transport_t transport);
+
+/*!
* \brief Processes normal response packet.
*
* \param nameserver Name server structure to provide the needed data.
@@ -215,7 +222,7 @@ int zones_ns_conf_hook(const struct conf_t *conf, void *data);
* \todo Expects the xfr structure to be initialized in some way.
* \todo Update documentation!!!
*/
-int zones_store_changesets(knot_ns_xfr_t *xfr);
+int zones_store_changesets(knot_zone_t *zone, knot_changesets_t *src);
/*!
* \brief Begin changesets storing transaction.
@@ -223,7 +230,7 @@ int zones_store_changesets(knot_ns_xfr_t *xfr);
* \retval pointer to journal if successful
* \retval NULL on failure.
*/
-journal_t *zones_store_changesets_begin(knot_ns_xfr_t *xfr);
+journal_t *zones_store_changesets_begin(knot_zone_t *zone);
/*!
* \brief Commit stored changesets.
@@ -289,6 +296,11 @@ int zones_xfr_load_changesets(knot_ns_xfr_t *xfr, uint32_t serial_from,
int zones_create_and_save_changesets(const knot_zone_t *old_zone,
const knot_zone_t *new_zone);
+int zones_store_and_apply_chgsets(knot_changesets_t *chs,
+ knot_zone_t *zone,
+ knot_zone_contents_t **new_contents,
+ const char *msgpref, int type);
+
/*!
* \brief Update zone timers.
*
@@ -319,6 +331,28 @@ int zones_timers_update(knot_zone_t *zone, conf_zone_t *cfzone, evsched_t *sch);
*/
int zones_cancel_notify(zonedata_t *zd, notify_ev_t *ev);
+
+/*!
+ * \brief Processes forwarded UPDATE response packet.
+ * \todo #1291 move to appropriate section (DDNS).
+ */
+int zones_process_update_response(knot_ns_xfr_t *data, uint8_t *rwire, size_t *rsize);
+
+/*!
+ * \brief Verify TSIG in query.
+ *
+ * \param query Query packet.
+ * \param key TSIG key used for this query.
+ * \param rcode Dst for resulting RCODE.
+ * \param tsig_rcode Dst for resulting TSIG RCODE.
+ * \param tsig_prev_time_signed Dst for previout time signed.
+ *
+ * \return KNOT_EOK if verified or error if not.
+ */
+int zones_verify_tsig_query(const knot_packet_t *query,
+ const knot_key_t *key,
+ knot_rcode_t *rcode, uint16_t *tsig_rcode,
+ uint64_t *tsig_prev_time_signed);
#endif // _KNOTD_ZONES_H_
/*! @} */
diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c
index a9f9af2..43c1893 100644
--- a/src/knot/zone/semantic-check.c
+++ b/src/knot/zone/semantic-check.c
@@ -969,15 +969,17 @@ static int check_nsec3_node_in_zone(knot_zone_contents_t *zone, knot_node_t *nod
return KNOT_EOK;
}
- if (((real_size = base32hex_encode_alloc(((char *)
- rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1,
+ if (((b32_ret = base32hex_encode_alloc((uint8_t *)
+ (rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1,
rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1,
- (char **)&next_dname_decoded)) <= 0) ||
+ &next_dname_decoded)) <= 0) ||
(next_dname_decoded == NULL)) {
fprintf(stderr, "Could not encode base32 string!\n");
return KNOT_ERROR;
}
+ real_size = b32_ret;
+
/* This is why we allocate maximum length of decoded string + 1 */
// memmove(next_dname_decoded + 1, next_dname_decoded, real_size);
// next_dname_decoded[0] = real_size;
@@ -1522,7 +1524,7 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
if (((b32_ret = base32hex_encode_alloc(((uint8_t *)
rdata_item_data(&(nsec3_rrset->rdata->items[4]))) + 1,
rdata_item_size(&nsec3_rrset->rdata->items[4]) - 1,
- (char **)&next_dname_decoded)) <= 0) ||
+ &next_dname_decoded)) <= 0) ||
(next_dname_decoded == NULL)) {
fprintf(stderr, "Could not encode base32 string!\n");
err_handler_handle_error(handler, last_nsec3_node,
@@ -1530,6 +1532,8 @@ void log_cyclic_errors_in_zone(err_handler_t *handler,
return;
}
+ real_size = b32_ret;
+
/* Local allocation, will be discarded. */
knot_dname_t *next_dname =
knot_dname_new_from_str((char *)next_dname_decoded,
diff --git a/src/knot/zone/zone-dump-text.c b/src/knot/zone/zone-dump-text.c
index bf02ea1..f231a70 100644
--- a/src/knot/zone/zone-dump-text.c
+++ b/src/knot/zone/zone-dump-text.c
@@ -549,25 +549,33 @@ static char *rdata_time_to_string(knot_rdata_item_t item)
static char *rdata_base32_to_string(knot_rdata_item_t item)
{
- int length;
+ char *ret = NULL;
size_t size = rdata_item_size(item);
+
if (size == 0) {
- char *ret = malloc(sizeof(char) * 2);
+ ret = malloc(2);
+
ret[0] = '-';
ret[1] = '\0';
- return ret;
- }
-
- size -= 1; // remove length byte from count
- char *ret = NULL;
- length = base32hex_encode_alloc((char *)rdata_item_data(item) + 1,
- size, &ret);
- if (length > 0) {
- return ret;
} else {
- free(ret);
- return NULL;
+ int32_t b32_out;
+ uint32_t out_len = ((size + 4) / 5) * 8;
+
+ ret = malloc(out_len + 1);
+
+ b32_out = base32hex_encode(rdata_item_data(item) + 1,
+ size - 1,
+ (uint8_t *)ret,
+ out_len);
+ if (b32_out <= 0) {
+ free(ret);
+ ret = NULL;
+ }
+
+ ret[b32_out] = '\0';
}
+
+ return ret;
}
/*!< \todo Replace with function from .../common after release. */
diff --git a/src/knot/zone/zone-dump.c b/src/knot/zone/zone-dump.c
index b9c7bc2..97e8a57 100644
--- a/src/knot/zone/zone-dump.c
+++ b/src/knot/zone/zone-dump.c
@@ -110,7 +110,7 @@ static int write_wrapper(const void *src,
return KNOT_EINVAL;
}
- dbg_zdump_detail("zdump: write_wrapper: Writing %d bytes to fd: %d.\n",
+ dbg_zdump_detail("zdump: write_wrapper: Writing %zu bytes to fd: %d.\n",
size * n, fd);
if (fd < 0) {
@@ -133,7 +133,7 @@ static int write_wrapper(const void *src,
/* Write to buffer first, if possible. */
if (*written_bytes + (size * n) < BUFFER_SIZE) {
dbg_zdump_detail("zdump: write_wrapper: Fits to "
- "buffer. Remaining=%d.\n",
+ "buffer. Remaining=%lu.\n",
BUFFER_SIZE - *written_bytes);
int ret = write_to_stream(src, size, n,
stream,
@@ -154,7 +154,7 @@ static int write_wrapper(const void *src,
size_t remainder = BUFFER_SIZE - *written_bytes;
dbg_zdump_detail("zdump: write_wrapper: "
"Flushing buffer, "
- "appending %d bytes.\n", remainder);
+ "appending %zu bytes.\n", remainder);
int ret = write_to_stream(src, 1,
remainder,
stream,
@@ -187,7 +187,7 @@ static int write_wrapper(const void *src,
if ((size * n) - remainder > BUFFER_SIZE) {
/* Write through. */
dbg_zdump("zdump: Attempting buffer write "
- "through. Total: %d bytes.\n",
+ "through. Total: %zu bytes.\n",
(size * n) - remainder);
ret = write_to_file_crc(src + remainder, 1,
(size * n) - remainder,
diff --git a/src/knot/zone/zone-load.c b/src/knot/zone/zone-load.c
index 28195f8..9973d75 100644
--- a/src/knot/zone/zone-load.c
+++ b/src/knot/zone/zone-load.c
@@ -450,7 +450,7 @@ static knot_rdata_t *knot_load_rdata(uint16_t type, FILE *f,
int ret = knot_rdata_set_items(rdata, items, rdata_count);
if (ret != KNOT_EOK) {
dbg_zload("zload: read_rdata: Could not set items "
- "when loading rdata. Reason: %\n.",
+ "when loading rdata. Reason: %s\n.",
knot_strerror(ret));
load_rdata_purge(rdata, items, desc->length, desc, type);
return NULL;
diff --git a/src/knotc.8 b/src/knotc.8
index f08642e..ba345ce 100644
--- a/src/knotc.8
+++ b/src/knotc.8
@@ -1,10 +1,10 @@
-.TH knotc "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.1.3"
+.TH knotc "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2"
.SH NAME
.B knotc
\- Knot DNS control utility
.SH SYNOPSIS
.B knotc
-[\fIparameters\fR] \fIstart|stop|restart|reload|running|compile\fR
+[\fIparameters\fR] \fI<action>\fR [\fIaction_args\fR]
.SH DESCRIPTION
.SS "Parameters:"
.HP
@@ -13,6 +13,16 @@
\fB\-j\fR [num], \fB\-\-jobs\fR=\fI[num]\fR
Number of parallel tasks to run (only for 'compile').
.TP
+\fB\-s\fR [server]\fR Remote server address (default 127.0.0.1)
+.TP
+\fB\-p\fR [port]\fR Remote server port (default 5553)
+.TP
+\fB\-y\fR [hmac:]name:key]\fR Use key_id for specified on the command line.
+.TP
+\fB\-k\fR [file]\fR Use key file (as in config section 'keys').
+f.e. echo "knotc-key hmac-md5 Wg==" > knotc.key
+If you omit algorithm, hmac-md5 will be used as default.
+.TP
\fB\-f\fR, \fB\-\-force\fR
Force operation \- override some checks.
.TP
@@ -47,20 +57,39 @@ Stops and then starts knot server daemon.
reload
Reload knot configuration and compiled zones.
.TP
-running
+flush
+Flush journal and update zone files.
+.TP
+status
Check if server is running.
.TP
compile
Compile zone file.
.TP
refresh
-Refresh all slave zones.
+Refresh slave zones (all if not specified).
.TP
checkconf
Check server configuration.
.TP
checkzone
Check zones before compiling (accepts specific zones, f.e. 'knotc checkzone example1.com example2.com').
+.SS "EXAMPLES"
+.TP
+.B Setup a keyfile for remote control
+.TP
+1. Generate keys
+dnssec-keygen -a hmac-md5 -b 256 -n HOST knotc-key
+.TP
+2. Extract secret in base64 format and create keyfile
+echo "knotc-key hmac-md5 <secret>" > knotc.key
+
+Make sure the key can be read/written only by owner for
+security reasons.
+.TP
+
+.B Reload server remotely
+knotc -s 127.0.0.1 -k knotc.key reload
.SH "SEE ALSO"
The full documentation for
.B Knot
diff --git a/src/knotd.8 b/src/knotd.8
index f57b5a5..4c96cff 100644
--- a/src/knotd.8
+++ b/src/knotd.8
@@ -1,4 +1,4 @@
-.TH "knotd" "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.1.3"
+.TH "knotd" "8" "September 2012" "CZ.NIC Labs" "Knot DNS, version 1.2-rc2"
.SH NAME
.B knotd
\- Knot DNS daemon
diff --git a/src/libknot/consts.h b/src/libknot/consts.h
index 4249763..3431bc3 100644
--- a/src/libknot/consts.h
+++ b/src/libknot/consts.h
@@ -57,7 +57,8 @@ typedef enum knot_packet_type {
KNOT_RESPONSE_NORMAL, /*!< Normal response. */
KNOT_RESPONSE_AXFR, /*!< AXFR transfer response. */
KNOT_RESPONSE_IXFR, /*!< IXFR transfer response. */
- KNOT_RESPONSE_NOTIFY /*!< NOTIFY response. */
+ KNOT_RESPONSE_NOTIFY, /*!< NOTIFY response. */
+ KNOT_RESPONSE_UPDATE /*!< Dynamic update response. */
} knot_packet_type_t;
/*
diff --git a/src/libknot/dname.c b/src/libknot/dname.c
index eed2fd6..5c7e961 100644
--- a/src/libknot/dname.c
+++ b/src/libknot/dname.c
@@ -178,8 +178,13 @@ static int knot_dname_str_to_wire(const char *name, uint size,
while (ch - (const uint8_t *)name < size) {
assert(w - wire - 1 == ch - (const uint8_t *)name);
-
+
if (*ch == '.') {
+ /* Zero-length label inside a dname - invalid. */
+ if (label_length == 0) {
+ free(wire);
+ return -1;
+ }
dbg_dname_detail("Position %zd (%p): "
"label length: %u\n",
label_start - wire,
@@ -262,6 +267,11 @@ static int knot_dname_find_labels(knot_dname_t *dname, int alloc)
short label_count = 0;
while (pos - name < size && *pos != '\0' && label_count < KNOT_MAX_DNAME_LABELS ) {
+ if (*pos > 63) { /* Check label lengths. */
+ dbg_dname("Wrong wire format of domain name!\n");
+ dbg_dname("Label %d exceeds 63 bytes.\n", label_count);
+ return -1;
+ }
labels[label_count++] = pos - name;
pos += *pos + 1;
}
@@ -471,8 +481,9 @@ knot_dname_t *knot_dname_new_from_wire(const uint8_t *name, uint size,
/*----------------------------------------------------------------------------*/
knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire,
- size_t *pos, size_t size,
- knot_node_t *node)
+ size_t *pos, size_t size,
+ knot_node_t *node,
+ knot_dname_t *dname)
{
uint8_t name[KNOT_MAX_DNAME_LENGTH];
uint8_t labels[KNOT_MAX_DNAME_LABELS];
@@ -539,34 +550,30 @@ knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire,
*pos += 1;
}
- knot_dname_t *dname = knot_dname_new();
+ /* Allocate if NULL. */
+ if (dname == NULL) {
+ dname = knot_dname_new();
+ if (dname) {
+ dname->name = (uint8_t *)malloc((i + 1) * sizeof(uint8_t));
+ dname->labels = (uint8_t *)malloc((l + 1) * sizeof(uint8_t));
+ }
+ }
if (dname == NULL) {
ERR_ALLOC_FAILED;
return NULL;
}
- dname->name = (uint8_t *)malloc((i + 1) * sizeof(uint8_t));
- if (dname->name == NULL) {
+ if (dname->name == NULL || dname->labels == NULL) {
ERR_ALLOC_FAILED;
knot_dname_free(&dname);
return NULL;
}
memcpy(dname->name, name, i + 1);
- dname->size = i + 1;
-
- /*! \todo Why l + 1 ?? */
- dname->labels = (uint8_t *)malloc((l + 1) * sizeof(uint8_t));
- if (dname->labels == NULL) {
- ERR_ALLOC_FAILED;
- knot_dname_free(&dname);
- return NULL;
- }
memcpy(dname->labels, labels, l + 1);
-
+ dname->size = i + 1;
dname->label_count = l;
-
dname->node = node;
return dname;
diff --git a/src/libknot/dname.h b/src/libknot/dname.h
index 347e699..6f649ce 100644
--- a/src/libknot/dname.h
+++ b/src/libknot/dname.h
@@ -124,9 +124,22 @@ knot_dname_t *knot_dname_new_from_wire(const uint8_t *name,
unsigned int size,
struct knot_node *node);
+/*!
+ * \brief Parse dname from wire.
+ *
+ * \param wire Message in wire format.
+ * \param pos Position of the domain name on wire.
+ * \param size Domain name length.
+ * \param node Zone node the domain name belongs to. Set to NULL if not
+ * applicable.
+ * \param dname Destination dname (will allocate new when NULL).
+ *
+ * \return parsed domain name or NULL.
+ */
knot_dname_t *knot_dname_parse_from_wire(const uint8_t *wire,
- size_t *pos, size_t size,
- struct knot_node *node);
+ size_t *pos, size_t size,
+ struct knot_node *node,
+ knot_dname_t *dname);
/*!
* \brief Initializes domain name by the name given in wire format.
diff --git a/src/libknot/hash/cuckoo-hash-table.c b/src/libknot/hash/cuckoo-hash-table.c
index 7358e14..7d454a9 100644
--- a/src/libknot/hash/cuckoo-hash-table.c
+++ b/src/libknot/hash/cuckoo-hash-table.c
@@ -1434,7 +1434,7 @@ int ck_deep_copy(ck_hash_table_t *from, ck_hash_table_t **to)
return -2;
}
- dbg_ck_detail("Copying stash item: %p with item %p, ", si,
+ dbg_ck_detail("Copying stash item: %p with item %p.\n", si,
si->item);
if (si->item == NULL) {
@@ -1473,9 +1473,10 @@ dbg_ck_exec_detail(
(si_new) ? si_new->item : NULL);
assert(si_new != NULL);
- assert(si_new->item != NULL);
- dbg_ck_detail("key: %.*s\n", (int)si_new->item->key_length,
- si_new->item->key);
+ if (si_new->item != NULL) {
+ dbg_ck_detail("key: %.*s\n", (int)si_new->item->key_length,
+ si_new->item->key);
+ }
);
}
diff --git a/src/libknot/nameserver/name-server.c b/src/libknot/nameserver/name-server.c
index 0a7a298..8238d7e 100644
--- a/src/libknot/nameserver/name-server.c
+++ b/src/libknot/nameserver/name-server.c
@@ -2950,7 +2950,7 @@ static int ns_ixfr(knot_ns_xfr_t *xfr)
/*----------------------------------------------------------------------------*/
static int knot_ns_prepare_response(knot_packet_t *query, knot_packet_t **resp,
- size_t max_size)
+ size_t max_size, int copy_question)
{
assert(max_size >= 500);
@@ -2969,7 +2969,7 @@ static int knot_ns_prepare_response(knot_packet_t *query, knot_packet_t **resp,
return ret;
}
- ret = knot_response_init_from_query(*resp, query);
+ ret = knot_response_init_from_query(*resp, query, copy_question);
if (ret != KNOT_EOK) {
dbg_ns("Failed to init response structure.\n");
@@ -3207,7 +3207,7 @@ int knot_ns_parse_packet(const uint8_t *query_wire, size_t qsize,
if(knot_packet_is_query(packet)) {
*type = KNOT_QUERY_UPDATE;
} else {
- return KNOT_RCODE_FORMERR;
+ *type = KNOT_RESPONSE_UPDATE;
}
break;
default:
@@ -3437,7 +3437,7 @@ int knot_ns_prep_normal_response(knot_nameserver_t *nameserver,
resp_max_size = MAX_UDP_PAYLOAD;
}
- ret = knot_ns_prepare_response(query, resp, resp_max_size);
+ ret = knot_ns_prepare_response(query, resp, resp_max_size, 1);
if (ret != KNOT_EOK) {
return KNOT_ERROR;
}
@@ -3487,6 +3487,143 @@ dbg_ns_exec_verb(
/*----------------------------------------------------------------------------*/
+int knot_ns_prep_update_response(knot_nameserver_t *nameserver,
+ knot_packet_t *query, knot_packet_t **resp,
+ knot_zone_t **zone, size_t max_size)
+{
+ dbg_ns_verb("knot_ns_prep_update_response()\n");
+
+ if (nameserver == NULL || query == NULL || resp == NULL
+ || zone == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // first, parse the rest of the packet
+ assert(knot_packet_is_query(query));
+ dbg_ns_verb("Query - parsed: %zu, total wire size: %zu\n",
+ knot_packet_parsed(query), knot_packet_size(query));
+ int ret;
+
+ ret = knot_packet_parse_rest(query);
+ if (ret != KNOT_EOK) {
+ dbg_ns("Failed to parse rest of the query: %s.\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /*
+ * Semantic checks
+ *
+ * Check the QDCOUNT and in case of anything but 1 send back
+ * FORMERR
+ */
+ if (knot_packet_qdcount(query) != 1) {
+ dbg_ns("QDCOUNT != 1. Reply FORMERR.\n");
+ return KNOT_EMALF;
+ }
+
+ /*
+ * Check what is in the Additional section. Only OPT and TSIG are
+ * allowed. TSIG must be the last record if present.
+ */
+ /*! \todo Put to separate function - used in prep_normal_response(). */
+ if (knot_packet_arcount(query) > 0) {
+ int ok = 0;
+ const knot_rrset_t *add1 =
+ knot_packet_additional_rrset(query, 0);
+ if (knot_packet_additional_rrset_count(query) == 1
+ && (knot_rrset_type(add1) == KNOT_RRTYPE_OPT
+ || knot_rrset_type(add1) == KNOT_RRTYPE_TSIG)) {
+ ok = 1;
+ } else if (knot_packet_additional_rrset_count(query) == 2) {
+ const knot_rrset_t *add2 =
+ knot_packet_additional_rrset(query, 1);
+ if (knot_rrset_type(add1) == KNOT_RRTYPE_OPT
+ && knot_rrset_type(add2) == KNOT_RRTYPE_TSIG) {
+ ok = 1;
+ }
+ }
+
+ if (!ok) {
+ dbg_ns("Additional section malformed. Reply FORMERR\n");
+ return KNOT_EMALF;
+ }
+ }
+
+ size_t resp_max_size = 0;
+
+ knot_packet_dump(query);
+
+ /*! \todo Put to separate function - used in prep_normal_response(). */
+ if (max_size > 0) {
+ // if TCP is used, buffer size is the only constraint
+ assert(max_size > 0);
+ resp_max_size = max_size;
+ } else if (knot_query_edns_supported(query)) {
+ assert(max_size == 0);
+ if (knot_edns_get_payload(&query->opt_rr) <
+ knot_edns_get_payload(nameserver->opt_rr)) {
+ resp_max_size = knot_edns_get_payload(&query->opt_rr);
+ } else {
+ resp_max_size = knot_edns_get_payload(
+ nameserver->opt_rr);
+ }
+ }
+
+ if (resp_max_size < MAX_UDP_PAYLOAD) {
+ resp_max_size = MAX_UDP_PAYLOAD;
+ }
+
+ ret = knot_ns_prepare_response(query, resp, resp_max_size, 0);
+ if (ret != KNOT_EOK) {
+ return KNOT_ERROR;
+ }
+
+ dbg_ns_verb("Query - parsed: %zu, total wire size: %zu\n",
+ query->parsed, query->size);
+ dbg_ns_detail("Opt RR: version: %d, payload: %d\n",
+ query->opt_rr.version, query->opt_rr.payload);
+
+ // get the answer for the query
+ knot_zonedb_t *zonedb = rcu_dereference(nameserver->zone_db);
+
+ dbg_ns_detail("EDNS supported in query: %d\n",
+ knot_query_edns_supported(query));
+
+ // set the OPT RR to the response
+ if (knot_query_edns_supported(query)) {
+ ret = knot_response_add_opt(*resp, nameserver->opt_rr, 1,
+ knot_query_nsid_requested(query));
+ if (ret != KNOT_EOK) {
+ dbg_ns("Failed to set OPT RR to the response"
+ ": %s\n", knot_strerror(ret));
+ } else {
+ // copy the DO bit from the query
+ if (knot_query_dnssec_requested(query)) {
+ knot_edns_set_do(&(*resp)->opt_rr);
+ }
+ }
+ }
+
+ dbg_ns_verb("Response max size: %zu\n", (*resp)->max_size);
+
+ const knot_dname_t *qname = knot_packet_qname(knot_packet_query(*resp));
+ assert(qname != NULL);
+
+// uint16_t qtype = knot_packet_qtype(*resp);
+dbg_ns_exec_verb(
+ char *name_str = knot_dname_to_str(qname);
+ dbg_ns_verb("Trying to find zone %s\n", name_str);
+ free(name_str);
+);
+ // find zone
+ *zone = knot_zonedb_find_zone(zonedb, qname);
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
int knot_ns_answer_normal(knot_nameserver_t *nameserver,
const knot_zone_t *zone, knot_packet_t *resp,
uint8_t *response_wire, size_t *rsize, int check_any)
@@ -3619,7 +3756,7 @@ dbg_ns_exec_verb(
response->wireformat = xfr->wire;
response->max_size = xfr->wire_size;
- ret = knot_response_init_from_query(response, xfr->query);
+ ret = knot_response_init_from_query(response, xfr->query, 1);
if (ret != KNOT_EOK) {
dbg_ns("Failed to init response structure.\n");
@@ -3796,8 +3933,9 @@ int knot_ns_answer_axfr(knot_nameserver_t *nameserver, knot_ns_xfr_t *xfr)
if (ret < 0 && ret != KNOT_ECONN) {
dbg_ns("AXFR failed, sending SERVFAIL.\n");
// now only one type of error (SERVFAIL), later maybe more
- /*! \todo xfr->wire is not NULL, will fail on assert! */
- knot_ns_xfr_send_error(nameserver, xfr, KNOT_RCODE_SERVFAIL);
+ knot_ns_error_response_from_query(nameserver, xfr->query,
+ KNOT_RCODE_SERVFAIL,
+ xfr->wire, &xfr->wire_size);
ret = xfr->send(xfr->session, &xfr->addr, xfr->wire,
xfr->wire_size);
} else if (ret > 0) {
@@ -4076,143 +4214,140 @@ int knot_ns_process_ixfrin(knot_nameserver_t *nameserver,
}
/*----------------------------------------------------------------------------*/
-
-int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query,
- uint8_t *response_wire, size_t *rsize,
- knot_zone_t **zone, knot_changeset_t **changeset)
+/*
+ * The given query is already fully parsed. But the parameter contains an
+ * already prepared response structure.
+ *
+ * This function should process the contents, prepare prerequisities, prepare
+ * changeset and return to the caller.
+ */
+int knot_ns_process_update(const knot_packet_t *query,
+ const knot_zone_contents_t *zone,
+ knot_changeset_t *changeset, knot_rcode_t *rcode)
{
- // 1) Parse the rest of the packet
assert(knot_packet_is_query(query));
- knot_packet_t *response;
- assert(*rsize >= MAX_UDP_PAYLOAD);
- int ret = knot_ns_prepare_response(query, &response, MAX_UDP_PAYLOAD);
- if (ret != KNOT_EOK) {
- knot_ns_error_response_from_query(nameserver, query,
- KNOT_RCODE_SERVFAIL,
- response_wire, rsize);
- return KNOT_EOK;
- }
-
- assert(response != NULL);
+ dbg_ns("Processing Dynamic Update.\n");
- dbg_ns_verb("Query - parsed: %zu, total wire size: %zu\n",
- query->parsed, query->size);
-
- if (knot_packet_parsed(query) < knot_packet_size(query)) {
- ret = knot_packet_parse_rest(query);
- if (ret != KNOT_EOK) {
- dbg_ns("Failed to parse rest of the query: "
- "%s.\n", knot_strerror(ret));
- knot_ns_error_response_full(nameserver, response,
- (ret == KNOT_EMALF)
- ? KNOT_RCODE_FORMERR
- : KNOT_RCODE_SERVFAIL,
- response_wire, rsize);
- knot_packet_free(&response);
- return KNOT_EOK;
- }
+ *rcode = KNOT_RCODE_NOERROR;
+
+ // 1) Check zone
+ // Already done
+// dbg_ns_verb("Checking zone for DDNS.\n");
+// int ret = knot_ddns_check_zone(zone, query, rcode);
+// if (ret != KNOT_EOK) {
+// dbg_ns("Failed to check zone for update: "
+// "%s.\n", knot_strerror(ret));
+// return ret;
+// }
+
+ // 2) Convert prerequisities
+ // Already done
+// dbg_ns_verb("Processing prerequisities.\n");
+// knot_ddns_prereq_t *prereqs = NULL;
+// int ret = knot_ddns_process_prereqs(query, &prereqs, rcode);
+// if (ret != KNOT_EOK) {
+// dbg_ns("Failed to check zone for update: "
+// "%s.\n", knot_strerror(ret));
+// return ret;
+// }
+
+// assert(prereqs != NULL);
+
+ // 3) Check prerequisities
+ /*! \todo Somehow ensure the zone will not be changed until the update
+ * is finished.
+ */
+ // Already done
+// dbg_ns_verb("Checking prerequisities.\n");
+// ret = knot_ddns_check_prereqs(zone, &prereqs, rcode);
+// if (ret != KNOT_EOK) {
+// knot_ddns_prereqs_free(&prereqs);
+// dbg_ns("Failed to check zone for update: "
+// "%s.\n", knot_strerror(ret));
+// return ret;
+// }
+
+ // 4) Convert update to changeset
+ dbg_ns_verb("Converting UPDATE packet to changeset.\n");
+ int ret = knot_ddns_process_update(zone, query, changeset, rcode);
+ if (ret != KNOT_EOK) {
+ dbg_ns("Failed to check zone for update: "
+ "%s.\n", knot_strerror(ret));
+ return ret;
}
- dbg_ns_verb("Query - parsed: %zu, total wire size: %zu\n",
- knot_packet_parsed(query), knot_packet_size(query));
+ assert(changeset != NULL);
- /*! \todo API for EDNS values. */
- dbg_ns_verb("Opt RR: version: %d, payload: %d\n",
- query->opt_rr.version, query->opt_rr.payload);
-
- // 2) Find zone for the query
- // we do not check if there is only one entry in the Question section
- // because the packet structure does not allow it
- if (knot_packet_qtype(query) != KNOT_RRTYPE_SOA) {
- dbg_ns("Question is not of type SOA.\n");
- knot_ns_error_response_full(nameserver, response,
- KNOT_RCODE_FORMERR,
- response_wire, rsize);
- knot_packet_free(&response);
- return KNOT_EOK;
- }
+ // Done in zones.c
+// knot_ddns_prereqs_free(&prereqs);
+ return ret;
+}
- *zone = knot_zonedb_find_zone(nameserver->zone_db,
- knot_packet_qname(query));
- if (*zone == NULL) {
- dbg_ns("Zone not found for the update.\n");
- knot_ns_error_response_full(nameserver, response,
- KNOT_RCODE_NOTAUTH,
- response_wire, rsize);
- knot_packet_free(&response);
- return KNOT_EOK;
+/*----------------------------------------------------------------------------*/
+/*
+ * This function should:
+ * 1) Create zone shallow copy and the changes structure.
+ * 2) Call knot_ddns_process_update2().
+ * - If something went bad, call xfrin_rollback_update() and return an error.
+ * - If everything went OK, continue.
+ * 3) Finalize the updated zone.
+ *
+ * NOTE: Mostly copied from xfrin_apply_changesets(). Should be refactored in
+ * order to get rid of duplicate code.
+ */
+int knot_ns_process_update2(const knot_packet_t *query,
+ knot_zone_contents_t *old_contents,
+ knot_zone_contents_t **new_contents,
+ knot_changesets_t *chgs, knot_rcode_t *rcode)
+{
+ /*! \todo Implement. */
+ if (query == NULL || old_contents == NULL || chgs == NULL ||
+ chgs->sets == NULL || new_contents == NULL || rcode == NULL) {
+ return KNOT_EINVAL;
}
- uint8_t rcode = 0;
- // 3) Check zone
- ret = knot_ddns_check_zone(*zone, query, &rcode);
- if (ret == KNOT_EBADZONE) {
- // zone is slave, forward the request
- /*! \todo Implement forwarding. */
- return KNOT_EBADZONE;
- } else if (ret != KNOT_EOK) {
- dbg_ns("Failed to check zone for update: "
- "%s.\n", knot_strerror(ret));
- knot_ns_error_response_full(nameserver, response, rcode,
- response_wire, rsize);
- knot_packet_free(&response);
- return KNOT_EOK;
- }
+ dbg_ns("Applying UPDATE to zone...\n");
- // 4) Convert prerequisities
- knot_ddns_prereq_t *prereqs = NULL;
- ret = knot_ddns_process_prereqs(query, &prereqs, &rcode);
+ /* 1) Create zone shallow copy. */
+ dbg_ns_verb("Creating shallow copy of the zone...\n");
+ knot_zone_contents_t *contents_copy = NULL;
+ knot_changes_t *changes = NULL;
+ int ret = xfrin_prepare_zone_copy(old_contents, &contents_copy,
+ &changes);
if (ret != KNOT_EOK) {
- dbg_ns("Failed to check zone for update: "
- "%s.\n", knot_strerror(ret));
- knot_ns_error_response_full(nameserver, response, rcode,
- response_wire, rsize);
- knot_packet_free(&response);
- return KNOT_EOK;
+ dbg_ns("Failed to prepare zone copy: %s\n",
+ knot_strerror(ret));
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
}
-
- assert(prereqs != NULL);
-
- // 5) Check prerequisities
- /*! \todo Somehow ensure the zone will not be changed until the update
- * is finished.
- */
- ret = knot_ddns_check_prereqs(knot_zone_contents(*zone), &prereqs,
- &rcode);
+
+ /* 2) Apply the UPDATE and create changesets. */
+ dbg_ns_verb("Applying the UPDATE and creating changeset...\n");
+ ret = knot_ddns_process_update2(contents_copy, query, &chgs->sets[0],
+ changes, rcode);
if (ret != KNOT_EOK) {
- dbg_ns("Failed to check zone for update: "
- "%s.\n", knot_strerror(ret));
- knot_ns_error_response_full(nameserver, response, rcode,
- response_wire, rsize);
- knot_ddns_prereqs_free(&prereqs);
- knot_packet_free(&response);
- return KNOT_EOK;
+ dbg_ns("Failed to apply UPDATE to the zone copy or no update"
+ " made: %s\n", (ret < 0) ? knot_strerror(ret)
+ : "No change made.");
+ xfrin_rollback_update(old_contents, &contents_copy, &changes);
+ return ret;
}
-
- // 6) Convert update to changeset
- ret = knot_ddns_process_update(query, changeset, &rcode);
+
+ dbg_ns_verb("Finalizing updated zone...\n");
+ ret = xfrin_finalize_updated_zone(contents_copy, changes, old_contents);
if (ret != KNOT_EOK) {
- dbg_ns("Failed to check zone for update: "
- "%s.\n", knot_strerror(ret));
- knot_ns_error_response_full(nameserver, response, rcode,
- response_wire, rsize);
- knot_ddns_prereqs_free(&prereqs);
- knot_packet_free(&response);
- return KNOT_EOK;
+ dbg_ns("Failed to finalize updated zone: %s\n",
+ knot_strerror(ret));
+ xfrin_rollback_update(old_contents, &contents_copy, &changes);
+ *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
+ : KNOT_RCODE_SERVFAIL;
+ return ret;
}
- assert(changeset != NULL);
-
- // 7) Create response
- dbg_ns("Update converted successfuly.\n");
-
- /*! \todo No response yet. Distinguish somehow in the caller.
- * Maybe only this case will be EOK, other cases some error.
- */
-
- knot_ddns_prereqs_free(&prereqs);
- knot_packet_free(&response);
+ chgs->changes = changes;
+ *new_contents = contents_copy;
+
return KNOT_EOK;
}
@@ -4221,7 +4356,10 @@ int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query,
int knot_ns_create_forward_query(const knot_packet_t *query,
uint8_t *query_wire, size_t *size)
{
- // just copy the wireformat of the query and set a new random ID to it
+ /* Forward UPDATE query:
+ * assign a new packet id
+ */
+ int ret = KNOT_EOK;
if (knot_packet_size(query) > *size) {
return KNOT_ESPACE;
}
@@ -4229,10 +4367,9 @@ int knot_ns_create_forward_query(const knot_packet_t *query,
memcpy(query_wire, knot_packet_wireformat(query),
knot_packet_size(query));
*size = knot_packet_size(query);
-
knot_wire_set_id(query_wire, knot_random_id());
- return KNOT_EOK;
+ return ret;
}
/*----------------------------------------------------------------------------*/
diff --git a/src/libknot/nameserver/name-server.h b/src/libknot/nameserver/name-server.h
index 3fe1210..6bb7a86 100644
--- a/src/libknot/nameserver/name-server.h
+++ b/src/libknot/nameserver/name-server.h
@@ -121,6 +121,9 @@ typedef struct knot_ns_xfr {
uint8_t *digest; /*!< Buffer for counting digest. */
size_t digest_size; /*!< Size of the digest. */
size_t digest_max_size; /*!< Size of the buffer. */
+
+ /*! \note [DDNS] Update forwarding fields. */
+ int fwd_src_fd; /*!< Query originator fd. */
uint16_t tsig_rcode;
uint64_t tsig_prev_time_signed;
@@ -158,12 +161,14 @@ typedef enum knot_ns_transport {
*/
typedef enum knot_ns_xfr_type_t {
/* DNS events. */
- XFR_TYPE_AIN = 1 << 0, /*!< AXFR-IN request (start transfer). */
- XFR_TYPE_AOUT= 1 << 1, /*!< AXFR-OUT request (incoming transfer). */
- XFR_TYPE_IIN = 1 << 2, /*!< IXFR-IN request (start transfer). */
- XFR_TYPE_IOUT = 1 << 3, /*!< IXFR-OUT request (incoming transfer). */
- XFR_TYPE_SOA = 1 << 4, /*!< Pending SOA request. */
- XFR_TYPE_NOTIFY = 1 << 5 /*!< Pending NOTIFY query. */
+ XFR_TYPE_AIN = 1 << 0, /*!< AXFR-IN request (start transfer). */
+ XFR_TYPE_AOUT= 1 << 1, /*!< AXFR-OUT request (incoming transfer). */
+ XFR_TYPE_IIN = 1 << 2, /*!< IXFR-IN request (start transfer). */
+ XFR_TYPE_IOUT = 1 << 3, /*!< IXFR-OUT request (incoming transfer). */
+ XFR_TYPE_SOA = 1 << 4, /*!< Pending SOA request. */
+ XFR_TYPE_NOTIFY = 1 << 5, /*!< Pending NOTIFY query. */
+ XFR_TYPE_UPDATE = 1 << 6, /*!< UPDATE request (incoming UPDATE). */
+ XFR_TYPE_FORWARD = 1 << 7 /*!< UPDATE forward request. */
} knot_ns_xfr_type_t;
/*----------------------------------------------------------------------------*/
@@ -227,6 +232,10 @@ int knot_ns_prep_normal_response(knot_nameserver_t *nameserver,
knot_packet_t *query, knot_packet_t **resp,
const knot_zone_t **zone, size_t max_size);
+int knot_ns_prep_update_response(knot_nameserver_t *nameserver,
+ knot_packet_t *query, knot_packet_t **resp,
+ knot_zone_t **zone, size_t max_size);
+
/*!
* \brief Creates a response for the given normal query using the data of the
* nameserver.
@@ -339,9 +348,14 @@ int knot_ns_switch_zone(knot_nameserver_t *nameserver,
int knot_ns_process_ixfrin(knot_nameserver_t *nameserver,
knot_ns_xfr_t *xfr);
-int knot_ns_process_update(knot_nameserver_t *nameserver, knot_packet_t *query,
- uint8_t *response_wire, size_t *rsize,
- knot_zone_t **zone, knot_changeset_t **changeset);
+int knot_ns_process_update(const knot_packet_t *query,
+ const knot_zone_contents_t *zone,
+ knot_changeset_t *changeset, knot_rcode_t *rcode);
+
+int knot_ns_process_update2(const knot_packet_t *query,
+ knot_zone_contents_t *old_contents,
+ knot_zone_contents_t **new_contents,
+ knot_changesets_t *chgs, knot_rcode_t *rcode);
int knot_ns_create_forward_query(const knot_packet_t *query,
uint8_t *query_wire, size_t *size);
diff --git a/src/libknot/packet/packet.c b/src/libknot/packet/packet.c
index 6a047fb..9b7e7c7 100644
--- a/src/libknot/packet/packet.c
+++ b/src/libknot/packet/packet.c
@@ -291,22 +291,25 @@ static int knot_packet_parse_question(const uint8_t *wire, size_t *pos,
dbg_packet_verb("Parsing dname starting on position %zu and "
"%zu bytes long.\n", *pos, i - *pos + 1);
dbg_packet_verb("Alloc: %d\n", alloc);
+ size_t bp = *pos;
if (alloc) {
- question->qname = knot_dname_new_from_wire(
- wire + *pos, i - *pos + 1, NULL);
+ question->qname = knot_dname_parse_from_wire(wire, pos,
+ i + 1,
+ NULL, NULL);
if (question->qname == NULL) {
return KNOT_ENOMEM;
}
} else {
- int res = knot_dname_from_wire(wire + *pos, i - *pos + 1,
- NULL, question->qname);
- if (res != KNOT_EOK) {
- assert(res != KNOT_EINVAL);
- return res;
+ void *parsed = knot_dname_parse_from_wire(wire, pos,
+ i + 1,
+ NULL, question->qname);
+ if (!parsed) {
+ return KNOT_EMALF;
}
}
-
- *pos = i + 1;
+ if (*pos != i + 1) {
+ dbg_packet("Parsed dname expected len=%zu, parsed=%zu.\n", i+1-bp, *pos-bp);
+ }
question->qtype = knot_wire_read_u16(wire + i + 1);
question->qclass = knot_wire_read_u16(wire + i + 3);
*pos += 4;
@@ -386,7 +389,7 @@ static knot_rrset_t *knot_packet_parse_rr(const uint8_t *wire, size_t *pos,
*pos, size);
knot_dname_t *owner = knot_dname_parse_from_wire(wire, pos, size,
- NULL);
+ NULL, NULL);
dbg_packet_detail("Created owner: %p, actual position: %zu\n", owner,
*pos);
if (owner == NULL) {
@@ -512,7 +515,7 @@ dbg_packet_exec_detail(
free(name);
);
- if (knot_rrset_compare((*rrsets)[i], rrset,
+ if (knot_rrset_match((*rrsets)[i], rrset,
KNOT_RRSET_COMPARE_HEADER)) {
/*! \todo Test this!!! */
// no duplicate checking here, the packet should
@@ -1257,7 +1260,7 @@ const knot_rrset_t *knot_packet_answer_rrset(
/*----------------------------------------------------------------------------*/
const knot_rrset_t *knot_packet_authority_rrset(
- knot_packet_t *packet, short pos)
+ const knot_packet_t *packet, short pos)
{
if (packet == NULL || pos > packet->ns_rrsets) {
return NULL;
@@ -1269,7 +1272,7 @@ const knot_rrset_t *knot_packet_authority_rrset(
/*----------------------------------------------------------------------------*/
const knot_rrset_t *knot_packet_additional_rrset(
- knot_packet_t *packet, short pos)
+ const knot_packet_t *packet, short pos)
{
if (packet == NULL || pos > packet->ar_rrsets) {
return NULL;
@@ -1289,19 +1292,19 @@ int knot_packet_contains(const knot_packet_t *packet,
}
for (int i = 0; i < packet->an_rrsets; ++i) {
- if (knot_rrset_compare(packet->answer[i], rrset, cmp)) {
+ if (knot_rrset_match(packet->answer[i], rrset, cmp)) {
return 1;
}
}
for (int i = 0; i < packet->ns_rrsets; ++i) {
- if (knot_rrset_compare(packet->authority[i], rrset, cmp)) {
+ if (knot_rrset_match(packet->authority[i], rrset, cmp)) {
return 1;
}
}
for (int i = 0; i < packet->ar_rrsets; ++i) {
- if (knot_rrset_compare(packet->additional[i], rrset, cmp)) {
+ if (knot_rrset_match(packet->additional[i], rrset, cmp)) {
return 1;
}
}
@@ -1390,7 +1393,7 @@ void knot_packet_header_to_wire(const knot_header_t *header,
int knot_packet_question_to_wire(knot_packet_t *packet)
{
- if (packet == NULL) {
+ if (packet == NULL || packet->question.qname == NULL) {
return KNOT_EINVAL;
}
@@ -1551,6 +1554,10 @@ void knot_packet_dump(const knot_packet_t *packet)
knot_wire_flags_get_ra(packet->header.flags2) ? "ra" : "",
knot_wire_flags_get_ad(packet->header.flags2) ? "ad" : "",
knot_wire_flags_get_cd(packet->header.flags2) ? "cd" : "");
+ dbg_packet(" RCODE: %u\n", knot_wire_flags_get_rcode(
+ packet->header.flags2));
+ dbg_packet(" OPCODE: %u\n", knot_wire_flags_get_opcode(
+ packet->header.flags1));
dbg_packet(" QDCOUNT: %u\n", packet->header.qdcount);
dbg_packet(" ANCOUNT: %u\n", packet->header.ancount);
dbg_packet(" NSCOUNT: %u\n", packet->header.nscount);
diff --git a/src/libknot/packet/packet.h b/src/libknot/packet/packet.h
index 522ae8e..5a95bae 100644
--- a/src/libknot/packet/packet.h
+++ b/src/libknot/packet/packet.h
@@ -451,7 +451,7 @@ const knot_rrset_t *knot_packet_answer_rrset(
* or NULL if there is no such RRSet.
*/
const knot_rrset_t *knot_packet_authority_rrset(
- knot_packet_t *packet, short pos);
+ const knot_packet_t *packet, short pos);
/*!
* \brief Returns the requested Additional RRset.
@@ -465,7 +465,7 @@ const knot_rrset_t *knot_packet_authority_rrset(
* or NULL if there is no such RRSet.
*/
const knot_rrset_t *knot_packet_additional_rrset(
- knot_packet_t *packet, short pos);
+ const knot_packet_t *packet, short pos);
/*!
* \brief Checks if the packet already contains the given RRSet.
diff --git a/src/libknot/packet/query.h b/src/libknot/packet/query.h
index cda72b9..3ca4fd3 100644
--- a/src/libknot/packet/query.h
+++ b/src/libknot/packet/query.h
@@ -81,6 +81,8 @@ int knot_query_set_opcode(knot_packet_t *query, uint8_t opcode);
int knot_query_add_rrset_authority(knot_packet_t *query,
const knot_rrset_t *rrset);
+int knot_query_rr_to_wire(const knot_rrset_t *rrset, const knot_rdata_t *rdata,
+ uint8_t **wire, uint8_t *endp);
#endif /* _KNOT_QUERY_H_ */
diff --git a/src/libknot/packet/response.c b/src/libknot/packet/response.c
index c6a1a09..476c6b3 100644
--- a/src/libknot/packet/response.c
+++ b/src/libknot/packet/response.c
@@ -903,7 +903,8 @@ int knot_response_init(knot_packet_t *response)
/*----------------------------------------------------------------------------*/
int knot_response_init_from_query(knot_packet_t *response,
- knot_packet_t *query)
+ knot_packet_t *query,
+ int copy_question)
{
if (response == NULL || query == NULL) {
@@ -913,21 +914,29 @@ int knot_response_init_from_query(knot_packet_t *response,
// copy the header from the query
memcpy(&response->header, &query->header, sizeof(knot_header_t));
- // copy the Question section (but do not copy the QNAME)
- memcpy(&response->question, &query->question,
- sizeof(knot_question_t));
-
int err = 0;
- // put the qname into the compression table
- // TODO: get rid of the numeric constants
- if ((err = knot_response_store_dname_pos(&response->compression,
- response->question.qname, 0, 12, 12, 0)) != KNOT_EOK) {
- return err;
- }
+ /*! \todo Constant. */
+ size_t to_copy = 12;
+
+ if (copy_question) {
+ // copy the Question section (but do not copy the QNAME)
+ memcpy(&response->question, &query->question,
+ sizeof(knot_question_t));
+
+ // put the qname into the compression table
+ // TODO: get rid of the numeric constants
+ if ((err = knot_response_store_dname_pos(&response->compression,
+ response->question.qname, 0, 12, 12, 0))
+ != KNOT_EOK) {
+ return err;
+ }
- // copy the wireformat of Header and Question from the query
- // TODO: get rid of the numeric constants
- size_t to_copy = 12 + 4 + knot_dname_size(response->question.qname);
+ /*! \todo Constant. */
+ to_copy += 4 + knot_dname_size(response->question.qname);
+ } else {
+ response->header.qdcount = 0;
+ knot_wire_set_qdcount(response->wireformat, 0);
+ }
assert(response->max_size >= to_copy);
memcpy(response->wireformat, query->wireformat, to_copy);
diff --git a/src/libknot/packet/response.h b/src/libknot/packet/response.h
index beb1a59..277b1aa 100644
--- a/src/libknot/packet/response.h
+++ b/src/libknot/packet/response.h
@@ -62,7 +62,8 @@ int knot_response_init(knot_packet_t *response);
* \retval KNOT_EOK
*/
int knot_response_init_from_query(knot_packet_t *response,
- knot_packet_t *query);
+ knot_packet_t *query,
+ int copy_question);
/*!
* \brief Clears the response structure for reuse.
diff --git a/src/libknot/rdata.c b/src/libknot/rdata.c
index 9bcdbe5..352bb6c 100644
--- a/src/libknot/rdata.c
+++ b/src/libknot/rdata.c
@@ -242,7 +242,7 @@ int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire,
case KNOT_RDATA_WF_LITERAL_DNAME:
pos2 = *pos;
dname = knot_dname_parse_from_wire(
- wire, &pos2, total_size, NULL);
+ wire, &pos2, total_size, NULL, NULL);
if (dname == NULL) {
free(items);
return KNOT_ERROR;
@@ -308,7 +308,7 @@ int knot_rdata_from_wire(knot_rdata_t *rdata, const uint8_t *wire,
case 3:
pos2 = *pos;
dname = knot_dname_parse_from_wire(
- wire, &pos2, total_size, NULL);
+ wire, &pos2, total_size, NULL, NULL);
if (dname == NULL) {
knot_rdata_free_items(items, i,
desc->type, 1);
@@ -698,6 +698,18 @@ int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata)
}
/*---------------------------------------------------------------------------*/
+void knot_rdata_soa_serial_set(knot_rdata_t *rdata, uint32_t serial)
+{
+ if (!rdata || rdata->count < 3) {
+ return;
+ }
+
+ // the number is in network byte order, transform it
+ knot_wire_write_u32((uint8_t *)(rdata->items[2].raw_data + 1),
+ serial);
+}
+
+/*---------------------------------------------------------------------------*/
uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata)
{
@@ -816,3 +828,43 @@ const uint8_t *knot_rdata_nsec3_salt(const knot_rdata_t *rdata)
return ((uint8_t *)(rdata->items[3].raw_data + 1)) + 1;
}
+
+/*----------------------------------------------------------------------------*/
+
+uint8_t knot_rdata_ds_digest_type(const knot_rdata_t *rdata)
+{
+ if (rdata->count < 3) {
+ return 0;
+ }
+
+ return *((uint8_t *)(rdata->items[2].raw_data + 1));
+}
+
+/*----------------------------------------------------------------------------*/
+
+uint16_t knot_rdata_ds_digest_len(const knot_rdata_t *rdata)
+{
+ if (rdata->count < 4) {
+ return 0;
+ }
+
+ return *(rdata->items[3].raw_data);
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_rdata_ds_check(const knot_rdata_t *rdata)
+{
+ // Check if the legth of the digest corresponds to the proper size of
+ // the digest according to the given algorithm
+ uint16_t len = knot_rdata_ds_digest_len(rdata);
+ uint8_t type = knot_rdata_ds_digest_type(rdata);
+
+ if (type == 0 || len == 0) {
+ return KNOT_EINVAL;
+ } else if (len != knot_ds_digest_length(type)) {
+ return KNOT_EDSDIGESTLEN;
+ } else {
+ return KNOT_EOK;
+ }
+}
diff --git a/src/libknot/rdata.h b/src/libknot/rdata.h
index 57517bd..eb7bd55 100644
--- a/src/libknot/rdata.h
+++ b/src/libknot/rdata.h
@@ -329,6 +329,7 @@ const knot_dname_t *knot_rdata_get_name(const knot_rdata_t *rdata,
uint16_t type);
int64_t knot_rdata_soa_serial(const knot_rdata_t *rdata);
+void knot_rdata_soa_serial_set(knot_rdata_t *rdata, uint32_t serial);
uint32_t knot_rdata_soa_refresh(const knot_rdata_t *rdata);
uint32_t knot_rdata_soa_retry(const knot_rdata_t *rdata);
@@ -342,6 +343,10 @@ uint16_t knot_rdata_nsec3_iterations(const knot_rdata_t *rdata);
uint8_t knot_rdata_nsec3_salt_length(const knot_rdata_t *rdata);
const uint8_t *knot_rdata_nsec3_salt(const knot_rdata_t *rdata);
+uint8_t knot_rdata_ds_digest_type(const knot_rdata_t *rdata);
+uint16_t knot_rdata_ds_digest_len(const knot_rdata_t *rdata);
+int knot_rdata_ds_check(const knot_rdata_t *rdata);
+
#endif /* _KNOT_RDATA_H */
/*! @} */
diff --git a/src/libknot/rrset.c b/src/libknot/rrset.c
index d8b10ce..84d5075 100644
--- a/src/libknot/rrset.c
+++ b/src/libknot/rrset.c
@@ -215,7 +215,7 @@ int knot_rrset_add_rrsigs(knot_rrset_t *rrset, knot_rrset_t *rrsigs,
if (dupl == KNOT_RRSET_DUPL_MERGE) {
rc = knot_rrset_merge_no_dupl((void **)&rrset->rrsigs,
(void **)&rrsigs);
- if (rc != KNOT_EOK) {
+ if (rc < 0) {
return rc;
} else {
return 1;
@@ -280,6 +280,15 @@ void knot_rrset_set_ttl(knot_rrset_t *rrset, uint32_t ttl)
/*----------------------------------------------------------------------------*/
+void knot_rrset_set_class(knot_rrset_t *rrset, uint16_t rclass)
+{
+ if (rrset) {
+ rrset->rclass = rclass;
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+
uint16_t knot_rrset_type(const knot_rrset_t *rrset)
{
return rrset->type;
@@ -604,7 +613,7 @@ int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size,
/*----------------------------------------------------------------------------*/
-int knot_rrset_compare(const knot_rrset_t *r1,
+int knot_rrset_match(const knot_rrset_t *r1,
const knot_rrset_t *r2,
knot_rrset_compare_type_t cmp)
{
@@ -838,6 +847,7 @@ dbg_rrset_exec_detail(
}
knot_rdata_t *walk2 = rrset2->rdata;
+ int deleted = 0;
// no RDATA in RRSet 1
if (rrset1->rdata == NULL && rrset2->rdata != NULL) {
@@ -934,6 +944,7 @@ dbg_rrset_exec_detail(
knot_rdata_deep_free(&tmp, rrset1->type, 1);
assert(tmp == NULL);
/* Maybe caller should be warned about this. */
+ ++deleted;
}
}
@@ -954,5 +965,5 @@ dbg_rrset_exec_detail(
*/
rrset2->rdata = NULL;
- return KNOT_EOK;
+ return deleted;
}
diff --git a/src/libknot/rrset.h b/src/libknot/rrset.h
index b5b62db..077b067 100644
--- a/src/libknot/rrset.h
+++ b/src/libknot/rrset.h
@@ -163,6 +163,8 @@ void knot_rrset_set_owner(knot_rrset_t *rrset, knot_dname_t* owner);
void knot_rrset_set_ttl(knot_rrset_t *rrset, uint32_t ttl);
+void knot_rrset_set_class(knot_rrset_t *rrset, uint16_t rclass);
+
/*!
* \brief Returns the TYPE of the RRSet.
*
@@ -259,7 +261,7 @@ int knot_rrset_to_wire(const knot_rrset_t *rrset, uint8_t *wire, size_t *size,
* \retval <> 0 If RRSets are equal.
* \retval 0 if RRSets are not equal.
*/
-int knot_rrset_compare(const knot_rrset_t *r1,
+int knot_rrset_match(const knot_rrset_t *r1,
const knot_rrset_t *r2,
knot_rrset_compare_type_t cmp);
@@ -344,6 +346,8 @@ int knot_rrset_merge(void **r1, void **r2);
* \retval KNOT_EOK
* \retval KNOT_EINVAL if the RRSets could not be merged, because their
* Owner, Type, Class or TTL does not match.
+ * \retval >0 if some RDATA have been removed because they were duplicate. The
+ * return value indicates number of such RDATA.
*/
int knot_rrset_merge_no_dupl(void **r1, void **r2);
diff --git a/src/libknot/tsig-op.c b/src/libknot/tsig-op.c
index cb280ab..55316a9 100644
--- a/src/libknot/tsig-op.c
+++ b/src/libknot/tsig-op.c
@@ -108,20 +108,16 @@ static int knot_tsig_compute_digest(const uint8_t *wire, size_t wire_len,
char decoded_key[B64BUFSIZE];
memset(decoded_key, 0, sizeof(decoded_key));
- size_t decoded_key_size = B64BUFSIZE;
- int ret = base64_decode(key->secret, strlen(key->secret),
- decoded_key,
- &decoded_key_size);
- if (ret != 1) {
- dbg_tsig("TSIG: New decode function failed! (%d)\n", ret);
- return KNOT_ERROR;
- }
-
- if (decoded_key_size < 0) {
+ int32_t ret = base64_decode((uint8_t *)key->secret, strlen(key->secret),
+ (uint8_t *)decoded_key, B64BUFSIZE);
+
+ if (ret < 0) {
dbg_tsig("TSIG: Could not decode Base64\n");
return KNOT_ERROR;
}
+ size_t decoded_key_size = ret;
+
dbg_tsig_detail("TSIG: decoded key size: %d\n", decoded_key_size);
dbg_tsig_detail("TSIG: decoded key:\n");
dbg_tsig_hex_detail(decoded_key, decoded_key_size);
@@ -221,14 +217,14 @@ static int knot_tsig_write_tsig_variables(uint8_t *wire,
/* Copy class. */
knot_wire_write_u16(wire + offset, knot_rrset_class(tsig_rr));
- dbg_tsig_verb("TSIG: write variables: written CLASS: %u - ",
+ dbg_tsig_verb("TSIG: write variables: written CLASS: %u - \n",
knot_rrset_class(tsig_rr));
dbg_tsig_hex_detail(wire + offset, sizeof(uint16_t));
offset += sizeof(uint16_t);
/* Copy TTL - always 0. */
knot_wire_write_u32(wire + offset, knot_rrset_ttl(tsig_rr));
- dbg_tsig_verb("TSIG: write variables: written TTL: %u - ",
+ dbg_tsig_verb("TSIG: write variables: written TTL: %u - \n",
knot_rrset_ttl(tsig_rr));
dbg_tsig_hex_detail(wire + offset, sizeof(uint32_t));
offset += sizeof(uint32_t);
@@ -816,6 +812,9 @@ static int knot_tsig_check_digest(const knot_rrset_t *tsig_rr,
memset(wire_to_sign, 0, sizeof(uint8_t) * size);
memcpy(wire_to_sign, wire, size);
+
+ /* Restore message id. */
+ knot_wire_set_id(wire_to_sign, tsig_rdata_orig_id(tsig_rr));
/* Decrease arcount. */
knot_wire_set_arcount(wire_to_sign,
@@ -864,10 +863,10 @@ static int knot_tsig_check_digest(const knot_rrset_t *tsig_rr,
return KNOT_TSIG_EBADSIG;
}
- dbg_tsig_verb("TSIG: calc digest : ");
+ dbg_tsig_verb("TSIG: calc digest :\n");
dbg_tsig_hex_verb(digest_tmp, digest_tmp_len);
- dbg_tsig_verb("TSIG: given digest: ");
+ dbg_tsig_verb("TSIG: given digest:\n");
dbg_tsig_hex_verb(tsig_mac, mac_length);
if (strncasecmp((char *)(tsig_mac), (char *)digest_tmp,
diff --git a/src/libknot/updates/changesets.c b/src/libknot/updates/changesets.c
index ab83b07..5405726 100644
--- a/src/libknot/updates/changesets.c
+++ b/src/libknot/updates/changesets.c
@@ -66,7 +66,7 @@ static int knot_changeset_check_count(knot_rrset_t ***rrsets, size_t count,
static int knot_changeset_rrsets_match(const knot_rrset_t *rrset1,
const knot_rrset_t *rrset2)
{
- return knot_rrset_compare(rrset1, rrset2, KNOT_RRSET_COMPARE_HEADER)
+ return knot_rrset_match(rrset1, rrset2, KNOT_RRSET_COMPARE_HEADER)
&& (knot_rrset_type(rrset1) != KNOT_RRTYPE_RRSIG
|| knot_rdata_rrsig_type_covered(
knot_rrset_rdata(rrset1))
@@ -76,7 +76,8 @@ static int knot_changeset_rrsets_match(const knot_rrset_t *rrset1,
/*----------------------------------------------------------------------------*/
-int knot_changeset_allocate(knot_changesets_t **changesets)
+int knot_changeset_allocate(knot_changesets_t **changesets,
+ uint32_t flags)
{
// create new changesets
*changesets = (knot_changesets_t *)(malloc(sizeof(knot_changesets_t)));
@@ -85,8 +86,15 @@ int knot_changeset_allocate(knot_changesets_t **changesets)
}
memset(*changesets, 0, sizeof(knot_changesets_t));
+ (*changesets)->flags = flags;
- return knot_changesets_check_size(*changesets);
+ if (knot_changesets_check_size(*changesets) != KNOT_EOK) {
+ free(*changesets);
+ *changesets = NULL;
+ return KNOT_ENOMEM;
+ }
+
+ return KNOT_EOK;
}
/*----------------------------------------------------------------------------*/
@@ -138,19 +146,19 @@ int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count,
int knot_changeset_add_new_rr(knot_changeset_t *changeset,
knot_rrset_t *rrset,
- xfrin_changeset_part_t part)
+ knot_changeset_part_t part)
{
knot_rrset_t ***rrsets = NULL;
size_t *count = NULL;
size_t *allocated = NULL;
switch (part) {
- case XFRIN_CHANGESET_ADD:
+ case KNOT_CHANGESET_ADD:
rrsets = &changeset->add;
count = &changeset->add_count;
allocated = &changeset->add_allocated;
break;
- case XFRIN_CHANGESET_REMOVE:
+ case KNOT_CHANGESET_REMOVE:
rrsets = &changeset->remove;
count = &changeset->remove_count;
allocated = &changeset->remove_allocated;
@@ -173,6 +181,29 @@ int knot_changeset_add_new_rr(knot_changeset_t *changeset,
/*----------------------------------------------------------------------------*/
+knot_rrset_t *knot_changeset_remove_rr(knot_rrset_t **rrsets, size_t *count,
+ int pos)
+{
+ if (pos >= *count || *count == 0) {
+ return NULL;
+ }
+
+ knot_rrset_t *removed = rrsets[pos];
+
+ // shift all RRSets from pos+1 one cell to the left
+ for (int i = pos; i < *count - 1; ++i) {
+ rrsets[i] = rrsets[i + 1];
+ }
+
+ // just to be sure, set the last previously occupied position to NULL
+ rrsets[*count - 1] = NULL;
+ *count -= 1;
+
+ return removed;
+}
+
+/*----------------------------------------------------------------------------*/
+
void knot_changeset_store_soa(knot_rrset_t **chg_soa,
uint32_t *chg_serial, knot_rrset_t *soa)
{
@@ -183,14 +214,14 @@ void knot_changeset_store_soa(knot_rrset_t **chg_soa,
/*----------------------------------------------------------------------------*/
int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa,
- xfrin_changeset_part_t part)
+ knot_changeset_part_t part)
{
switch (part) {
- case XFRIN_CHANGESET_ADD:
+ case KNOT_CHANGESET_ADD:
knot_changeset_store_soa(&changeset->soa_to,
&changeset->serial_to, soa);
break;
- case XFRIN_CHANGESET_REMOVE:
+ case KNOT_CHANGESET_REMOVE:
knot_changeset_store_soa(&changeset->soa_from,
&changeset->serial_from, soa);
break;
@@ -212,7 +243,8 @@ int knot_changesets_check_size(knot_changesets_t *changesets)
}
/* How many steps is needed to content count? */
- size_t extra = (changesets->count - changesets->allocated) % KNOT_CHANGESET_STEP;
+ size_t extra = (changesets->count - changesets->allocated)
+ % KNOT_CHANGESET_STEP;
extra = (extra + 1) * KNOT_CHANGESET_STEP;
/* Allocate new memory block. */
@@ -223,10 +255,15 @@ int knot_changesets_check_size(knot_changesets_t *changesets)
return KNOT_ENOMEM;
}
- /* Clear old memory block and copy old data. */
+ /* Clear new memory block and copy old data. */
memset(sets, 0, new_count * item_len);
memcpy(sets, changesets->sets, changesets->allocated * item_len);
+ /* Set type to all newly allocated changesets. */
+ for (int i = changesets->allocated; i < new_count; ++i) {
+ sets[i].flags = changesets->flags;
+ }
+
/* Replace old changesets. */
free(changesets->sets);
changesets->sets = sets;
@@ -237,6 +274,32 @@ int knot_changesets_check_size(knot_changesets_t *changesets)
/*----------------------------------------------------------------------------*/
+void knot_changeset_set_flags(knot_changeset_t *changeset,
+ uint32_t flags)
+{
+ changeset->flags = flags;
+}
+
+/*----------------------------------------------------------------------------*/
+
+uint32_t knot_changeset_flags(knot_changeset_t *changeset)
+{
+ return changeset->flags;
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_changeset_is_empty(const knot_changeset_t *changeset)
+{
+ if (changeset == NULL) {
+ return 0;
+ }
+
+ return (changeset->add_count == 0 && changeset->remove_count == 0);
+}
+
+/*----------------------------------------------------------------------------*/
+
void knot_free_changeset(knot_changeset_t **changeset)
{
assert((*changeset)->add_allocated >= (*changeset)->add_count);
@@ -287,3 +350,274 @@ void knot_free_changesets(knot_changesets_t **changesets)
free(*changesets);
*changesets = NULL;
}
+
+/*----------------------------------------------------------------------------*/
+/* knot_changes_t manipulation */
+/*----------------------------------------------------------------------------*/
+
+int knot_changes_rrsets_reserve(knot_rrset_t ***rrsets,
+ int *count, int *allocated, int to_add)
+{
+ if (rrsets == NULL || count == NULL || allocated == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (*count + to_add <= *allocated) {
+ return KNOT_EOK;
+ }
+
+ int new_count = (*allocated == 0) ? 2 : *allocated * 2;
+ while (new_count < *count + to_add) {
+ new_count *= 2;
+ }
+
+ /* Allocate new memory block. */
+ knot_rrset_t **rrsets_new = malloc(new_count * sizeof(knot_rrset_t *));
+ if (rrsets_new == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ /* Initialize new memory and copy old data. */
+ memset(rrsets_new, 0, new_count * sizeof(knot_rrset_t *));
+ memcpy(rrsets_new, *rrsets, (*allocated) * sizeof(knot_rrset_t *));
+
+ /* Free old nodes and switch pointers. */
+ free(*rrsets);
+ *rrsets = rrsets_new;
+ *allocated = new_count;
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_changes_nodes_reserve(knot_node_t ***nodes,
+ int *count, int *allocated)
+{
+ if (nodes == NULL || count == NULL || allocated == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (*count + 2 <= *allocated) {
+ return KNOT_EOK;
+ }
+
+ int new_count = (*allocated == 0) ? 2 : *allocated * 2;
+
+ /* Allocate new memory block. */
+ const size_t node_len = sizeof(knot_node_t *);
+ knot_node_t **nodes_new = malloc(new_count * node_len);
+ if (nodes_new == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ /* Clear memory block and copy old data. */
+ memset(nodes_new, 0, new_count * node_len);
+ memcpy(nodes_new, *nodes, (*allocated) * node_len);
+
+ /* Free old nodes and switch pointers. */
+ free(*nodes);
+ *nodes = nodes_new;
+ *allocated = new_count;
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_changes_rdata_reserve(knot_rdata_t ***rdatas, uint16_t **types,
+ int count, int *allocated, int to_add)
+{
+ if (rdatas == NULL || types == NULL || allocated == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (count + to_add <= *allocated) {
+ return KNOT_EOK;
+ }
+
+ int new_count = (*allocated == 0) ? 2 : *allocated * 2;
+ while (new_count < count + to_add) {
+ new_count *= 2;
+ }
+
+ /* Allocate new memory block. */
+ knot_rdata_t **rdatas_new = malloc(new_count * sizeof(knot_rdata_t *));
+ if (rdatas_new == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ uint16_t *types_new = malloc(new_count * sizeof(uint));
+ if (types_new == NULL) {
+ free(rdatas_new);
+ return KNOT_ENOMEM;
+ }
+
+ /* Initialize new memory and copy old data. */
+ memset(rdatas_new, 0, new_count * sizeof(knot_rdata_t *));
+ memcpy(rdatas_new, *rdatas, (*allocated) * sizeof(knot_rdata_t *));
+
+ memset(types_new, 0, new_count * sizeof(uint));
+ memcpy(types_new, *types, (*allocated) * sizeof(uint));
+
+ /* Free old rdatas and switch pointers. */
+ free(*rdatas);
+ free(*types);
+ *rdatas = rdatas_new;
+ *types = types_new;
+ *allocated = new_count;
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+void knot_changes_add_rdata(knot_rdata_t **rdatas, uint16_t *types,
+ int *count, knot_rdata_t *rdata, uint16_t type)
+{
+ if (rdatas == NULL || types == NULL || count == NULL || rdata == NULL) {
+ return;
+ }
+
+ // Add all RDATAs from the chain!!
+
+ knot_rdata_t *r = rdata;
+ do {
+// dbg_xfrin_detail("Adding RDATA to RDATA list: %p\n", r);
+ rdatas[*count] = r;
+ types[*count] = type;
+ ++*count;
+
+ r = r->next;
+ } while (r != NULL && r != rdata);
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_changes_add_old_rrsets(knot_rrset_t **rrsets, int count,
+ knot_changes_t *changes, int add_rdata)
+{
+ if (rrsets == NULL || changes == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (count == 0) {
+ return KNOT_EOK;
+ }
+
+ /* Reserve twice the space, to have enough space for RRSIGs if
+ * there are some.
+ */
+ int ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
+ &changes->old_rrsets_count,
+ &changes->old_rrsets_allocated,
+ 2 * count);
+ if (ret != KNOT_EOK) {
+// dbg_xfrin("Failed to reserve changes rrsets.\n");
+ return ret;
+ }
+
+ /* Mark RRsets and RDATA for removal. */
+ for (unsigned i = 0; i < count; ++i) {
+ if (rrsets[i] == NULL) {
+ continue;
+ }
+
+ knot_rrset_t *rrsigs = knot_rrset_get_rrsigs(rrsets[i]);
+
+ if (add_rdata) {
+
+ /* RDATA count in the RRSet. */
+ int rdata_count = knot_rrset_rdata_rr_count(rrsets[i]);
+
+ if (rrsigs != NULL) {
+ /* Increment the RDATA count by the count of
+ * RRSIGs. */
+ rdata_count += knot_rrset_rdata_rr_count(
+ rrsigs);
+ }
+
+ /* Remove old RDATA. */
+
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
+ &changes->old_rdata_types,
+ changes->old_rdata_count,
+ &changes->old_rdata_allocated,
+ rdata_count);
+ if (ret != KNOT_EOK) {
+// dbg_xfrin("Failed to reserve changes rdata.\n");
+ return ret;
+ }
+
+ knot_changes_add_rdata(changes->old_rdata,
+ changes->old_rdata_types,
+ &changes->old_rdata_count,
+ knot_rrset_get_rdata(rrsets[i]),
+ knot_rrset_type(rrsets[i]));
+
+ knot_changes_add_rdata(changes->old_rdata,
+ changes->old_rdata_types,
+ &changes->old_rdata_count,
+ knot_rrset_get_rdata(rrsigs),
+ KNOT_RRTYPE_RRSIG);
+ }
+
+ /* Disconnect RRsigs from rrset. */
+ knot_rrset_set_rrsigs(rrsets[i], NULL);
+ changes->old_rrsets[changes->old_rrsets_count++] = rrsets[i];
+ changes->old_rrsets[changes->old_rrsets_count++] = rrsigs;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+int knot_changes_add_new_rrsets(knot_rrset_t **rrsets, int count,
+ knot_changes_t *changes, int add_rdata)
+{
+ if (rrsets == NULL || changes == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ if (count == 0) {
+ return KNOT_EOK;
+ }
+
+ int ret = knot_changes_rrsets_reserve(&changes->new_rrsets,
+ &changes->new_rrsets_count,
+ &changes->new_rrsets_allocated,
+ count);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Mark RRsets and RDATA for removal. */
+ for (unsigned i = 0; i < count; ++i) {
+ if (rrsets[i] == NULL) {
+ continue;
+ }
+
+ if (add_rdata) {
+ int rdata_count = knot_rrset_rdata_rr_count(rrsets[i]);
+ ret = knot_changes_rdata_reserve(&changes->new_rdata,
+ &changes->new_rdata_types,
+ changes->new_rdata_count,
+ &changes->new_rdata_allocated,
+ rdata_count);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ knot_changes_add_rdata(changes->new_rdata,
+ changes->new_rdata_types,
+ &changes->new_rdata_count,
+ knot_rrset_get_rdata(rrsets[i]),
+ knot_rrset_type(rrsets[i]));
+ }
+
+ changes->new_rrsets[changes->new_rrsets_count++] = rrsets[i];
+ }
+
+ return KNOT_EOK;
+}
diff --git a/src/libknot/updates/changesets.h b/src/libknot/updates/changesets.h
index 642b155..4585abc 100644
--- a/src/libknot/updates/changesets.h
+++ b/src/libknot/updates/changesets.h
@@ -30,6 +30,14 @@
#include "rrset.h"
#include "zone/node.h"
+/*----------------------------------------------------------------------------*/
+
+/*! \brief Changeset flags, stored as first 4 bytes in serialized changeset. */
+typedef enum {
+ KNOT_CHANGESET_TYPE_IXFR = 1 << 0,
+ KNOT_CHANGESET_TYPE_DDNS = 1 << 1
+} knot_changeset_flag_t;
+
/*! \todo Changeset must be serializable/deserializable, so
* all data and pointers have to be changeset-exclusive,
* or more advanced structure serialization scheme has to be
@@ -53,6 +61,8 @@ typedef struct {
size_t allocated;
uint32_t serial_from;
uint32_t serial_to;
+
+ uint32_t flags; /*!< DDNS / IXFR */
} knot_changeset_t;
/*----------------------------------------------------------------------------*/
@@ -69,7 +79,7 @@ typedef struct {
* Deleted after successful update.
*/
knot_rdata_t **old_rdata;
- unsigned *old_rdata_types;
+ uint16_t *old_rdata_types;
int old_rdata_count;
int old_rdata_allocated;
@@ -86,7 +96,7 @@ typedef struct {
* Deleted after failed update.
*/
knot_rdata_t **new_rdata;
- unsigned *new_rdata_types;
+ uint16_t *new_rdata_types;
int new_rdata_count;
int new_rdata_allocated;
@@ -109,19 +119,21 @@ typedef struct {
size_t count;
size_t allocated;
knot_rrset_t *first_soa;
+ uint32_t flags;
knot_changes_t *changes;
} knot_changesets_t;
/*----------------------------------------------------------------------------*/
typedef enum {
- XFRIN_CHANGESET_ADD,
- XFRIN_CHANGESET_REMOVE
-} xfrin_changeset_part_t;
+ KNOT_CHANGESET_ADD,
+ KNOT_CHANGESET_REMOVE
+} knot_changeset_part_t;
/*----------------------------------------------------------------------------*/
-int knot_changeset_allocate(knot_changesets_t **changesets);
+int knot_changeset_allocate(knot_changesets_t **changesets,
+ uint32_t flags);
int knot_changeset_add_rrset(knot_rrset_t ***rrsets,
size_t *count, size_t *allocated,
@@ -132,20 +144,51 @@ int knot_changeset_add_rr(knot_rrset_t ***rrsets, size_t *count,
int knot_changeset_add_new_rr(knot_changeset_t *changeset,
knot_rrset_t *rrset,
- xfrin_changeset_part_t part);
+ knot_changeset_part_t part);
+
+knot_rrset_t *knot_changeset_remove_rr(knot_rrset_t **rrsets, size_t *count,
+ int pos);
void knot_changeset_store_soa(knot_rrset_t **chg_soa,
uint32_t *chg_serial, knot_rrset_t *soa);
int knot_changeset_add_soa(knot_changeset_t *changeset, knot_rrset_t *soa,
- xfrin_changeset_part_t part);
+ knot_changeset_part_t part);
int knot_changesets_check_size(knot_changesets_t *changesets);
+void knot_changeset_set_flags(knot_changeset_t *changeset,
+ uint32_t flags);
+
+uint32_t knot_changeset_flags(knot_changeset_t *changeset);
+
+int knot_changeset_is_empty(const knot_changeset_t *changeset);
+
void knot_free_changeset(knot_changeset_t **changeset);
void knot_free_changesets(knot_changesets_t **changesets);
+int knot_changes_rrsets_reserve(knot_rrset_t ***rrsets,
+ int *count, int *allocated, int to_add);
+
+int knot_changes_nodes_reserve(knot_node_t ***nodes,
+ int *count, int *allocated);
+
+int knot_changes_rdata_reserve(knot_rdata_t ***rdatas, uint16_t **types,
+ int count, int *allocated, int to_add);
+
+void knot_changes_add_rdata(knot_rdata_t **rdatas, uint16_t *types,
+ int *count, knot_rdata_t *rdata, uint16_t type);
+
+/*!
+ * \note Also processes RRSIGs. May be switched by a parameter later, if needed.
+ */
+int knot_changes_add_old_rrsets(knot_rrset_t **rrsets, int count,
+ knot_changes_t *changes, int add_rdata);
+
+int knot_changes_add_new_rrsets(knot_rrset_t **rrsets, int count,
+ knot_changes_t *changes, int add_rdata);
+
#endif /* _KNOT_CHANGESETS_H_ */
/*! @} */
diff --git a/src/libknot/updates/ddns.c b/src/libknot/updates/ddns.c
index 72a1be9..90a578f 100644
--- a/src/libknot/updates/ddns.c
+++ b/src/libknot/updates/ddns.c
@@ -21,26 +21,24 @@
#include "util/debug.h"
#include "packet/packet.h"
#include "consts.h"
+#include "common/mempattern.h"
+#include "nameserver/name-server.h" // ns_serial_compare() - TODO: extract
+#include "updates/xfr-in.h"
/*----------------------------------------------------------------------------*/
// Copied from XFR - maybe extract somewhere else
static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets,
size_t *count, size_t *allocated)
{
- int new_count = 0;
- if (*count == *allocated) {
- new_count = *allocated * 2;
- }
-
- knot_rrset_t **rrsets_new =
- (knot_rrset_t **)calloc(new_count, sizeof(knot_rrset_t *));
- if (rrsets_new == NULL) {
+ /* This is really confusing, it's ptr -> array of "knot_rrset_t*" */
+ char *arr = (char*)*rrsets;
+ int ret = 0;
+ ret = mreserve(&arr, sizeof(knot_rrset_t*), *count + 1, 0, allocated);
+ if (ret < 0) {
return KNOT_ENOMEM;
}
-
- memcpy(rrsets_new, *rrsets, (*count) * sizeof(knot_rrset_t *));
- *rrsets = rrsets_new;
- *allocated = new_count;
+
+ *rrsets = (knot_rrset_t**)arr;
return KNOT_EOK;
}
@@ -50,20 +48,15 @@ static int knot_ddns_prereq_check_rrsets(knot_rrset_t ***rrsets,
static int knot_ddns_prereq_check_dnames(knot_dname_t ***dnames,
size_t *count, size_t *allocated)
{
- int new_count = 0;
- if (*count == *allocated) {
- new_count = *allocated * 2;
- }
-
- knot_dname_t **dnames_new =
- (knot_dname_t **)calloc(new_count, sizeof(knot_dname_t *));
- if (dnames_new == NULL) {
+ /* This is really confusing, it's ptr -> array of "knot_dname_t*" */
+ char *arr = (char*)*dnames;
+ int ret = 0;
+ ret = mreserve(&arr, sizeof(knot_dname_t*), *count + 1, 0, allocated);
+ if (ret < 0) {
return KNOT_ENOMEM;
}
-
- memcpy(dnames_new, *dnames, (*count) * sizeof(knot_dname_t *));
- *dnames = dnames_new;
- *allocated = new_count;
+
+ *dnames = (knot_dname_t**)arr;
return KNOT_EOK;
}
@@ -77,8 +70,8 @@ static int knot_ddns_add_prereq_rrset(const knot_rrset_t *rrset,
// check if such RRSet is not already there and merge if needed
int ret;
for (int i = 0; i < *count; ++i) {
- if (knot_rrset_compare(rrset, (*rrsets)[i],
- KNOT_RRSET_COMPARE_HEADER) == 0) {
+ if (knot_rrset_match(rrset, (*rrsets)[i],
+ KNOT_RRSET_COMPARE_HEADER) == 1) {
ret = knot_rrset_merge((void **)&((*rrsets)[i]),
(void **)&rrset);
if (ret != KNOT_EOK) {
@@ -188,39 +181,119 @@ static int knot_ddns_add_prereq(knot_ddns_prereq_t *prereqs,
/*----------------------------------------------------------------------------*/
+static int knot_ddns_check_remove_rr(knot_changeset_t *changeset,
+ const knot_rrset_t *rr)
+{
+ dbg_ddns_verb("Removing possible redundant RRs from changeset.\n");
+ for (int i = 0; i < changeset->add_count; ++i) {
+ // Removing RR(s) from this owner
+ if (knot_dname_compare(knot_rrset_owner(rr),
+ knot_rrset_owner(changeset->add[i])) == 0) {
+ // Removing one or all RRSets
+ if (knot_rrset_class(rr) == KNOT_CLASS_ANY) {
+ dbg_ddns_detail("Removing one or all "
+ "RRSets\n");
+ if (knot_rrset_type(rr)
+ == knot_rrset_type(changeset->add[i])
+ || knot_rrset_type(rr) == KNOT_RRTYPE_ANY) {
+ knot_rrset_t *remove =
+ knot_changeset_remove_rr(
+ changeset->add,
+ &changeset->add_count, i);
+ dbg_ddns_detail("Removed RRSet from "
+ "chgset:\n");
+ knot_rrset_dump(remove, 0);
+ knot_rrset_deep_free(&remove, 1, 1, 1);
+ }
+ } else if (knot_rrset_type(rr)
+ == knot_rrset_type(changeset->add[i])){
+ /* All other classes are checked in
+ * knot_ddns_check_update().
+ */
+ assert(knot_rrset_class(rr) == KNOT_CLASS_NONE);
+
+ // Removing specific RR from a RRSet
+ knot_rdata_t *rdata = knot_rrset_remove_rdata(
+ changeset->add[i],
+ knot_rrset_rdata(rr));
+
+ dbg_ddns_detail("Removed RR from chgset: \n");
+ knot_rdata_dump(rdata, knot_rrset_type(rr), 0);
+
+ knot_rdata_deep_free(&rdata,
+ knot_rrset_type(changeset->add[i]), 1);
+ // if the RRSet is empty, remove from changeset
+ if (knot_rrset_rdata_rr_count(changeset->add[i])
+ == 0) {
+ knot_rrset_t *remove =
+ knot_changeset_remove_rr(
+ changeset->add,
+ &changeset->add_count, i);
+ dbg_ddns_detail("RRSet empty, removing."
+ "\n");
+ knot_rrset_deep_free(&remove, 1, 1, 1);
+ }
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
static int knot_ddns_add_update(knot_changeset_t *changeset,
- const knot_rrset_t *rrset, uint16_t qclass)
+ const knot_rrset_t *rrset, uint16_t qclass,
+ knot_rrset_t **rrset_copy)
{
assert(changeset != NULL);
assert(rrset != NULL);
+ assert(rrset_copy != NULL);
int ret;
// create a copy of the RRSet
- /*! \todo If the packet was not parsed all at once, we could save this
+ /*! \todo ref #937 If the packet was not parsed all at once, we could save this
* copy.
*/
- knot_rrset_t *rrset_copy;
- ret = knot_rrset_deep_copy(rrset, &rrset_copy, 0);
+ *rrset_copy = NULL;
+ ret = knot_rrset_deep_copy(rrset, rrset_copy, 0);
if (ret != KNOT_EOK) {
return ret;
}
- /*! \todo What about the SOAs? */
-
if (knot_rrset_class(rrset) == qclass) {
// this RRSet should be added to the zone
+ dbg_ddns_detail(" * adding RR %p\n", *rrset_copy);
ret = knot_changeset_add_rr(&changeset->add,
&changeset->add_count,
&changeset->add_allocated,
- rrset_copy);
+ *rrset_copy);
} else {
// this RRSet marks removal of something from zone
- // what should be removed is distinguished when applying
+
+ /* To imitate in-order processing of UPDATE RRs, we must check
+ * If this REMOVE RR does not affect any of the previous
+ * ADD RRs in this update. If yes, they must be removed from
+ * the changeset.
+ *
+ * See https://git.nic.cz/redmine/issues/937#note-14 and below.
+ */
+
+ // TODO: finish, disabled for now
+
+ dbg_ddns_detail(" * removing RR %p\n", *rrset_copy);
+
+ ret = knot_ddns_check_remove_rr(changeset, *rrset_copy);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(rrset_copy, 1, 1, 1);
+ return ret;
+ }
+
ret = knot_changeset_add_rr(&changeset->remove,
&changeset->remove_count,
&changeset->remove_allocated,
- rrset_copy);
+ *rrset_copy);
}
return ret;
@@ -229,7 +302,7 @@ static int knot_ddns_add_update(knot_changeset_t *changeset,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_exist(const knot_zone_contents_t *zone,
- const knot_rrset_t *rrset, uint8_t *rcode)
+ const knot_rrset_t *rrset, knot_rcode_t *rcode)
{
assert(zone != NULL);
assert(rrset != NULL);
@@ -261,7 +334,8 @@ static int knot_ddns_check_exist(const knot_zone_contents_t *zone,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone,
- const knot_rrset_t *rrset, uint8_t *rcode)
+ const knot_rrset_t *rrset,
+ knot_rcode_t *rcode)
{
assert(zone != NULL);
assert(rrset != NULL);
@@ -305,7 +379,8 @@ static int knot_ddns_check_exist_full(const knot_zone_contents_t *zone,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone,
- const knot_rrset_t *rrset, uint8_t *rcode)
+ const knot_rrset_t *rrset,
+ knot_rcode_t *rcode)
{
assert(zone != NULL);
assert(rrset != NULL);
@@ -322,23 +397,15 @@ static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone,
}
const knot_node_t *node;
- const knot_rrset_t *found;
node = knot_zone_contents_find_node(zone, knot_rrset_owner(rrset));
if (node == NULL) {
return KNOT_EOK;
- } else if ((found = knot_node_rrset(node, knot_rrset_type(rrset)))
- == NULL) {
+ } else if (knot_node_rrset(node, knot_rrset_type(rrset)) == NULL) {
return KNOT_EOK;
- } else {
- // do not have to compare the header, it is already done
- assert(knot_rrset_type(found) == knot_rrset_type(rrset));
- assert(knot_dname_compare(knot_rrset_owner(found),
- knot_rrset_owner(rrset)) == 0);
- if (knot_rrset_compare_rdata(found, rrset) <= 0) {
- return KNOT_EOK;
- }
}
+
+ /* RDATA is always empty for simple RRset checks. */
*rcode = KNOT_RCODE_YXRRSET;
return KNOT_EPREREQ;
@@ -347,7 +414,8 @@ static int knot_ddns_check_not_exist(const knot_zone_contents_t *zone,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_in_use(const knot_zone_contents_t *zone,
- const knot_dname_t *dname, uint8_t *rcode)
+ const knot_dname_t *dname,
+ knot_rcode_t *rcode)
{
assert(zone != NULL);
assert(dname != NULL);
@@ -376,7 +444,8 @@ static int knot_ddns_check_in_use(const knot_zone_contents_t *zone,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone,
- const knot_dname_t *dname, uint8_t *rcode)
+ const knot_dname_t *dname,
+ knot_rcode_t *rcode)
{
assert(zone != NULL);
assert(dname != NULL);
@@ -405,10 +474,13 @@ static int knot_ddns_check_not_in_use(const knot_zone_contents_t *zone,
/* API functions */
/*----------------------------------------------------------------------------*/
-int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query,
- uint8_t *rcode)
+int knot_ddns_check_zone(const knot_zone_contents_t *zone,
+ const knot_packet_t *query, knot_rcode_t *rcode)
{
if (zone == NULL || query == NULL || rcode == NULL) {
+ if (rcode != NULL) {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ }
return KNOT_EINVAL;
}
@@ -417,18 +489,8 @@ int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query,
return KNOT_EMALF;
}
- if(!knot_zone_contents(zone)) {
- *rcode = KNOT_RCODE_REFUSED;
- return KNOT_ENOZONE;
- }
-
- // 1) check if the zone is master or slave
- if (!knot_zone_is_master(zone)) {
- return KNOT_EBADZONE;
- }
-
- // 2) check zone CLASS
- if (knot_zone_contents_class(knot_zone_contents(zone)) !=
+ // check zone CLASS
+ if (knot_zone_contents_class(zone) !=
knot_packet_qclass(query)) {
*rcode = KNOT_RCODE_NOTAUTH;
return KNOT_ENOZONE;
@@ -439,8 +501,8 @@ int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query,
/*----------------------------------------------------------------------------*/
-int knot_ddns_process_prereqs(knot_packet_t *query,
- knot_ddns_prereq_t **prereqs, uint8_t *rcode)
+int knot_ddns_process_prereqs(const knot_packet_t *query,
+ knot_ddns_prereq_t **prereqs, knot_rcode_t *rcode)
{
/*! \todo Consider not parsing the whole packet at once, but
* parsing one RR at a time - could save some memory and time.
@@ -449,6 +511,8 @@ int knot_ddns_process_prereqs(knot_packet_t *query,
if (query == NULL || prereqs == NULL || rcode == NULL) {
return KNOT_EINVAL;
}
+
+ dbg_ddns("Processing prerequisities.\n");
// allocate space for the prerequisities
*prereqs = (knot_ddns_prereq_t *)calloc(1, sizeof(knot_ddns_prereq_t));
@@ -478,9 +542,11 @@ int knot_ddns_process_prereqs(knot_packet_t *query,
/*----------------------------------------------------------------------------*/
int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
- knot_ddns_prereq_t **prereqs, uint8_t *rcode)
+ knot_ddns_prereq_t **prereqs, knot_rcode_t *rcode)
{
int i, ret;
+
+ dbg_ddns("Checking 'exist' prerequisities.\n");
for (i = 0; i < (*prereqs)->exist_count; ++i) {
ret = knot_ddns_check_exist(zone, (*prereqs)->exist[i], rcode);
@@ -489,6 +555,7 @@ int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
}
}
+ dbg_ddns("Checking 'exist full' prerequisities.\n");
for (i = 0; i < (*prereqs)->exist_full_count; ++i) {
ret = knot_ddns_check_exist_full(zone,
(*prereqs)->exist_full[i],
@@ -498,6 +565,7 @@ int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
}
}
+ dbg_ddns("Checking 'not exist' prerequisities.\n");
for (i = 0; i < (*prereqs)->not_exist_count; ++i) {
ret = knot_ddns_check_not_exist(zone, (*prereqs)->not_exist[i],
rcode);
@@ -506,6 +574,7 @@ int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
}
}
+ dbg_ddns("Checking 'in use' prerequisities.\n");
for (i = 0; i < (*prereqs)->in_use_count; ++i) {
ret = knot_ddns_check_in_use(zone, (*prereqs)->in_use[i],
rcode);
@@ -514,6 +583,7 @@ int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
}
}
+ dbg_ddns("Checking 'not in use' prerequisities.\n");
for (i = 0; i < (*prereqs)->not_in_use_count; ++i) {
ret = knot_ddns_check_not_in_use(zone,
(*prereqs)->not_in_use[i],
@@ -529,10 +599,15 @@ int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
/*----------------------------------------------------------------------------*/
static int knot_ddns_check_update(const knot_rrset_t *rrset,
- const knot_packet_t *query, uint8_t *rcode)
+ const knot_packet_t *query,
+ knot_rcode_t *rcode)
{
- if (!knot_dname_is_subdomain(knot_rrset_owner(rrset),
- knot_packet_qname(query))) {
+ /* Accept both subdomain and dname match. */
+ dbg_ddns("Checking UPDATE packet.\n");
+ const knot_dname_t *owner = knot_rrset_owner(rrset);
+ const knot_dname_t *qname = knot_packet_qname(query);
+ int is_sub = knot_dname_is_subdomain(owner, qname);
+ if (!is_sub && knot_dname_compare(owner, qname) != 0) {
*rcode = KNOT_RCODE_NOTZONE;
return KNOT_EBADZONE;
}
@@ -565,8 +640,9 @@ static int knot_ddns_check_update(const knot_rrset_t *rrset,
/*----------------------------------------------------------------------------*/
-int knot_ddns_process_update(knot_packet_t *query,
- knot_changeset_t **changeset, uint8_t *rcode)
+int knot_ddns_process_update(const knot_zone_contents_t *zone,
+ const knot_packet_t *query,
+ knot_changeset_t *changeset, knot_rcode_t *rcode)
{
// just put all RRSets from query's Authority section
// it will be distinguished when applying to the zone
@@ -575,63 +651,1739 @@ int knot_ddns_process_update(knot_packet_t *query,
return KNOT_EINVAL;
}
- *changeset = (knot_changeset_t *)calloc(1, sizeof(knot_changeset_t));
- CHECK_ALLOC_LOG(*changeset, KNOT_ENOMEM);
+ int ret = KNOT_EOK;
+
+ /* Copy base SOA query. */
+ const knot_rrset_t *soa = knot_node_rrset(knot_zone_contents_apex(zone),
+ KNOT_RRTYPE_SOA);
+ knot_rrset_t *soa_begin = NULL;
+ knot_rrset_t *soa_end = NULL;
+ ret = knot_rrset_deep_copy(soa, &soa_begin, 0);
+ if (ret == KNOT_EOK) {
+ knot_changeset_store_soa(&changeset->soa_from,
+ &changeset->serial_from, soa_begin);
+ } else {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
- int ret;
+ /* Current SERIAL */
+ int64_t sn = knot_rdata_soa_serial(knot_rrset_rdata(soa_begin));
+ int64_t sn_new;
+ /* Incremented SERIAL
+ * We must set it now to be able to compare SERIAL from SOAs in the
+ * UPDATE to it. Although we do not have the new SOA yet.
+ */
+ if (sn > -1) {
+ sn_new = (uint32_t)sn + 1;
+ } else {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+
+ const knot_rrset_t *rrset = NULL;
+ knot_rrset_t *rrset_copy = NULL;
+ dbg_ddns("Processing UPDATE section.\n");
for (int i = 0; i < knot_packet_authority_rrset_count(query); ++i) {
- const knot_rrset_t *rrset =
- knot_packet_authority_rrset(query, i);
+ rrset = knot_packet_authority_rrset(query, i);
ret = knot_ddns_check_update(rrset, query, rcode);
if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to check update RRSet:%s\n",
+ knot_strerror(ret));
return ret;
}
- ret = knot_ddns_add_update(*changeset, rrset,
- knot_packet_qclass(query));
+ ret = knot_ddns_add_update(changeset, rrset,
+ knot_packet_qclass(query),
+ &rrset_copy);
if (ret != KNOT_EOK) {
dbg_ddns("Failed to add update RRSet:%s\n",
knot_strerror(ret));
*rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
: KNOT_RCODE_SERVFAIL;
- knot_free_changeset(changeset);
return ret;
}
+
+ /* Check if the added record is SOA. If yes, check the SERIAL.
+ * If this record should cause the SOA to be replaced in the
+ * zone, use it as the ending SOA.
+ *
+ * Also handle cases where there are multiple SOAs to be added
+ * in the same UPDATE. The one with the largest SERIAL should
+ * be used.
+ *
+ * TODO: If there are more SOAs in the UPDATE one after another,
+ * the ddns_add_update() function will merge them into a
+ * RRSet. This should be handled somehow.
+ */
+ if (knot_rrset_type(rrset) == KNOT_RRTYPE_SOA
+ && ns_serial_compare(knot_rdata_soa_serial(
+ knot_rrset_rdata(rrset)),
+ sn_new) > 0) {
+ sn_new = knot_rdata_soa_serial(knot_rrset_rdata(rrset));
+ soa_end = (knot_rrset_t *)rrset_copy;
+ }
}
- return KNOT_EOK;
+ /* Ending SOA */
+ if (soa_end == NULL) {
+ /* If not set */
+ assert(sn_new == (uint32_t)sn + 1);
+ ret = knot_rrset_deep_copy(soa, &soa_end, 1);
+ knot_rdata_t *rd = knot_rrset_get_rdata(soa_end);
+ knot_rdata_soa_serial_set(rd, sn_new);
+ }
+
+ knot_changeset_store_soa(&changeset->soa_to,
+ &changeset->serial_to,
+ soa_end);
+
+ return ret;
}
/*----------------------------------------------------------------------------*/
void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq)
{
+ dbg_ddns("Freeing prerequisities.\n");
+
int i;
for (i = 0; i < (*prereq)->exist_count; ++i) {
knot_rrset_deep_free(&(*prereq)->exist[i], 1, 1, 1);
}
+ free((*prereq)->exist);
for (i = 0; i < (*prereq)->exist_full_count; ++i) {
knot_rrset_deep_free(&(*prereq)->exist_full[i], 1, 1, 1);
}
+ free((*prereq)->exist_full);
for (i = 0; i < (*prereq)->not_exist_count; ++i) {
knot_rrset_deep_free(&(*prereq)->not_exist[i], 1, 1, 1);
}
+ free((*prereq)->not_exist);
for (i = 0; i < (*prereq)->in_use_count; ++i) {
knot_dname_free(&(*prereq)->in_use[i]);
}
+ free((*prereq)->in_use);
for (i = 0; i < (*prereq)->not_in_use_count; ++i) {
knot_dname_free(&(*prereq)->not_in_use[i]);
}
+ free((*prereq)->not_in_use);
free(*prereq);
*prereq = NULL;
}
+
+/*----------------------------------------------------------------------------*/
+/* New DDNS processing - */
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_check_remove_rr2(knot_changeset_t *changeset,
+ const knot_dname_t *owner,
+ uint16_t type, const knot_rdata_t *rdata,
+ knot_rrset_t ***removed,
+ size_t *removed_count)
+{
+ assert(changeset != NULL);
+ assert(removed != NULL);
+ assert(removed_count != NULL);
+
+ *removed_count = 0;
+ *removed = (knot_rrset_t **)malloc(changeset->add_count
+ * sizeof(knot_rrset_t *));
+ if (*removed == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ knot_rrset_t *remove = NULL;
+
+ /*
+ * We assume that each RR in the ADD section of the changeset is in its
+ * own RRSet. It should be as this is how they are stored there by the
+ * ddns_process_add() function.
+ */
+
+ dbg_ddns_verb("Removing possible redundant RRs from changeset.\n");
+ for (int i = 0; i < changeset->add_count; ++i) {
+ // Removing RR(s) from this owner
+ if (knot_dname_compare(knot_rrset_owner(changeset->add[i]),
+ owner) == 0) {
+ // Removing one or all RRSets
+ if (rdata == NULL
+ && (type == knot_rrset_type(changeset->add[i])
+ || type == KNOT_RRTYPE_ANY)) {
+ dbg_ddns_detail("Removing one or all RRSets\n");
+ remove = knot_changeset_remove_rr(
+ changeset->add,
+ &changeset->add_count, i);
+ } else if (type == knot_rrset_type(changeset->add[i])) {
+ // Removing specific RR
+ assert(rdata != NULL);
+
+ knot_rrtype_descriptor_t *desc =
+ knot_rrtype_descriptor_by_type(type);
+
+ // We must check if the RDATA match
+ if (knot_rdata_compare(rdata,
+ knot_rrset_rdata(changeset->add[i]),
+ desc->wireformat)) {
+ remove = knot_changeset_remove_rr(
+ changeset->add,
+ &changeset->add_count, i);
+ }
+ }
+
+ dbg_ddns_detail("Removed RRSet from chgset:\n");
+ knot_rrset_dump(remove, 0);
+ (*removed)[(*removed_count)++] = remove;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static void knot_ddns_check_add_rr(knot_changeset_t *changeset,
+ const knot_rrset_t *rr,
+ knot_rrset_t **removed)
+{
+ assert(changeset != NULL);
+ assert(rr != NULL);
+ assert(removed != NULL);
+
+ *removed = NULL;
+
+ dbg_ddns_verb("Removing possible redundant RRs from changeset.\n");
+ for (int i = 0; i < changeset->remove_count; ++i) {
+ /* Just check exact match, the changeset contains only
+ * whole RRs that have been removed.
+ */
+ if (knot_rrset_match(rr, changeset->remove[i],
+ KNOT_RRSET_COMPARE_WHOLE) == 1) {
+ *removed = knot_changeset_remove_rr(
+ changeset->remove,
+ &changeset->remove_count, i);
+ dbg_ddns_detail("Removed RRSet from chgset:\n");
+ knot_rrset_dump(*removed, 0);
+ break;
+ }
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_rr_is_nsec3(const knot_rrset_t *rr)
+{
+ assert(rr != NULL);
+
+ if ((knot_rrset_type(rr) == KNOT_RRTYPE_NSEC3)
+ || (knot_rrset_type(rr) == KNOT_RRTYPE_RRSIG
+ && knot_rrset_rdata(rr)
+ && knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr))
+ == KNOT_RRTYPE_NSEC3))
+ {
+ dbg_ddns_detail("This is NSEC3-related RRSet.\n");
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+/*! \note Copied from xfrin_add_new_node(). */
+static knot_node_t *knot_ddns_add_new_node(knot_zone_contents_t *zone,
+ knot_dname_t *owner, int is_nsec3)
+{
+ assert(zone != NULL);
+ assert(owner != NULL);
+
+ knot_node_t *node = knot_node_new(owner, NULL, 0);
+ if (node == NULL) {
+ dbg_xfrin("Failed to create a new node.\n");
+ return NULL;
+ }
+
+ int ret = 0;
+
+ // insert the node into zone structures and create parents if
+ // necessary
+ if (is_nsec3) {
+ ret = knot_zone_contents_add_nsec3_node(zone, node, 1, 0, 1);
+ } else {
+ ret = knot_zone_contents_add_node(zone, node, 1, 0, 1);
+ }
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new node to zone contents.\n");
+ knot_node_free(&node);
+ return NULL;
+ }
+
+ /*!
+ * \note It is not needed to set the previous node, we will do this
+ * in adjusting after the transfer.
+ */
+ assert(zone->zone != NULL);
+ //knot_node_set_zone(node, zone->zone);
+ assert(node->zone == zone->zone);
+
+ return node;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static knot_node_t *knot_ddns_get_node(knot_zone_contents_t *zone,
+ const knot_rrset_t *rr)
+{
+ assert(zone != NULL);
+ assert(rr != NULL);
+
+ knot_node_t *node = NULL;
+ knot_dname_t *owner = knot_rrset_get_owner(rr);
+
+ dbg_ddns_detail("Searching for node...\n");
+ if (knot_ddns_rr_is_nsec3(rr)) {
+ node = knot_zone_contents_get_nsec3_node(zone, owner);
+ } else {
+ node = knot_zone_contents_get_node(zone, owner);
+ }
+
+ return node;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_add_cname(knot_node_t *node,
+ const knot_rrset_t *rr,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes)
+{
+ assert(node != NULL);
+ assert(rr != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+
+ dbg_ddns_detail("Adding CNAME RR.\n");
+
+ int ret = 0;
+
+ /* Get the current CNAME RR from the node. */
+ knot_rrset_t *removed = knot_node_get_rrset(node, KNOT_RRTYPE_CNAME);
+
+ if (removed != NULL) {
+ /* If they are identical, ignore. */
+ if (knot_rrset_match(removed, rr, KNOT_RRSET_COMPARE_WHOLE)
+ == 1) {
+ dbg_ddns_verb("CNAME identical to one in the node.\n");
+ return 1;
+ }
+
+ /*! \note
+ * Together with the removed CNAME we remove also its RRSIGs as
+ * they would not be valid for the new CNAME anyway.
+ *
+ * \todo Document!!
+ */
+
+ /* b) Store it to 'changes', together with its RRSIGs. */
+ ret = knot_changes_add_old_rrsets(&removed, 1, changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add removed RRSet to "
+ "'changes': %s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ /* c) And remove it from the node. */
+ (void)knot_node_remove_rrset(node, KNOT_RRTYPE_CNAME);
+
+ /* d) Check if this CNAME was not previously added by
+ * the UPDATE. If yes, remove it from the ADD
+ * section and do not add it to the REMOVE section.
+ */
+ knot_rrset_t **from_chgset = NULL;
+ size_t from_chgset_count = 0;
+ ret = knot_ddns_check_remove_rr2(
+ changeset, knot_rrset_owner(removed),
+ KNOT_RRTYPE_CNAME, knot_rrset_rdata(removed),
+ &from_chgset, &from_chgset_count);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to remove possible redundant "
+ "RRs from ADD section: %s.\n",
+ knot_strerror(ret));
+ free(from_chgset);
+ return ret;
+ }
+
+ assert(from_chgset_count <= 1);
+
+ if (from_chgset_count == 1) {
+ /* Just delete the RRSet. */
+ knot_rrset_deep_free(&(from_chgset[0]), 1, 1, 1);
+ /* Okay, &(from_chgset[0]) is basically equal to just
+ * from_chgset, but it's more clear this way that we are
+ * deleting the first RRSet in the array ;-)
+ */
+ } else {
+ /* Otherwise copy the removed CNAME and add it
+ * to the REMOVE section.
+ */
+ knot_rrset_t *removed_copy;
+ ret = knot_rrset_deep_copy(removed,
+ &removed_copy, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy removed RRSet:"
+ " %s\n", knot_strerror(ret));
+ free(from_chgset);
+ return ret;
+ }
+
+ ret = knot_changeset_add_rrset(
+ &changeset->remove,
+ &changeset->remove_count,
+ &changeset->remove_allocated,
+ removed_copy);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(&removed_copy,
+ 1, 1, 1);
+ dbg_ddns("Failed to add removed CNAME "
+ "to changeset: %s\n",
+ knot_strerror(ret));
+ free(from_chgset);
+ return ret;
+ }
+ }
+ free(from_chgset);
+ } else if (knot_node_rrset_count(node) != 0) {
+ /* 2) Other occupied node => ignore. */
+ return 1;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_add_soa(knot_node_t *node,
+ const knot_rrset_t *rr,
+ knot_changes_t *changes)
+{
+ assert(node != NULL);
+ assert(rr != NULL);
+ assert(changes != NULL);
+
+ dbg_ddns_detail("Adding SOA RR.\n");
+
+ int ret = 0;
+
+ /*
+ * Just remove the SOA from the node, together with its RRSIGs.
+ * Adding the RR is done in the caller function. Note that only SOA
+ * with larger SERIAL than the current one will get to these functions,
+ * so we don't have to check the SERIALS again. But an assert won't
+ * hurt.
+ */
+
+ /* Get the current SOA RR from the node. */
+ knot_rrset_t *removed = knot_node_get_rrset(node, KNOT_RRTYPE_SOA);
+
+ if (removed != NULL) {
+ dbg_ddns_detail("Found SOA in the node.\n");
+ /* If they are identical, ignore. */
+ if (knot_rrset_match(removed, rr, KNOT_RRSET_COMPARE_WHOLE)
+ == 1) {
+ dbg_ddns_detail("Old and new SOA identical.\n");
+ return 1;
+ }
+
+ /* Check that the serial is indeed larger than the current one*/
+ assert(ns_serial_compare(knot_rdata_soa_serial(
+ knot_rrset_rdata(removed)),
+ knot_rdata_soa_serial(
+ knot_rrset_rdata(rr))) < 0);
+
+ /* 1) Store it to 'changes', together with its RRSIGs. */
+ ret = knot_changes_add_old_rrsets(
+ &removed, 1, changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add removed RRSet to "
+ "'changes': %s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ /* 2) And remove it from the node. */
+ (void)knot_node_remove_rrset(node, KNOT_RRTYPE_SOA);
+
+ /* No changeset processing needed in this case. */
+ } else {
+ dbg_ddns_detail("No SOA in node, ignoring.\n");
+ /* If there is no SOA in the node, ignore. */
+ return 1;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr_new_normal(knot_node_t *node, knot_rrset_t *rr_copy,
+ knot_changes_t *changes)
+{
+ assert(node != NULL);
+ assert(rr_copy != NULL);
+ assert(changes != NULL);
+
+ dbg_ddns_verb("Adding normal RR.\n");
+
+ /* Add the RRSet to 'changes'. */
+ int ret = knot_changes_add_new_rrsets(&rr_copy, 1, changes, 1);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(&rr_copy, 1, 1, 1);
+ dbg_ddns("Failed to store copy of the added RR: "
+ "%s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ /* Add the RRSet to the node. */
+ ret = knot_node_add_rrset(node, rr_copy, 0);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add RR to node: %s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr_new_rrsig(knot_node_t *node, knot_rrset_t *rr_copy,
+ knot_changes_t *changes,
+ uint16_t type_covered)
+{
+ assert(node != NULL);
+ assert(rr_copy != NULL);
+ assert(changes != NULL);
+
+ dbg_ddns_verb("Adding RRSIG RR.\n");
+
+ /* Create RRSet to be covered by the RRSIG. */
+ knot_rrset_t *covered_rrset = knot_rrset_new(
+ knot_rrset_get_owner(rr_copy), type_covered,
+ knot_rrset_class(rr_copy),
+ knot_rrset_ttl(rr_copy));
+ if (covered_rrset == NULL) {
+ dbg_ddns("Failed to create RRSet to be covered"
+ " by the UPDATE RRSIG RR.\n");
+ knot_rrset_deep_free(&rr_copy, 1, 1, 1);
+ return KNOT_ENOMEM;
+ }
+
+ /* Add the RRSet to the node. */
+ int ret = knot_node_add_rrset(node, covered_rrset, 0);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add the RRSet to be covered to the node: %s"
+ ".\n", knot_strerror(ret));
+ knot_rrset_deep_free(&rr_copy, 1, 1, 1);
+ knot_rrset_deep_free(&covered_rrset, 1, 1, 1);
+ return KNOT_ENOMEM;
+ }
+
+ /* Add the RRSet to 'changes'. */
+ ret = knot_changes_add_new_rrsets(&covered_rrset, 1, changes, 0);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new RRSet (covered) to list: %s.\n",
+ knot_strerror(ret));
+ knot_rrset_deep_free(&rr_copy, 1, 1, 1);
+ knot_rrset_deep_free(&covered_rrset, 1, 1, 1);
+ return ret;
+ }
+
+ /* Add the RRSIG RRSet to 'changes'. */
+ ret = knot_changes_add_new_rrsets(&rr_copy, 1, changes, 1);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(&rr_copy, 1, 1, 1);
+ dbg_ddns("Failed to store copy of the added RRSIG: %s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /* Add the RRSIG RRSet to the covered RRSet. */
+ ret = knot_rrset_add_rrsigs(covered_rrset, rr_copy,
+ KNOT_RRSET_DUPL_SKIP);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add RRSIG RR to the covered RRSet.\n");
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr_merge_normal(knot_rrset_t *node_rrset_copy,
+ knot_rrset_t **rr_copy)
+{
+ assert(node_rrset_copy != NULL);
+ assert(rr_copy != NULL);
+ assert(*rr_copy != NULL);
+
+ dbg_ddns_verb("Merging normal RR to existing RRSet.\n");
+
+ /* In case the RRSet is empty (and only remained there because
+ * of the RRSIGs) it may happen that the TTL may be different
+ * than that of he new RRs. Update the TTL according to the
+ * first RR.
+ */
+ if (knot_rrset_rdata(node_rrset_copy) == NULL
+ && knot_rrset_ttl(node_rrset_copy)
+ != knot_rrset_ttl(*rr_copy)) {
+ knot_rrset_set_ttl(node_rrset_copy,
+ knot_rrset_ttl(*rr_copy));
+ }
+
+ int rdata_in_copy = knot_rrset_rdata_rr_count(*rr_copy);
+ int ret = knot_rrset_merge_no_dupl((void **)&node_rrset_copy,
+ (void **)rr_copy);
+ dbg_ddns_detail("Merge returned: %d\n", ret);
+
+ if (ret < 0) {
+ dbg_ddns("Failed to merge UPDATE RR to node RRSet: %s."
+ "\n", knot_strerror(ret));
+ return ret;
+ }
+
+ knot_rrset_free(rr_copy);
+
+ if (rdata_in_copy == ret) {
+ /* All RDATA have been removed, because they were duplicates
+ * or there were none (0). In general this means, that no
+ * change was made.
+ */
+ return 1;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr_merge_rrsig(knot_rrset_t *node_rrset_copy,
+ knot_rrset_t **rr_copy,
+ knot_changes_t *changes)
+{
+ assert(node_rrset_copy != NULL);
+ assert(rr_copy != NULL);
+ assert(*rr_copy != NULL);
+ assert(changes != NULL);
+
+ dbg_ddns_verb("Adding RRSIG RR to existing RRSet.\n");
+
+ knot_rrset_t *rrsigs_old = knot_rrset_get_rrsigs(node_rrset_copy);
+ int ret = 0;
+
+ if (rrsigs_old != NULL) {
+ /* If there is an RRSIG RRSet already, copy it too. */
+ knot_rrset_t *rrsigs_copy = NULL;
+ ret = xfrin_copy_old_rrset(rrsigs_old, &rrsigs_copy,
+ changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy RRSIG RRSet: "
+ "%s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ /* Replace the RRSIGs by the copy. */
+ ret = knot_rrset_set_rrsigs(node_rrset_copy, rrsigs_copy);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to replace RRSIGs in "
+ "the RRSet: %s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /* Merge the UPDATE RR to the copied RRSIG
+ * RRSet.
+ */
+ dbg_ddns_detail("Merging RRSIG to the one in the RRSet.\n");
+
+ int rdata_in_copy = knot_rrset_rdata_rr_count(*rr_copy);
+ ret = knot_rrset_merge_no_dupl(
+ (void **)&rrsigs_copy, (void **)rr_copy);
+ if (ret < 0) {
+ dbg_xfrin("Failed to merge UPDATE RRSIG to copy: %s.\n",
+ knot_strerror(ret));
+ return KNOT_ERROR;
+ }
+
+ knot_rrset_free(rr_copy);
+
+ if (rdata_in_copy == ret) {
+ /* All RDATA have been removed, because they were
+ * duplicates or there were none (0). In general this
+ * means, that no change was made.
+ */
+ return 1;
+ }
+ } else {
+ /* If there is no RRSIG RRSet yet, just add the
+ * UPDATE RR to the copied covered RRSet.
+ */
+ /* Add the RRSet to 'changes'. */
+ ret = knot_changes_add_new_rrsets(rr_copy, 1, changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to store copy of the added RR: %s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /* Add the RRSet to the covered RRSet. */
+ ret = knot_rrset_add_rrsigs(node_rrset_copy, *rr_copy,
+ KNOT_RRSET_DUPL_SKIP);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add RRSIG RR to the"
+ " covered RRSet.\n");
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+/*!
+ * \todo We should check, how it's possible that IXFR is not leaking due to the
+ * same issue with merge. Or maybe it is, we should try it!!
+ */
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr(knot_node_t *node, const knot_rrset_t *rr,
+ knot_changes_t *changes, knot_rrset_t **rr_copy)
+{
+ assert(node != NULL);
+ assert(rr != NULL);
+ assert(changes != NULL);
+ assert(rr_copy != NULL);
+
+ /* Copy the RRSet from the packet. */
+ //knot_rrset_t *rr_copy;
+ int ret = knot_rrset_deep_copy(rr, rr_copy, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy RR: %s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ uint16_t type = knot_rrset_type(rr);
+ uint16_t type_covered = (type == KNOT_RRTYPE_RRSIG)
+ ? knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr))
+ : type;
+
+ /* If the RR belongs to a RRSet already present in the node, we must
+ * take this RRSet from the node, copy it, and merge this RR into it.
+ *
+ * This code is more or less copied from xfr-in.c.
+ */
+ knot_rrset_t *node_rrset_copy = NULL;
+ ret = xfrin_copy_rrset(node, type_covered, &node_rrset_copy, changes,
+ 0);
+
+ if (node_rrset_copy == NULL) {
+ /* No such RRSet in the node. Add the whole UPDATE RRSet. */
+ dbg_ddns_detail("Adding whole UPDATE RR to the zone.\n");
+ if (type_covered != type) {
+ /* Adding RRSIG. */
+ ret = knot_ddns_add_rr_new_rrsig(node, *rr_copy,
+ changes, type_covered);
+ } else {
+ ret = knot_ddns_add_rr_new_normal(node, *rr_copy,
+ changes);
+ }
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add new RR to node.\n");
+ return ret;
+ }
+ } else {
+ /* We have copied the RRSet from the node. */
+dbg_ddns_exec_detail(
+ dbg_ddns_detail("Merging RR to an existing RRSet.\n");
+ knot_rrset_dump(node_rrset_copy, 1);
+ dbg_ddns_detail("New RR:\n");
+ knot_rrset_dump(*rr_copy, 0);
+);
+
+ if (type_covered != type) {
+ /* Adding RRSIG. */
+ ret = knot_ddns_add_rr_merge_rrsig(node_rrset_copy,
+ rr_copy, changes);
+ } else {
+ ret = knot_ddns_add_rr_merge_normal(node_rrset_copy,
+ rr_copy);
+ }
+
+dbg_ddns_exec_detail(
+ dbg_ddns_detail("After merge:\n");
+ knot_rrset_dump(node_rrset_copy, 1);
+);
+
+ if (ret < KNOT_EOK) {
+ dbg_ddns("Failed to merge UPDATE RR to node RRSet.\n");
+ knot_rrset_deep_free(rr_copy, 1, 1, 1);
+ knot_rrset_deep_free(&node_rrset_copy, 1, 1, 1);
+ return ret;
+ }
+
+ // save the new RRSet together with the new RDATA to 'changes'
+ // do not overwrite 'ret', it have to be returned
+ int r = knot_changes_add_new_rrsets(&node_rrset_copy, 1,
+ changes, 1);
+ if (r != KNOT_EOK) {
+ dbg_ddns("Failed to store RRSet copy to 'changes'\n");
+ knot_rrset_deep_free(&node_rrset_copy, 1, 1, 1);
+ return r;
+ }
+ }
+
+ assert(ret >= 0);
+ return ret;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_final_soa_to_chgset(const knot_rrset_t *soa,
+ knot_changeset_t *changeset)
+{
+ assert(soa != NULL);
+ assert(changeset != NULL);
+
+ knot_rrset_t *soa_copy = NULL;
+ int ret = knot_rrset_deep_copy(soa, &soa_copy, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy SOA RR to the changeset: "
+ "%s\n", knot_strerror(ret));
+ return ret;
+ }
+
+ knot_changeset_store_soa(&changeset->soa_to,
+ &changeset->serial_to,
+ soa_copy);
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_add_rr_to_chgset(const knot_rrset_t *rr,
+ knot_changeset_t *changeset)
+{
+ assert(rr != NULL);
+ assert(changeset != NULL);
+
+ int ret = 0;
+ knot_rrset_t *chgset_rr = NULL;
+ knot_ddns_check_add_rr(changeset, rr, &chgset_rr);
+ if (chgset_rr == NULL) {
+ ret = knot_rrset_deep_copy(rr, &chgset_rr, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy RR to the changeset: "
+ "%s\n", knot_strerror(ret));
+ return ret;
+ }
+ /* No such RR in the changeset, add it. */
+ ret = knot_changeset_add_rrset(&changeset->add,
+ &changeset->add_count,
+ &changeset->add_allocated,
+ chgset_rr);
+ if (ret != KNOT_EOK) {
+ knot_rrset_deep_free(&chgset_rr, 1, 1, 1);
+ dbg_ddns("Failed to add RR to changeset: %s.\n",
+ knot_strerror(ret));
+ return ret;
+ }
+ } else {
+ knot_rrset_deep_free(&chgset_rr, 1, 1, 1);
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_add(const knot_rrset_t *rr,
+ knot_node_t *node,
+ knot_zone_contents_t *zone,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes,
+ knot_rrset_t **rr_copy)
+{
+ assert(rr != NULL);
+ assert(zone != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+ assert(rr_copy != NULL);
+
+ dbg_ddns_verb("Adding RR.\n");
+
+ if (node == NULL) {
+ // create new node, connect it properly to the
+ // zone nodes
+ dbg_ddns_detail("Node not found. Creating new.\n");
+ node = knot_ddns_add_new_node(zone, knot_rrset_get_owner(rr),
+ knot_ddns_rr_is_nsec3(rr));
+ if (node == NULL) {
+ dbg_xfrin("Failed to create new node in zone.\n");
+ return KNOT_ERROR;
+ }
+ }
+
+ uint16_t type = knot_rrset_type(rr);
+ *rr_copy = NULL;
+ int ret = 0;
+
+ /*
+ * First, rule out special cases: CNAME, SOA and adding to CNAME node.
+ */
+ if (type == KNOT_RRTYPE_CNAME) {
+ /* 1) CNAME */
+ ret = knot_ddns_process_add_cname(node, rr, changeset, changes);
+ } else if (type == KNOT_RRTYPE_SOA) {
+ /* 2) SOA */
+ ret = knot_ddns_process_add_soa(node, rr, changes);
+ } else if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) {
+ /*
+ * Adding RR to CNAME node. Ignore the UPDATE RR.
+ *
+ * TODO: This may or may not be according to the RFC, it's quite
+ * unclear (see 3.4.2.2)
+ */
+ return KNOT_EOK;
+ }
+
+ if (ret == 1) {
+ dbg_ddns_detail("Ignoring the added RR.\n");
+ // Ignore
+ return KNOT_EOK;
+ } else if (ret != KNOT_EOK) {
+ dbg_ddns_detail("Adding RR failed.\n");
+ return ret;
+ }
+
+ /*
+ * In all other cases, the RR should just be added to the node.
+ */
+
+ /* Add the RRSet to the node (RRSIGs handled in the function). */
+ dbg_ddns_detail("Adding RR to the node.\n");
+ ret = knot_ddns_add_rr(node, rr, changes, rr_copy);
+ if (ret < 0) {
+ dbg_ddns("Failed to add RR to the node.\n");
+ return ret;
+ }
+
+ /*
+ * If adding SOA, it should not be stored in the changeset.
+ * (This is done in the calling function, and the SOA is stored in the
+ * soa_final field.)
+ */
+ if (type == KNOT_RRTYPE_SOA) {
+ return KNOT_EOK;
+ }
+
+ /* Add the RR to ADD section of the changeset. */
+ /* If the RR was previously removed, do not add it to the
+ * changeset, and remove the entry from the REMOVE section.
+ *
+ * If there was no change (i.e. all RDATA were duplicates), do not add
+ * the RR to the changeset.
+ */
+ if (ret == KNOT_EOK) {
+ dbg_ddns_detail("Adding RR to the changeset.\n");
+ ret = knot_ddns_add_rr_to_chgset(rr, changeset);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add the UPDATE RR to the changeset."
+ "\n");
+ return ret;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+/*!
+ * \todo Geez, this is soooooo long even I don't exactly know what it does...
+ * Refactor!
+ */
+static int knot_ddns_process_rem_rr(const knot_rrset_t *rr,
+ knot_node_t *node,
+ knot_zone_contents_t *zone,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes, uint16_t qclass)
+{
+ assert(rr != NULL);
+ assert(node != NULL);
+ assert(zone != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+
+ uint16_t type = knot_rrset_type(rr);
+ dbg_ddns_verb("Removing one RR.\n");
+
+ /*
+ * When doing changes to RRSets, we must:
+ * 1) Copy the RRSet (same as in IXFR changeset applying, maybe the
+ * function xfrin_copy_rrset() may be used for this).
+ * 2) Remove the RDATA (in this case only one). Check if it is not the
+ * last NS RR in the zone.
+ * 3) Store the removed RDATA in 'changes'.
+ * 4) If the RRSet is empty, remove it and store in 'changes'.
+ * 5) Check redundant RRs in changeset.
+ * 6) Store the RRSet containing the one RDATA in the changeset. We may
+ * use the RRSet from the packet for this - copy it, set CLASS
+ * and TTL.
+ *
+ * Special handling of RRSIGs is required in that the RRSet containing
+ * them must be copied as well. However, copying of RRSet copies also
+ * the RRSIGs, so copying the base RRSet is enough for both cases!
+ */
+
+ assert(type != KNOT_RRTYPE_SOA);
+ int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
+
+ /* If removing NS from an apex and there is only one NS left, ignore
+ * this removal right away. We do not have to check if the RRs match:
+ * - if they don't match, the removal will be ignored
+ * - if they match, the last NS cannot be removed anyway.
+ */
+ if (is_apex && type == KNOT_RRTYPE_NS
+ && knot_rrset_rdata_rr_count(knot_node_rrset(node, type)) == 1) {
+ return KNOT_EOK;
+ }
+
+ /*
+ * 1) Copy the RRSet.
+ */
+ uint16_t type_to_copy = (type != KNOT_RRTYPE_RRSIG) ? type
+ : knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr));
+ knot_rrset_t *rrset_copy = NULL;
+ int ret = xfrin_copy_rrset(node, type_to_copy, &rrset_copy, changes, 1);
+ if (ret < 0) {
+ dbg_ddns("Failed to copy RRSet for removal: %s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ if (rrset_copy == NULL) {
+ dbg_ddns_verb("RRSet not found.\n");
+ return KNOT_EOK;
+ }
+
+ /*
+ * Set some variables needed, according to the modified RR type.
+ */
+
+ int rdata_count;
+ knot_rrset_t *to_modify;
+ if (type == KNOT_RRTYPE_RRSIG) {
+ rdata_count = knot_rrset_rdata_rr_count(
+ knot_rrset_rrsigs(rrset_copy));
+ to_modify = knot_rrset_get_rrsigs(rrset_copy);
+ } else {
+ rdata_count = knot_rrset_rdata_rr_count(rrset_copy);
+ to_modify = rrset_copy;
+ }
+
+ /*
+ * 1.5) Prepare place for the removed RDATA.
+ * We don't know if there are some, but if this fails, at least we
+ * haven't removed them yet.
+ */
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
+ &changes->old_rdata_types,
+ changes->old_rdata_count,
+ &changes->old_rdata_allocated,
+ rdata_count);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to reserve place for RDATA.\n");
+ return ret;
+ }
+
+ /*
+ * 2) Remove the proper RDATA from the RRSet copy, or its RRSIGs.
+ */
+ knot_rdata_t *removed = knot_rrset_remove_rdata(to_modify,
+ knot_rrset_rdata(rr));
+
+ /* No such RR in the RRSet. */
+ if (removed == NULL) {
+ dbg_ddns_detail("No such RR found to be removed.\n");
+ return KNOT_EOK;
+ }
+
+ /* If we removed NS from apex, there should be at least one more. */
+ assert(!is_apex || type != KNOT_RRTYPE_NS
+ || knot_rrset_rdata(rrset_copy) != NULL);
+
+ /*
+ * 3) Store the removed RDATA in 'changes'.
+ */
+ knot_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
+ &changes->old_rdata_count, removed, type);
+
+ /*
+ * 4) If the RRSet is empty, remove it and store in 'changes'.
+ * Do this also if the RRSIGs are empty.
+ * And if both are empty, remove both.
+ */
+ if (type == KNOT_RRTYPE_RRSIG
+ && knot_rrset_rdata(to_modify) == NULL) {
+ /* Empty RRSIGs, remove the RRSIG RRSet */
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
+ &changes->old_rrsets_count,
+ &changes->old_rrsets_allocated,
+ 1);
+ if (ret == KNOT_EOK) {
+ knot_rrset_t *rrsig = knot_rrset_get_rrsigs(rrset_copy);
+ dbg_xfrin_detail("Removed RRSIG RRSet (%p).\n", rrsig);
+
+ assert(rrsig == to_modify);
+
+ // add the removed RRSet to list of old RRSets
+ changes->old_rrsets[changes->old_rrsets_count++]
+ = rrsig;
+
+ // remove it from the RRSet
+ knot_rrset_set_rrsigs(rrset_copy, NULL);
+ } else {
+ dbg_ddns("Failed to reserve space for empty RRSet.\n");
+ }
+ }
+
+ /*! \note Copied from xfr-in.c - maybe extract to some function. */
+ /*! \note This is not needed as rrset is already on the old_rrsets */
+// if (knot_rrset_rdata(rrset_copy) == NULL
+// && knot_rrset_rrsigs(rrset_copy) == NULL) {
+// // The RRSet should not be empty if we were removing NSs from
+// // apex in case of DDNS
+// assert(!is_apex);
+
+// ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
+// &changes->old_rrsets_count,
+// &changes->old_rrsets_allocated,
+// 1);
+// if (ret == KNOT_EOK) {
+// knot_rrset_t *tmp = knot_node_remove_rrset(node, type);
+// dbg_xfrin_detail("Removed whole RRSet (%p).\n", tmp);
+
+// assert(tmp == rrset_copy);
+
+// // add the removed RRSet to list of old RRSets
+// changes->old_rrsets[changes->old_rrsets_count++]
+// = rrset_copy;
+// } else {
+// dbg_ddns("Failed to reserve space for empty RRSet.\n");
+// }
+// }
+
+ /*
+ * 5) Check if the RR is not in the ADD section. If yes, remove it
+ * from there and do not add it to the REMOVE section.
+ */
+ knot_rrset_t **from_chgset = NULL;
+ size_t from_chgset_count = 0;
+ ret = knot_ddns_check_remove_rr2(changeset, knot_node_owner(node),
+ type, knot_rrset_rdata(rr),
+ &from_chgset, &from_chgset_count);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to remove possible redundant RRs from ADD "
+ "section: %s.\n", knot_strerror(ret));
+ free(from_chgset);
+ return ret;
+ }
+
+ assert(from_chgset_count <= 1);
+
+ if (from_chgset_count == 1) {
+ /* Just delete the RRSet. */
+ knot_rrset_deep_free(&(from_chgset[0]), 1, 1, 1);
+
+ /* Finish processing, no adding to changeset. */
+ free(from_chgset);
+ return KNOT_EOK;
+ }
+
+ free(from_chgset);
+
+ /*
+ * 6) Store the RRSet containing the one RDATA in the changeset. We may
+ * use the RRSet from the packet for this - copy it, set CLASS
+ * and TTL.
+ */
+ knot_rrset_t *to_chgset = NULL;
+ ret = knot_rrset_deep_copy(rr, &to_chgset, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy RRSet from packet to changeset.\n");
+ return ret;
+ }
+ knot_rrset_set_class(to_chgset, qclass);
+ knot_rrset_set_ttl(to_chgset, knot_rrset_ttl(to_modify));
+
+ ret = knot_changeset_add_rrset(&changeset->remove,
+ &changeset->remove_count,
+ &changeset->remove_allocated,
+ to_chgset);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to store the RRSet copy to changeset: %s.\n",
+ knot_strerror(ret));
+ knot_rrset_deep_free(&to_chgset, 1, 1, 1);
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_rem_rrsig(knot_node_t *node,
+ knot_rrset_t *rrset,
+ knot_changes_t *changes,
+ knot_rrset_t **rrsig)
+{
+ assert(node != NULL);
+ assert(rrset != NULL);
+ assert(changes != NULL);
+
+ knot_rrset_t *rrset_copy = NULL;
+
+ /* Copy RRSet. */
+ int ret = xfrin_copy_old_rrset(rrset, &rrset_copy, changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy RRSet from node: %s.\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /* Remove RRSIGs from the copy. */
+ *rrsig = knot_rrset_get_rrsigs(rrset_copy);
+ if (*rrsig != NULL) {
+ knot_rrset_set_rrsigs(rrset_copy, NULL);
+ }
+
+ /* Put the copy to the node. */
+ ret = knot_node_add_rrset(node, rrset_copy, 0);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add RRSet copy to the node: %s\n",
+ knot_strerror(ret));
+ knot_rrset_deep_free(&rrset_copy, 1, 1, 1);
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_rem_rrsigs(knot_node_t *node,
+ knot_changes_t *changes,
+ knot_rrset_t ***removed,
+ size_t *removed_count)
+{
+ assert(node != NULL);
+ assert(removed != NULL);
+ assert(removed_count != NULL);
+ assert(changes != NULL);
+
+ /* If removing RRSIGs, we must remove them from all RRSets in
+ * the node. This means to copy all RRSets and then remove the
+ * RRSIGs from them.
+ */
+ dbg_ddns_verb("Removing all RRSIGs from node.\n");
+
+ knot_rrset_t **rrsets = knot_node_get_rrsets(node);
+ if (rrsets == NULL) {
+ // No RRSets in the node, nothing to remove
+ return KNOT_EOK;
+ }
+
+ /* Allocate space for the removed RRSIGs. There may be as many as there
+ * are RRSets.
+ */
+ short rrset_count = knot_node_rrset_count(node);
+
+ *removed = malloc(rrset_count * sizeof(knot_rrset_t *));
+ CHECK_ALLOC_LOG(*removed, KNOT_ENOMEM);
+ *removed_count = 0;
+
+ /* Remove all the RRSets from the node, so that we may insert the copies
+ * right away.
+ */
+ knot_node_remove_all_rrsets(node);
+
+ knot_rrset_t *rrsig = NULL;
+ int ret = 0;
+ for (int i = 0; i < rrset_count; ++i) {
+ ret = knot_ddns_process_rem_rrsig(node, rrsets[i], changes,
+ &rrsig);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to remove RRSIG.\n");
+ return ret;
+ }
+ /* Store the RRSIGs to the array of removed RRSets. */
+ (*removed)[(*removed_count)++] = rrsig;
+ }
+
+ free(rrsets);
+
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_rem_rrset(uint16_t type,
+ knot_node_t *node,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes)
+{
+ assert(node != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+
+ /*! \note
+ * We decided to automatically remove RRSIGs together with the removed
+ * RRSet as they are no longer valid or required anyway.
+ *
+ * Also refer to RFC3007, section 4.3:
+ * 'When the contents of an RRset are updated, the server MAY delete
+ * all associated SIG records, since they will no longer be valid.'
+ *
+ * (Although we are compliant with this RFC only selectively. The next
+ * section says: 'If any changes are made, the server MUST, if
+ * necessary, generate a new SOA record and new NXT records, and sign
+ * these with the appropriate zone keys.' and we are definitely not
+ * doing this...
+ *
+ * \todo Document!!
+ */
+
+ // this should be ruled out before
+ assert(type != KNOT_RRTYPE_SOA);
+
+ if (knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL
+ && type == KNOT_RRTYPE_NS) {
+ // if removing NS from apex, ignore
+ return KNOT_EOK;
+ }
+
+ knot_rrset_t **removed = NULL;
+ size_t removed_count = 0;
+ int ret = 0;
+
+ if (type == KNOT_RRTYPE_RRSIG) {
+ /* Remove all RRSIGs from the node. */
+ ret = knot_ddns_process_rem_rrsigs(node, changes, &removed,
+ &removed_count);
+ } else {
+ /* Remove the RRSet from the node. */
+ removed = malloc(sizeof(knot_rrset_t *));
+ if (!removed) {
+ ERR_ALLOC_FAILED;
+ return KNOT_ENOMEM;
+ }
+
+ dbg_ddns_detail("Removing RRSet of type: %d\n", type);
+
+ *removed = knot_node_remove_rrset(node, type);
+ removed_count = 1;
+ }
+
+ dbg_ddns_detail("Removed: %p (first item: %p), removed count: %d\n",
+ removed, (removed == NULL) ? "none" : *removed,
+ removed_count);
+
+ // no such RR
+ if (removed_count == 0 || removed == NULL) {
+ // ignore
+ return KNOT_EOK;
+ }
+
+ /* 2) Store them to 'changes' for later deallocation, together with
+ * their RRSIGs.
+ */
+ ret = knot_changes_add_old_rrsets(removed, removed_count, changes, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to add removed RRSet to 'changes': %s.\n",
+ knot_strerror(ret));
+ free(removed);
+ return ret;
+ }
+
+ /* 3) Copy the RRSets, so that they can be stored to the changeset. */
+ knot_rrset_t **to_chgset = malloc(removed_count
+ * sizeof(knot_rrset_t *));
+ if (to_chgset == NULL) {
+ dbg_ddns("Failed to allocate space for RRSets going to "
+ "changeset.\n");
+ free(removed);
+ return KNOT_ENOMEM;
+ }
+
+ for (int i = 0; i < removed_count; ++i) {
+ ret = knot_rrset_deep_copy(removed[i], &to_chgset[i], 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy the removed RRSet: %s.\n",
+ knot_strerror(ret));
+ for (int j = 0; j < i; ++j) {
+ knot_rrset_deep_free(&to_chgset[j], 1, 1, 1);
+ }
+ free(to_chgset);
+ free(removed);
+ return ret;
+ }
+ }
+
+ free(removed);
+
+ /* 4) But we must check if some of the RRs were not previously added
+ * by the same UPDATE. If yes, these must be removed from the ADD
+ * section of the changeset and also from this RRSet copy (so they
+ * are neither stored in the REMOVE section of the changeset).
+ */
+ knot_rrset_t **from_chgset = NULL;
+ size_t from_chgset_count = 0;
+
+ /* 4 a) Remove redundant RRs from the ADD section of the changeset. */
+ ret = knot_ddns_check_remove_rr2(changeset, knot_node_owner(node), type,
+ NULL, &from_chgset,
+ &from_chgset_count);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to remove possible redundant RRs from ADD "
+ "section: %s.\n", knot_strerror(ret));
+ for (int i = 0; i < removed_count; ++i) {
+ knot_rrset_deep_free(&to_chgset[i], 1, 1, 1);
+ }
+ free(from_chgset);
+ free(to_chgset);
+ return ret;
+ }
+
+ /* 4 b) Remove these RRs from the copy of the RRSets removed from zone*/
+ knot_rdata_t *rem = NULL;
+ for (int j = 0; j < removed_count; ++j) {
+ /* In each RRSet removed from the node (each can have more
+ * RDATAs) ...
+ */
+ for (int i = 0; i < from_chgset_count; ++i) {
+ /* ...try to remove redundant RDATA. Each RRSet in
+ * 'from_chgset' contains only one RDATA.
+ */
+ rem = knot_rrset_remove_rdata(to_chgset[j],
+ knot_rrset_rdata(
+ from_chgset[i]));
+ /* And delete it right away, no use for that. */
+ knot_rdata_deep_free(&rem, knot_rrset_type(
+ from_chgset[i]), 1);
+ }
+ }
+
+ /* The array is cleared, we may delete the redundant RRs. */
+ for (int i = 0; i < from_chgset_count; ++i) {
+ knot_rrset_deep_free(&from_chgset[i], 1, 1, 1);
+ }
+ free(from_chgset);
+
+ /* 5) Store the remaining RRSet to the changeset. Do not try to merge
+ * to some previous RRSet, there should be none.
+ */
+ for (int i = 0; i < removed_count; ++i) {
+ ret = knot_changeset_add_rrset(&changeset->remove,
+ &changeset->remove_count,
+ &changeset->remove_allocated,
+ to_chgset[i]);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to store the RRSet copy to changeset: "
+ "%s.\n", knot_strerror(ret));
+ for (int j = i; j < removed_count; ++j) {
+ knot_rrset_deep_free(&to_chgset[j], 1, 1, 1);
+ }
+ free(to_chgset);
+ return ret;
+ }
+ }
+
+ free(to_chgset);
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_rem_all(knot_node_t *node,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes)
+{
+ assert(node != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+
+ /*! \note
+ * This basically means to call knot_ddns_process_rem_rrset() for every
+ * type present in the node.
+ *
+ * In case of SOA and NS in apex, the RRSets should not be removed, but
+ * what about their RRSIGs??
+ *
+ * If the zone has to remain properly signed, the UPDATE will have to
+ * contain at least new SOA and RRSIGs for it (as the auto-incremented
+ * SOA would not be signed). So it should not matter if we leave the
+ * RRSIGs there or not. But in case of the NSs it's not that clear.
+ *
+ * For now, we will leave the RRSIGs there. It's easier to implement.
+ *
+ * \todo Should document this!!
+ */
+ int ret = 0;
+ knot_rrset_t **rrsets = knot_node_get_rrsets(node);
+ int count = knot_node_rrset_count(node);
+
+ if (rrsets == NULL && count != 0) {
+ dbg_ddns("Failed to fetch RRSets from node.\n");
+ return KNOT_ENOMEM;
+ }
+
+ int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
+
+ dbg_ddns_verb("Removing all RRSets (count: %d).\n", count);
+ for (int i = 0; i < count; ++i) {
+ // If the node is apex, skip NS and SOA
+ if (is_apex &&
+ (knot_rrset_type(rrsets[i]) == KNOT_RRTYPE_SOA
+ || knot_rrset_type(rrsets[i]) == KNOT_RRTYPE_NS)) {
+ /* Do not remove these RRSets, nor their RRSIGs. */
+ continue;
+ }
+
+ ret = knot_ddns_process_rem_rrset(knot_rrset_type(rrsets[i]),
+ node, changeset, changes);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to remove RRSet: %s\n",
+ knot_strerror(ret));
+ free(rrsets);
+ return ret;
+ }
+ }
+
+ free(rrsets);
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int knot_ddns_process_rr(const knot_rrset_t *rr,
+ knot_zone_contents_t *zone,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes, uint16_t qclass,
+ knot_rrset_t **rr_copy)
+{
+ assert(rr != NULL);
+ assert(zone != NULL);
+ assert(changeset != NULL);
+ assert(changes != NULL);
+ assert(rr_copy != NULL);
+
+ /* 1) Find node that will be affected. */
+ knot_node_t *node = knot_ddns_get_node(zone, rr);
+
+ /* 2) Decide what to do. */
+
+ if (knot_rrset_class(rr) == knot_zone_contents_class(zone)) {
+ return knot_ddns_process_add(rr, node, zone, changeset,
+ changes, rr_copy);
+ } else if (knot_rrset_class(rr) == KNOT_CLASS_NONE) {
+ return knot_ddns_process_rem_rr(rr, node, zone, changeset,
+ changes, qclass);
+ } else if (knot_rrset_class(rr) == KNOT_CLASS_ANY) {
+ if (knot_rrset_type(rr) == KNOT_RRTYPE_ANY) {
+ return knot_ddns_process_rem_all(node, changeset,
+ changes);
+ } else {
+ return knot_ddns_process_rem_rrset(knot_rrset_type(rr),
+ node, changeset,
+ changes);
+ }
+ } else {
+ assert(0);
+ return KNOT_ERROR;
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+/*
+ * NOTES:
+ * - 'zone' must be a copy of the current zone.
+ * - changeset must be allocated
+ * - changes must be allocated
+ *
+ * All this is done in the first parts of xfrin_apply_changesets() - extract
+ * to separate function, if possible.
+ *
+ * If anything fails, rollback must be done. The xfrin_rollback_update() may
+ * be good for this.
+ */
+int knot_ddns_process_update2(knot_zone_contents_t *zone,
+ const knot_packet_t *query,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes,
+ knot_rcode_t *rcode)
+{
+ if (zone == NULL || query == NULL || changeset == NULL || rcode == NULL
+ || changes == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = KNOT_EOK;
+
+ /* Copy base SOA RR. */
+ const knot_rrset_t *soa = knot_node_rrset(knot_zone_contents_apex(zone),
+ KNOT_RRTYPE_SOA);
+ knot_rrset_t *soa_begin = NULL;
+ knot_rrset_t *soa_end = NULL;
+ ret = knot_rrset_deep_copy(soa, &soa_begin, 0);
+ if (ret == KNOT_EOK) {
+ knot_changeset_store_soa(&changeset->soa_from,
+ &changeset->serial_from, soa_begin);
+ } else {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+
+ /* Current SERIAL */
+ int64_t sn = knot_rdata_soa_serial(knot_rrset_rdata(soa_begin));
+ int64_t sn_new;
+
+ /* Incremented SERIAL
+ * We must set it now to be able to compare SERIAL from SOAs in the
+ * UPDATE to it. Although we do not have the new SOA yet.
+ */
+ if (sn > -1) {
+ sn_new = (uint32_t)sn + 1;
+ } else {
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+
+ /* Process all RRs the Authority (Update) section. */
+
+ const knot_rrset_t *rr = NULL;
+ knot_rrset_t *rr_copy = NULL;
+
+ dbg_ddns("Processing UPDATE section.\n");
+ for (int i = 0; i < knot_packet_authority_rrset_count(query); ++i) {
+
+ rr = knot_packet_authority_rrset(query, i);
+
+ /* Check if the entry is correct. */
+ ret = knot_ddns_check_update(rr, query, rcode);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to check update RRSet:%s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /* Check if the record is SOA. If yes, check the SERIAL.
+ * If this record should cause the SOA to be replaced in the
+ * zone, use it as the ending SOA.
+ *
+ * Also handle cases where there are multiple SOAs to be added
+ * in the same UPDATE. The one with the largest SERIAL should
+ * be used.
+ *
+ * TODO: If there are more SOAs in the UPDATE one after another,
+ * the ddns_add_update() function will merge them into a
+ * RRSet. This should be handled somehow.
+ *
+ * If the serial is not larger than the current zone serial,
+ * ignore the record and continue. This will ensure that the
+ * RR processing function receives only SOA RRs that should be
+ * added to the zone (replacing the old one).
+ */
+ if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA
+ && (knot_rrset_class(rr) == KNOT_CLASS_NONE
+ || knot_rrset_class(rr) == KNOT_CLASS_ANY
+ || ns_serial_compare(knot_rdata_soa_serial(
+ knot_rrset_rdata(rr)), sn_new) < 0)) {
+ // This ignores also SOA removals
+ dbg_ddns_verb("Ignoring SOA...\n");
+ continue;
+ }
+
+ ret = knot_ddns_process_rr(rr, zone, changeset, changes,
+ knot_packet_qclass(query),
+ &rr_copy);
+
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to process update RR:%s\n",
+ knot_strerror(ret));
+ *rcode = (ret == KNOT_EMALF) ? KNOT_RCODE_FORMERR
+ : KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+
+ // we need the RR copy, that's why this code is here
+ if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
+ int64_t sn_rr = knot_rdata_soa_serial(
+ knot_rrset_rdata(rr));
+ dbg_ddns_verb("Replacing SOA. Old serial: %d, new "
+ "serial: %d\n", sn_new, sn_rr);
+ assert(ns_serial_compare(sn_rr, sn_new) >= 0);
+ assert(rr_copy != NULL);
+ sn_new = sn_rr;
+ soa_end = (knot_rrset_t *)rr_copy;
+ }
+ }
+
+ /* Ending SOA (not in the UPDATE) */
+ if (soa_end == NULL) {
+ /* If the changeset is empty, do not process anything further
+ * and indicate this to the caller, so that the changeset is not
+ * saved and zone is not switched.
+ */
+ if (knot_changeset_is_empty(changeset)) {
+ return 1;
+ }
+
+ /* If not set, create new SOA. */
+ assert(sn_new == (uint32_t)sn + 1);
+ ret = knot_rrset_deep_copy(soa, &soa_end, 1);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy ending SOA: %s\n",
+ knot_strerror(ret));
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+ knot_rdata_t *rd = knot_rrset_get_rdata(soa_end);
+ knot_rdata_soa_serial_set(rd, sn_new);
+
+ /* And replace it in the zone. */
+ ret = xfrin_replace_rrset_in_node(
+ knot_zone_contents_get_apex(zone),
+ soa_end, changes, zone);
+ if (ret != KNOT_EOK) {
+ dbg_ddns("Failed to copy replace SOA in zone: %s\n",
+ knot_strerror(ret));
+ *rcode = KNOT_RCODE_SERVFAIL;
+ return ret;
+ }
+ }
+
+ ret = knot_ddns_final_soa_to_chgset(soa_end, changeset);
+
+ return ret;
+}
diff --git a/src/libknot/updates/ddns.h b/src/libknot/updates/ddns.h
index 35dfcb7..d3cb359 100644
--- a/src/libknot/updates/ddns.h
+++ b/src/libknot/updates/ddns.h
@@ -32,6 +32,8 @@
#include "packet/packet.h"
#include "rrset.h"
#include "dname.h"
+#include "consts.h"
+#include "common/lists.h"
typedef struct knot_ddns_prereq_t {
knot_rrset_t **exist;
@@ -55,17 +57,24 @@ typedef struct knot_ddns_prereq_t {
size_t not_in_use_allocd;
} knot_ddns_prereq_t;
-int knot_ddns_check_zone(const knot_zone_t *zone, knot_packet_t *query,
- uint8_t *rcode);
+int knot_ddns_check_zone(const knot_zone_contents_t *zone,
+ const knot_packet_t *query, knot_rcode_t *rcode);
-int knot_ddns_process_prereqs(knot_packet_t *query,
- knot_ddns_prereq_t **prereqs, uint8_t *rcode);
+int knot_ddns_process_prereqs(const knot_packet_t *query,
+ knot_ddns_prereq_t **prereqs, knot_rcode_t *rcode);
int knot_ddns_check_prereqs(const knot_zone_contents_t *zone,
- knot_ddns_prereq_t **prereqs, uint8_t *rcode);
+ knot_ddns_prereq_t **prereqs, knot_rcode_t *rcode);
-int knot_ddns_process_update(knot_packet_t *query,
- knot_changeset_t **changeset, uint8_t *rcode);
+int knot_ddns_process_update(const knot_zone_contents_t *zone,
+ const knot_packet_t *query,
+ knot_changeset_t *changeset, knot_rcode_t *rcode);
+
+int knot_ddns_process_update2(knot_zone_contents_t *zone,
+ const knot_packet_t *query,
+ knot_changeset_t *changeset,
+ knot_changes_t *changes,
+ knot_rcode_t *rcode);
void knot_ddns_prereqs_free(knot_ddns_prereq_t **prereq);
diff --git a/src/libknot/updates/xfr-in.c b/src/libknot/updates/xfr-in.c
index 7d3ffdf..98d4df6 100644
--- a/src/libknot/updates/xfr-in.c
+++ b/src/libknot/updates/xfr-in.c
@@ -267,14 +267,14 @@ static int xfrin_add_orphan_rrsig(xfrin_orphan_rrsig_t **rrsigs,
while (last != NULL) {
// check if the RRSIG is not similar to the one we want to add
assert(last->rrsig != NULL);
- if (knot_rrset_compare(last->rrsig, rr,
+ if (knot_rrset_match(last->rrsig, rr,
KNOT_RRSET_COMPARE_HEADER) == 1
&& knot_rdata_rrsig_type_covered(knot_rrset_rdata(
last->rrsig))
== knot_rdata_rrsig_type_covered(knot_rrset_rdata(rr))) {
ret = knot_rrset_merge_no_dupl((void **)&last->rrsig,
(void **)&rr);
- if (ret != KNOT_EOK) {
+ if (ret < 0) {
return ret;
} else {
return 1;
@@ -955,13 +955,13 @@ int xfrin_process_ixfr_packet(knot_ns_xfr_t *xfr)
if (*chs == NULL) {
dbg_xfrin_verb("Changesets empty, creating new.\n");
- ret = knot_changeset_allocate(chs);
+ ret = knot_changeset_allocate(chs, KNOT_CHANGESET_TYPE_IXFR);
if (ret != KNOT_EOK) {
knot_rrset_deep_free(&rr, 1, 1, 1);
knot_packet_free(&packet);
return ret;
}
-
+
// the first RR must be a SOA
if (knot_rrset_type(rr) != KNOT_RRTYPE_SOA) {
dbg_xfrin("First RR is not a SOA RR!\n");
@@ -1069,9 +1069,9 @@ int xfrin_process_ixfr_packet(knot_ns_xfr_t *xfr)
assert((*chs)->sets[(*chs)->count - 1].soa_from != NULL);
if ((*chs)->sets[(*chs)->count - 1].soa_to == NULL) {
- state = XFRIN_CHANGESET_REMOVE;
+ state = KNOT_CHANGESET_REMOVE;
} else {
- state = XFRIN_CHANGESET_ADD;
+ state = KNOT_CHANGESET_ADD;
}
}
@@ -1134,17 +1134,17 @@ dbg_xfrin_exec_verb(
ret = knot_changeset_add_soa(
&(*chs)->sets[(*chs)->count - 1], rr,
- XFRIN_CHANGESET_REMOVE);
+ KNOT_CHANGESET_REMOVE);
if (ret != KNOT_EOK) {
knot_rrset_deep_free(&rr, 1, 1, 1);
goto cleanup;
}
// change state to REMOVE
- state = XFRIN_CHANGESET_REMOVE;
+ state = KNOT_CHANGESET_REMOVE;
}
break;
- case XFRIN_CHANGESET_REMOVE:
+ case KNOT_CHANGESET_REMOVE:
// if the next RR is SOA, store it and change state to
// ADD
if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
@@ -1154,25 +1154,25 @@ dbg_xfrin_exec_verb(
ret = knot_changeset_add_soa(
&(*chs)->sets[(*chs)->count - 1], rr,
- XFRIN_CHANGESET_ADD);
+ KNOT_CHANGESET_ADD);
if (ret != KNOT_EOK) {
knot_rrset_deep_free(&rr, 1, 1, 1);
goto cleanup;
}
- state = XFRIN_CHANGESET_ADD;
+ state = KNOT_CHANGESET_ADD;
} else {
// just add the RR to the REMOVE part and
// continue
if ((ret = knot_changeset_add_new_rr(
&(*chs)->sets[(*chs)->count - 1], rr,
- XFRIN_CHANGESET_REMOVE)) != KNOT_EOK) {
+ KNOT_CHANGESET_REMOVE)) != KNOT_EOK) {
knot_rrset_deep_free(&rr, 1, 1, 1);
goto cleanup;
}
}
break;
- case XFRIN_CHANGESET_ADD:
+ case KNOT_CHANGESET_ADD:
// if the next RR is SOA change to state -1 and do not
// parse next RR
if (knot_rrset_type(rr) == KNOT_RRTYPE_SOA) {
@@ -1183,7 +1183,7 @@ dbg_xfrin_exec_verb(
// just add the RR to the ADD part and continue
if ((ret = knot_changeset_add_new_rr(
&(*chs)->sets[(*chs)->count - 1], rr,
- XFRIN_CHANGESET_ADD)) != KNOT_EOK) {
+ KNOT_CHANGESET_ADD)) != KNOT_EOK) {
knot_rrset_deep_free(&rr, 1, 1, 1);
goto cleanup;
}
@@ -1230,137 +1230,6 @@ cleanup:
/* Applying changesets to zone */
/*----------------------------------------------------------------------------*/
-static int xfrin_changes_check_rrsets(knot_rrset_t ***rrsets,
- int *count, int *allocated, int to_add)
-{
- if (*count + to_add <= *allocated) {
- return KNOT_EOK;
- }
-
- int new_count = (*allocated == 0) ? 2 : *allocated * 2;
- while (new_count < *count + to_add) {
- new_count *= 2;
- }
-
- /* Allocate new memory block. */
- knot_rrset_t **rrsets_new = malloc(new_count * sizeof(knot_rrset_t *));
- if (rrsets_new == NULL) {
- return KNOT_ENOMEM;
- }
-
- /* Initialize new memory and copy old data. */
- memset(rrsets_new, 0, new_count * sizeof(knot_rrset_t *));
- memcpy(rrsets_new, *rrsets, (*allocated) * sizeof(knot_rrset_t *));
-
- /* Free old nodes and switch pointers. */
- free(*rrsets);
- *rrsets = rrsets_new;
- *allocated = new_count;
-
- return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_changes_check_nodes(knot_node_t ***nodes,
- int *count, int *allocated)
-{
- assert(nodes != NULL);
- assert(count != NULL);
- assert(allocated != 0);
-
- if (*count + 2 <= *allocated) {
- return KNOT_EOK;
- }
-
- int new_count = (*allocated == 0) ? 2 : *allocated * 2;
-
- /* Allocate new memory block. */
- const size_t node_len = sizeof(knot_node_t *);
- knot_node_t **nodes_new = malloc(new_count * node_len);
- if (nodes_new == NULL) {
- return KNOT_ENOMEM;
- }
-
- /* Clear memory block and copy old data. */
- memset(nodes_new, 0, new_count * node_len);
- memcpy(nodes_new, *nodes, (*allocated) * node_len);
-
- /* Free old nodes and switch pointers. */
- free(*nodes);
- *nodes = nodes_new;
- *allocated = new_count;
-
- return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static int xfrin_changes_check_rdata(knot_rdata_t ***rdatas, uint **types,
- int count, int *allocated, int to_add)
-{
- if (count + to_add <= *allocated) {
- return KNOT_EOK;
- }
-
- int new_count = (*allocated == 0) ? 2 : *allocated * 2;
- while (new_count < count + to_add) {
- new_count *= 2;
- }
-
- /* Allocate new memory block. */
- knot_rdata_t **rdatas_new = malloc(new_count * sizeof(knot_rdata_t *));
- if (rdatas_new == NULL) {
- return KNOT_ENOMEM;
- }
-
- uint *types_new = malloc(new_count * sizeof(uint));
- if (types_new == NULL) {
- free(rdatas_new);
- return KNOT_ENOMEM;
- }
-
- /* Initialize new memory and copy old data. */
- memset(rdatas_new, 0, new_count * sizeof(knot_rdata_t *));
- memcpy(rdatas_new, *rdatas, (*allocated) * sizeof(knot_rdata_t *));
-
- memset(types_new, 0, new_count * sizeof(uint));
- memcpy(types_new, *types, (*allocated) * sizeof(uint));
-
- /* Free old rdatas and switch pointers. */
- free(*rdatas);
- free(*types);
- *rdatas = rdatas_new;
- *types = types_new;
- *allocated = new_count;
-
- return KNOT_EOK;
-}
-
-/*----------------------------------------------------------------------------*/
-
-static void xfrin_changes_add_rdata(knot_rdata_t **rdatas, uint *types,
- int *count, knot_rdata_t *rdata, uint type)
-{
- if (rdata == NULL) {
- return;
- }
-
- // Add all RDATAs from the chain!!
-
- knot_rdata_t *r = rdata;
- do {
- dbg_xfrin_detail("Adding RDATA to RDATA list: %p\n", r);
- rdatas[*count] = r;
- types[*count] = type;
- ++*count;
-
- r = r->next;
- } while (r != NULL && r != rdata);
-}
-
-/*----------------------------------------------------------------------------*/
-
static void xfrin_zone_contents_free(knot_zone_contents_t **contents)
{
/*! \todo This should be all in some API!! */
@@ -1386,7 +1255,8 @@ static void xfrin_zone_contents_free(knot_zone_contents_t **contents)
/*----------------------------------------------------------------------------*/
static knot_rdata_t *xfrin_remove_rdata(knot_rrset_t *from,
- const knot_rrset_t *what)
+ const knot_rrset_t *what,
+ int ddns_check)
{
knot_rdata_t *old = NULL;
knot_rdata_t *old_actual = NULL;
@@ -1396,6 +1266,20 @@ static knot_rdata_t *xfrin_remove_rdata(knot_rrset_t *from,
while (rdata != NULL) {
// rdata - the RDATA to be removed
// old_actual - removed RDATA
+
+ /*
+ * DDNS special handling - last apex NS should remain in the
+ * zone.
+ *
+ * TODO: this is not correct, the last NS from the 'what' RRSet
+ * may not even be in the zone.
+ */
+ if (ddns_check
+ && knot_rrset_rdata_next(what, rdata) == NULL) {
+ assert(knot_rrset_type(from) == KNOT_RRTYPE_NS);
+ return old;
+ }
+
old_actual = knot_rrset_remove_rdata(from, rdata);
if (old_actual != NULL) {
old_actual->next = old;
@@ -1410,8 +1294,8 @@ static knot_rdata_t *xfrin_remove_rdata(knot_rrset_t *from,
/*----------------------------------------------------------------------------*/
-static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
- knot_changes_t *changes)
+int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
+ knot_changes_t *changes, int save_new)
{
dbg_xfrin_detail("Copying old RRSet: %p\n", old);
// create new RRSet by copying the old one
@@ -1420,57 +1304,65 @@ static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
dbg_xfrin("Failed to create RRSet copy.\n");
return KNOT_ENOMEM;
}
+
+ int count = 0;
// add the RRSet to the list of new RRSets
// create place also for RRSIGs
- ret = xfrin_changes_check_rrsets(&changes->new_rrsets,
- &changes->new_rrsets_count,
- &changes->new_rrsets_allocated, 2);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add new RRSet to list.\n");
- knot_rrset_deep_free(copy, 1, 1, 1);
- return ret;
- }
-
- int count = knot_rrset_rdata_rr_count(*copy);
- count += knot_rrset_rdata_rr_count((*copy)->rrsigs);
-
- // add the copied RDATA to the list of new RDATA
- ret = xfrin_changes_check_rdata(&changes->new_rdata,
- &changes->new_rdata_types,
- changes->new_rdata_count,
- &changes->new_rdata_allocated, count);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add new RRSet to list.\n");
- knot_rrset_deep_free(copy, 1, 1, 1);
- return ret;
- }
-
- changes->new_rrsets[changes->new_rrsets_count++] = *copy;
+ if (save_new) {
+ ret = knot_changes_rrsets_reserve(&changes->new_rrsets,
+ &changes->new_rrsets_count,
+ &changes->new_rrsets_allocated,
+ 2);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new RRSet to list.\n");
+ knot_rrset_deep_free(copy, 1, 1, 1);
+ return ret;
+ }
- dbg_xfrin_detail("Adding RDATA from the RRSet copy to new RDATA list."
- "\n");
- xfrin_changes_add_rdata(changes->new_rdata, changes->new_rdata_types,
- &changes->new_rdata_count,
- knot_rrset_get_rdata(*copy),
- knot_rrset_type(*copy));
+ count = knot_rrset_rdata_rr_count(*copy);
+ count += knot_rrset_rdata_rr_count((*copy)->rrsigs);
+
+ // add the copied RDATA to the list of new RDATA
+ ret = knot_changes_rdata_reserve(&changes->new_rdata,
+ &changes->new_rdata_types,
+ changes->new_rdata_count,
+ &changes->new_rdata_allocated,
+ count);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new RRSet to list.\n");
+ knot_rrset_deep_free(copy, 1, 1, 1);
+ return ret;
+ }
+
+ changes->new_rrsets[changes->new_rrsets_count++] = *copy;
+
+ dbg_xfrin_detail("Adding RDATA from the RRSet copy to new RDATA list."
+ "\n");
+ knot_changes_add_rdata(changes->new_rdata,
+ changes->new_rdata_types,
+ &changes->new_rdata_count,
+ knot_rrset_get_rdata(*copy),
+ knot_rrset_type(*copy));
- if ((*copy)->rrsigs != NULL) {
- assert(old->rrsigs != NULL);
- changes->new_rrsets[changes->new_rrsets_count++] =
- (*copy)->rrsigs;
-
- dbg_xfrin_detail("Adding RDATA from RRSIG of the RRSet copy to "
- "new RDATA list.\n");
- xfrin_changes_add_rdata(changes->new_rdata,
- changes->new_rdata_types,
- &changes->new_rdata_count,
- knot_rrset_get_rdata((*copy)->rrsigs),
- KNOT_RRTYPE_RRSIG);
+ if ((*copy)->rrsigs != NULL) {
+ assert(old->rrsigs != NULL);
+ changes->new_rrsets[changes->new_rrsets_count++] =
+ (*copy)->rrsigs;
+
+ dbg_xfrin_detail("Adding RDATA from RRSIG of the RRSet copy to "
+ "new RDATA list.\n");
+ knot_changes_add_rdata(changes->new_rdata,
+ changes->new_rdata_types,
+ &changes->new_rdata_count,
+ knot_rrset_get_rdata(
+ (*copy)->rrsigs),
+ KNOT_RRTYPE_RRSIG);
+ }
}
// add the old RRSet to the list of old RRSets
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
&changes->old_rrsets_count,
&changes->old_rrsets_allocated, 2);
if (ret != KNOT_EOK) {
@@ -1482,7 +1374,7 @@ static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
count += knot_rrset_rdata_rr_count(old->rrsigs);
// and old RDATA to the list of old RDATA
- ret = xfrin_changes_check_rdata(&changes->old_rdata,
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
&changes->old_rdata_types,
changes->old_rdata_count,
&changes->old_rdata_allocated, count);
@@ -1494,7 +1386,7 @@ static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
changes->old_rrsets[changes->old_rrsets_count++] = old;
dbg_xfrin_detail("Adding RDATA from old RRSet to old RDATA list.\n");
- xfrin_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
+ knot_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
&changes->old_rdata_count, old->rdata,
knot_rrset_type(old));
@@ -1504,7 +1396,7 @@ static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
dbg_xfrin_detail("Adding RDATA from RRSIG of the old RRSet to "
"old RDATA list.\n");
- xfrin_changes_add_rdata(changes->old_rdata,
+ knot_changes_add_rdata(changes->old_rdata,
changes->old_rdata_types,
&changes->old_rdata_count,
old->rrsigs->rdata,
@@ -1516,8 +1408,9 @@ static int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
/*----------------------------------------------------------------------------*/
-static int xfrin_copy_rrset(knot_node_t *node, knot_rr_type_t type,
- knot_rrset_t **rrset, knot_changes_t *changes)
+int xfrin_copy_rrset(knot_node_t *node, knot_rr_type_t type,
+ knot_rrset_t **rrset, knot_changes_t *changes,
+ int save_new)
{
dbg_xfrin_exec_detail(
char *name = knot_dname_to_str(knot_node_owner(node));
@@ -1536,7 +1429,7 @@ dbg_xfrin_exec_detail(
return 1;
}
- int ret = xfrin_copy_old_rrset(old, rrset, changes);
+ int ret = xfrin_copy_old_rrset(old, rrset, changes, save_new);
if (ret != KNOT_EOK) {
return ret;
}
@@ -1591,7 +1484,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
// copy the rrset
dbg_xfrin_detail("Copying RRSet that carries the RRSIGs.\n");
- ret = xfrin_copy_rrset(node, type, rrset, changes);
+ ret = xfrin_copy_rrset(node, type, rrset, changes, 1);
if (ret != KNOT_EOK) {
dbg_xfrin("Failed to copy rrset from changeset.\n");
return ret;
@@ -1616,7 +1509,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
dbg_xfrin_verb("Using RRSIG from previous iteration\n");
rrsigs = *rrsigs_old;
} else {
- ret = xfrin_copy_old_rrset(old, &rrsigs, changes);
+ ret = xfrin_copy_old_rrset(old, &rrsigs, changes, 1);
if (ret != KNOT_EOK) {
return ret;
}
@@ -1640,7 +1533,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
// now in '*rrset' we have a copy of the RRSet which holds the RRSIGs
// and in 'rrsigs' we have the copy of the RRSIGs
- knot_rdata_t *rdata = xfrin_remove_rdata(rrsigs, remove);
+ knot_rdata_t *rdata = xfrin_remove_rdata(rrsigs, remove, 0);
if (rdata == NULL) {
dbg_xfrin("Failed to remove RDATA from RRSet: %s.\n",
knot_strerror(ret));
@@ -1650,7 +1543,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
int count = knot_rdata_count(rdata);
// connect the RDATA to the list of old RDATA
- ret = xfrin_changes_check_rdata(&changes->old_rdata,
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
&changes->old_rdata_types,
changes->old_rdata_count,
&changes->old_rdata_allocated, count);
@@ -1658,7 +1551,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
return ret;
}
- xfrin_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
+ knot_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
&changes->old_rdata_count, rdata,
knot_rrset_type(remove));
@@ -1670,7 +1563,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
knot_rrset_set_rrsigs(*rrset, NULL);
// add RRSet to the list of old RRSets
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
&changes->old_rrsets_count,
&changes->old_rrsets_allocated,
1);
@@ -1695,7 +1588,7 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
knot_rrset_type(*rrset));
assert(tmp == *rrset);
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
&changes->old_rrsets_count,
&changes->old_rrsets_allocated,
1);
@@ -1720,7 +1613,8 @@ static int xfrin_apply_remove_rrsigs(knot_changes_t *changes,
static int xfrin_apply_remove_normal(knot_changes_t *changes,
const knot_rrset_t *remove,
knot_node_t *node,
- knot_rrset_t **rrset)
+ knot_rrset_t **rrset,
+ uint32_t chflags)
{
assert(changes != NULL);
assert(remove != NULL);
@@ -1731,7 +1625,18 @@ static int xfrin_apply_remove_normal(knot_changes_t *changes,
dbg_xfrin_detail("Removing RRSet: \n");
knot_rrset_dump(remove, 0);
-
+
+ int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
+
+ /*
+ * First handle the special case of DDNS - do not remove SOA from apex.
+ */
+ if ((chflags & KNOT_CHANGESET_TYPE_DDNS) && is_apex
+ && knot_rrset_type(remove) == KNOT_RRTYPE_SOA) {
+ dbg_xfrin_verb("Ignoring SOA removal in UPDATE.\n");
+ return KNOT_EOK;
+ }
+
// now we have the copy of the node, so lets get the right RRSet
// check if we do not already have it
if (*rrset
@@ -1748,7 +1653,7 @@ static int xfrin_apply_remove_normal(knot_changes_t *changes,
* probably not cause problems. TEST!!
*/
ret = xfrin_copy_rrset(node,
- knot_rrset_type(remove), rrset, changes);
+ knot_rrset_type(remove), rrset, changes, 1);
if (ret != KNOT_EOK) {
return ret;
}
@@ -1769,8 +1674,12 @@ dbg_xfrin_exec_detail(
);
// remove the specified RRs from the RRSet (de facto difference of sets)
- knot_rdata_t *rdata = xfrin_remove_rdata(*rrset, remove);
- if (rdata == NULL) {
+ int ddns_remove_ns_from_apex =
+ ((chflags & KNOT_CHANGESET_TYPE_DDNS) && is_apex
+ && knot_rrset_type(*rrset) == KNOT_RRTYPE_NS);
+ knot_rdata_t *rdata = xfrin_remove_rdata(*rrset, remove,
+ ddns_remove_ns_from_apex);
+ if (rdata == NULL && !ddns_remove_ns_from_apex) {
dbg_xfrin_verb("Failed to remove RDATA from RRSet\n");
// In this case, the RDATA was not found in the RRSet
return 1;
@@ -1788,25 +1697,32 @@ dbg_xfrin_exec_detail(
}
);
- int count = knot_rdata_count(rdata);
- // connect the RDATA to the list of old RDATA
- ret = xfrin_changes_check_rdata(&changes->old_rdata,
- &changes->old_rdata_types,
- changes->old_rdata_count,
- &changes->old_rdata_allocated, count);
- if (ret != KNOT_EOK) {
- return ret;
- }
+ if (rdata != NULL) {
+ int count = knot_rdata_count(rdata);
+ // connect the RDATA to the list of old RDATA
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
+ &changes->old_rdata_types,
+ changes->old_rdata_count,
+ &changes->old_rdata_allocated,
+ count);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
- xfrin_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
- &changes->old_rdata_count, rdata,
- knot_rrset_type(remove));
+ knot_changes_add_rdata(changes->old_rdata,
+ changes->old_rdata_types,
+ &changes->old_rdata_count, rdata,
+ knot_rrset_type(remove));
+ }
// if the RRSet is empty, remove from node and add to old RRSets
// check if there is no RRSIGs; if there are, leave the RRSet
// there; it may be eventually removed when the RRSIGs are removed
if (knot_rrset_rdata(*rrset) == NULL
&& knot_rrset_rrsigs(*rrset) == NULL) {
+ // The RRSet should not be empty if we were removing NSs from
+ // apex in case of DDNS
+ assert(!ddns_remove_ns_from_apex);
knot_rrset_t *tmp = knot_node_remove_rrset(node,
knot_rrset_type(*rrset));
@@ -1815,7 +1731,7 @@ dbg_xfrin_exec_detail(
// add the removed RRSet to list of old RRSets
assert(tmp == *rrset);
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
&changes->old_rrsets_count,
&changes->old_rrsets_allocated,
1);
@@ -1836,43 +1752,124 @@ dbg_xfrin_exec_detail(
/*----------------------------------------------------------------------------*/
/*! \todo Needs review - RRs may not be merged into RRSets. */
static int xfrin_apply_remove_all_rrsets(knot_changes_t *changes,
- knot_node_t *node, uint16_t type)
+ knot_node_t *node, uint16_t type,
+ uint32_t chflags)
{
- int ret;
+ int ret = KNOT_EOK;
+ knot_rrset_t **rrsets = NULL;
+ unsigned rrsets_count = 0;
+ int is_apex = knot_node_rrset(node, KNOT_RRTYPE_SOA) != NULL;
- if (type == KNOT_RRTYPE_ANY) {
- // put all the RRSets to the changes structure
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
- &changes->old_rrsets_count,
- &changes->old_rrsets_allocated,
- knot_node_rrset_count(node));
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to check changeset rrsets.\n");
- return ret;
- }
+dbg_xfrin_exec_verb(
+ char *name = knot_dname_to_str(knot_node_owner(node));
+ dbg_xfrin_verb("Removing all RRSets from node %s of type %s. "
+ "Is apex: %d, changeset flags: %u\n",
+ name, knot_rrtype_to_string(type), is_apex, chflags);
+ free(name);
+);
- knot_rrset_t **rrsets = knot_node_get_rrsets(node);
- knot_rrset_t **place = changes->old_rrsets
- + changes->old_rrsets_count;
- /*! \todo Test this!!! */
- memcpy(place, rrsets, knot_node_rrset_count(node)
- * sizeof(knot_rrset_t *));
+ /*! \todo ref #937 is it OK to modify nodes at this point?
+ * shouldn't it be after the zones are switched? */
+
+ /* Assemble RRSets to remove. */
+ if (type == KNOT_RRTYPE_ANY) {
+ /* Remove all RRSets from the node. */
+ /* If removing from zone apex in an UPDATE, NS and SOA records
+ * should be left unchanged.
+ * We might either remove all RRSets and then return SOA and
+ * NS RRSets to the node. Or find all existing types in the node
+ * and remove all except NS and SOA. The first approach is
+ * IMHO faster.
+ */
- // remove all RRSets from the node
+ rrsets = knot_node_get_rrsets(node);
+ short rr_count = knot_node_rrset_count(node);
+ if (rr_count > 0) {
+ rrsets_count = (unsigned)rr_count;
+ }
knot_node_remove_all_rrsets(node);
+
+ /*
+ * If apex, return SOA and NS RRSets to the node and remove
+ * them from the list (so they are not deleted later).
+ *
+ * This function is called only when processing DDNS, but one
+ * never knows, so we'll rather check it
+ */
+ if (is_apex && (chflags & KNOT_CHANGESET_TYPE_DDNS)) {
+ dbg_xfrin_detail("DDNS: returning SOA and NS to the "
+ "node.\n");
+ for (unsigned i = 0; i < rrsets_count; ++i) {
+ if (knot_rrset_type(rrsets[i])
+ == KNOT_RRTYPE_SOA
+ || knot_rrset_type(rrsets[i])
+ == KNOT_RRTYPE_NS) {
+ dbg_xfrin_detail("Returning...\n");
+ knot_node_add_rrset(node, rrsets[i], 0);
+ rrsets[i] = NULL;
+ }
+ }
+ }
} else {
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
- &changes->old_rrsets_count,
- &changes->old_rrsets_allocated,
- 1);
+ /* Remove only the RRSet with given type. */
+ /* First we must check if we're not removing NS or SOA from
+ * apex. This change should be ignored.
+ *
+ * This function is called only when processing DDNS, but one
+ * never knows, so we'll rather check it
+ */
+ if (is_apex && (chflags & KNOT_CHANGESET_TYPE_DDNS)
+ && (type == KNOT_RRTYPE_SOA || type == KNOT_RRTYPE_NS)) {
+ dbg_xfrin_detail("DDNS: ignoring SOA or NS removal.\n");
+ return KNOT_EOK;
+ }
+
+ rrsets = malloc(sizeof(knot_rrset_t*));
+ if (rrsets) {
+ *rrsets = knot_node_remove_rrset(node, type);
+ rrsets_count = 1;
+ }
+ }
+
+ ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
+ &changes->old_rrsets_count,
+ &changes->old_rrsets_allocated,
+ rrsets_count);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to reserve changes rrsets.\n");
+ free(rrsets);
+ return ret;
+ }
+
+ /* Mark RRsets and RDATA for removal. */
+ for (unsigned i = 0; i < rrsets_count; ++i) {
+ if (rrsets[i] == NULL) {
+ continue;
+ }
+
+ changes->old_rrsets[changes->old_rrsets_count++] = rrsets[i];
+
+ /* Remove old RDATA. */
+ int rdata_count = knot_rrset_rdata_rr_count(rrsets[i]);
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
+ &changes->old_rdata_types,
+ changes->old_rdata_count,
+ &changes->old_rdata_allocated,
+ rdata_count);
if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to check changeset rrsets.\n");
+ dbg_xfrin("Failed to reserve changes rdata.\n");
+ free(rrsets);
return ret;
}
- // remove only RRSet with the given type
- knot_rrset_t *rrset = knot_node_remove_rrset(node, type);
- changes->old_rrsets[changes->old_rrsets_count++] = rrset;
+
+ knot_changes_add_rdata(changes->old_rdata,
+ changes->old_rdata_types,
+ &changes->old_rdata_count,
+ knot_rrset_get_rdata(rrsets[i]),
+ knot_rrset_type(rrsets[i]));
}
+
+ free(rrsets);
return KNOT_EOK;
}
@@ -1917,11 +1914,179 @@ static knot_node_t *xfrin_add_new_node(knot_zone_contents_t *contents,
/*----------------------------------------------------------------------------*/
+int xfrin_replace_rrset_in_node(knot_node_t *node,
+ knot_rrset_t *rrset_new,
+ knot_changes_t *changes,
+ knot_zone_contents_t *contents)
+{
+ knot_rr_type_t type = knot_rrset_type(rrset_new);
+ // remove RRSet of the proper type from the node
+ dbg_xfrin_verb("Removing RRSet of type: %s.\n",
+ knot_rrtype_to_string(type));
+ knot_rrset_t *rrset_old = knot_node_remove_rrset(node, type);
+ assert(rrset_old != NULL);
+
+ // add the old RRSet to the list of old RRSets
+ int ret = knot_changes_rrsets_reserve(&changes->old_rrsets,
+ &changes->old_rrsets_count,
+ &changes->old_rrsets_allocated, 1);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add old RRSet to list.\n");
+ return ret;
+ }
+
+ // save also the RDATA, because RDATA are not deleted with the RRSet
+ // The count should be 1, but just to be sure....
+ int count = knot_rrset_rdata_rr_count(rrset_old);
+ ret = knot_changes_rdata_reserve(&changes->old_rdata,
+ &changes->old_rdata_types,
+ changes->old_rdata_count,
+ &changes->old_rdata_allocated, count);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add old RDATA to list.\n");
+ return ret;
+ }
+
+ // save the new RRSet to the new RRSet, so that it is deleted if the
+ // apply fails
+ ret = knot_changes_rrsets_reserve(&changes->new_rrsets,
+ &changes->new_rrsets_count,
+ &changes->new_rrsets_allocated, 1);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new RRSet to list.\n");
+ return ret;
+ }
+
+ // The count should be 1, but just to be sure....
+ count = knot_rrset_rdata_rr_count(rrset_new);
+ // save the new RDATA
+ ret = knot_changes_rdata_reserve(&changes->new_rdata,
+ &changes->new_rdata_types,
+ changes->new_rdata_count,
+ &changes->new_rdata_allocated, count);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to add new RDATA to list.\n");
+ return ret;
+ }
+
+ changes->old_rrsets[changes->old_rrsets_count++] = rrset_old;
+
+ dbg_xfrin_verb("Adding RDATA from old RRSet to the list of old RDATA."
+ "\n");
+ knot_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
+ &changes->old_rdata_count,
+ knot_rrset_get_rdata(rrset_old), type);
+
+ // store RRSIGs from the old RRSet to the new
+ knot_rrset_set_rrsigs(rrset_new, knot_rrset_get_rrsigs(rrset_old));
+
+ // insert the new RRSet to the node
+ dbg_xfrin_verb("Adding new RRSet.\n");
+ ret = knot_zone_contents_add_rrset(contents, rrset_new, &node,
+ KNOT_RRSET_DUPL_SKIP, 1);
+
+ if (ret < 0) {
+ dbg_xfrin("Failed to add RRSet to node.\n");
+ return KNOT_ERROR;
+ }
+ assert(ret == 0);
+
+ changes->new_rrsets[changes->new_rrsets_count++] = rrset_new;
+
+ dbg_xfrin_verb("Adding RDATA from new RRSet to the list of new RDATA."
+ "\n");
+ knot_changes_add_rdata(changes->new_rdata, changes->new_rdata_types,
+ &changes->new_rdata_count,
+ knot_rrset_get_rdata(rrset_new), type);
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+static int xfrin_apply_add_normal_ddns(knot_changes_t *changes,
+ knot_rrset_t *add, knot_node_t *node,
+ knot_zone_contents_t *contents)
+{
+ int ret;
+
+ /* 1) Adding SOA. */
+ if (knot_rrset_type(add) == KNOT_RRTYPE_SOA) {
+ /* a) If trying to add SOA to non-apex node, or the
+ * serial is less than the current serial, ignore.
+ */
+ if (knot_node_rrset(node, KNOT_RRTYPE_SOA) == NULL
+ || ns_serial_compare(knot_rdata_soa_serial(
+ knot_rrset_rdata(
+ knot_node_rrset(node, KNOT_RRTYPE_SOA))),
+ knot_rdata_soa_serial(knot_rrset_rdata(add)
+ )) > 0
+ ) {
+ dbg_ddns_verb("DDNS: Ignoring SOA.\n");
+ return KNOT_EOK;
+ } else {
+ dbg_ddns_verb("DDNS: replacing SOA (old serial: %u,"
+ " new serial: %u.\n",
+ knot_rdata_soa_serial(knot_rrset_rdata(
+ knot_node_rrset(node,
+ KNOT_RRTYPE_SOA))),
+ knot_rdata_soa_serial(knot_rrset_rdata(
+ add)));
+ /* b) Otherwise, replace the current SOA. */
+ ret = xfrin_replace_rrset_in_node(node, add,
+ changes,
+ contents);
+ /* In this case we must however remove the ADD RRSet
+ * from the changeset, so that it is not deleted
+ * afterwards.
+ */
+ if (ret == KNOT_EOK) {
+ return 3;
+ } else {
+ return ret;
+ }
+ }
+ } else if (knot_rrset_type(add) == KNOT_RRTYPE_CNAME) {
+ /* 2) Adding CNAME... */
+ if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) {
+ dbg_ddns_verb("DDNS: replacing CNAME.\n");
+ /* a) ... to a CNAME node => replace. */
+ ret = xfrin_replace_rrset_in_node(node, add, changes,
+ contents);
+ /* In this case we must however remove the ADD RRSet
+ * from the changeset, so that it is not deleted
+ * afterwards.
+ */
+ if (ret == KNOT_EOK) {
+ return 3;
+ } else {
+ return ret;
+ }
+ } else if (knot_node_rrset_count(node) > 0) {
+ dbg_ddns_verb("DDNS: ignoring CNAME (non-empty node)\n");
+ /* b) ... to a non-empty node => ignore. */
+ return KNOT_EOK;
+ }
+ /* c) ... to an empty node => process normally. */
+ } else if (knot_node_rrset(node, KNOT_RRTYPE_CNAME) != NULL) {
+ /* 3) Adding other RRSets to CNAME node => ignore. */
+ dbg_ddns_verb("DDNS: ignoring RRSet (CNAME node)\n");
+ // handled in previous case
+ assert(knot_rrset_type(add) != KNOT_RRTYPE_CNAME);
+ return KNOT_EOK;
+ }
+
+ return 1; // Continue normal processing
+}
+
+/*----------------------------------------------------------------------------*/
+
static int xfrin_apply_add_normal(knot_changes_t *changes,
knot_rrset_t *add,
knot_node_t *node,
knot_rrset_t **rrset,
- knot_zone_contents_t *contents)
+ knot_zone_contents_t *contents,
+ uint32_t chflags)
{
assert(changes != NULL);
assert(add != NULL);
@@ -1935,6 +2100,15 @@ dbg_xfrin_exec_detail(
dbg_xfrin_detail("applying rrset:\n");
knot_rrset_dump(add, 0);
);
+
+ /* DDNS special cases. */
+ if (chflags & KNOT_CHANGESET_TYPE_DDNS) {
+ ret = xfrin_apply_add_normal_ddns(changes, add, node, contents);
+ /* Continue only if return value is 1. */
+ if (ret != 1) {
+ return ret;
+ }
+ }
int copied = 0;
/*! \note Reusing RRSet from previous function caused it not to be
@@ -1955,7 +2129,7 @@ dbg_xfrin_exec_detail(
knot_rrset_t *old = *rrset;
if (*rrset != NULL) {
- ret = xfrin_copy_old_rrset(old, rrset, changes);
+ ret = xfrin_copy_old_rrset(old, rrset, changes, 1);
if (ret != KNOT_EOK) {
return ret;
}
@@ -1995,7 +2169,7 @@ dbg_xfrin_exec_detail(
if (ret < 0) {
dbg_xfrin("Failed to add RRSet to node.\n");
- return KNOT_ERROR;
+ return ret;
}
assert(ret == 0);
@@ -2021,6 +2195,7 @@ dbg_xfrin_exec_detail(
*
* TODO: add the 'add' rrset to list of old RRSets?
*/
+
dbg_xfrin_detail("Merging RRSets with owners: %s, %s types: %s, %s\n",
(*rrset)->owner->name, add->owner->name,
knot_rrtype_to_string((*rrset)->type),
@@ -2039,7 +2214,7 @@ dbg_xfrin_exec_detail(
}
ret = knot_rrset_merge_no_dupl((void **)rrset, (void **)&add);
- if (ret != KNOT_EOK) {
+ if (ret < 0) {
dbg_xfrin("Failed to merge changeset RRSet.\n");
return ret;
}
@@ -2103,7 +2278,7 @@ dbg_xfrin_exec_verb(
dbg_xfrin_verb("Using RRSet from previous iteration.\n");
} else {
// copy the rrset
- ret = xfrin_copy_rrset(node, type, rrset, changes);
+ ret = xfrin_copy_rrset(node, type, rrset, changes, 1);
if (ret < 0) {
return ret;
} else if (ret != KNOT_EOK) {
@@ -2128,7 +2303,7 @@ dbg_xfrin_exec_verb(
dbg_xfrin_detail("Created new RRSet for RRSIG: %p.\n", *rrset);
// add the RRset to the list of new RRsets
- ret = xfrin_changes_check_rrsets(
+ ret = knot_changes_rrsets_reserve(
&changes->new_rrsets,
&changes->new_rrsets_count,
&changes->new_rrsets_allocated, 1);
@@ -2183,7 +2358,8 @@ dbg_xfrin_exec_detail(
dbg_xfrin_verb("Using RRSIG from previous iteration\n");
rrsig = *rrsigs_old;
} else {
- ret = xfrin_copy_old_rrset(old, &rrsig, changes);
+ ret = xfrin_copy_old_rrset(old, &rrsig, changes,
+ 1);
if (ret != KNOT_EOK) {
return ret;
}
@@ -2202,7 +2378,7 @@ dbg_xfrin_exec_detail(
// merge the changeset RRSet to the copy
dbg_xfrin_detail("Merging RRSIG to the one in the RRSet.\n");
ret = knot_rrset_merge_no_dupl((void **)&rrsig, (void **)&add);
- if (ret != KNOT_EOK) {
+ if (ret < 0) {
dbg_xfrin("Failed to merge changeset RRSIG to copy: %s"
".\n", knot_strerror(ret));
return KNOT_ERROR;
@@ -2535,10 +2711,14 @@ dbg_xfrin_exec_detail(
knot_rrset_rdata(chset->remove[i]))
== KNOT_RRTYPE_NSEC3))
{
+ dbg_xfrin_verb("Removed RRSet belongs to NSEC3 tree.\n");
is_nsec3 = 1;
}
// check if the old node is not the one we should use
+ dbg_xfrin_verb("Node:%p Owner: %p Node owner: %p\n",
+ node, knot_rrset_owner(chset->remove[i]),
+ knot_node_owner(node));
if (!node || knot_rrset_owner(chset->remove[i])
!= knot_node_owner(node)) {
if (is_nsec3) {
@@ -2559,10 +2739,12 @@ dbg_xfrin_exec_detail(
assert(node != NULL);
// first check if all RRSets should be removed
+ dbg_xfrin_verb("RRSet class to be removed=%u\n",
+ knot_rrset_class(chset->remove[i]));
if (knot_rrset_class(chset->remove[i]) == KNOT_CLASS_ANY) {
ret = xfrin_apply_remove_all_rrsets(
changes, node,
- knot_rrset_type(chset->remove[i]));
+ knot_rrset_type(chset->remove[i]), chset->flags);
} else if (knot_rrset_type(chset->remove[i])
== KNOT_RRTYPE_RRSIG) {
// this should work also for UPDATE
@@ -2573,7 +2755,8 @@ dbg_xfrin_exec_detail(
// this should work also for UPDATE
ret = xfrin_apply_remove_normal(changes,
chset->remove[i],
- node, &rrset);
+ node, &rrset,
+ chset->flags);
}
dbg_xfrin_detail("xfrin_apply_remove() ret = %d\n", ret);
@@ -2594,7 +2777,7 @@ static int xfrin_apply_add(knot_zone_contents_t *contents,
knot_changeset_t *chset,
knot_changes_t *changes)
{
- int ret = 0;
+ int ret = KNOT_EOK;
knot_node_t *node = NULL;
knot_rrset_t *rrset = NULL;
knot_rrset_t *rrsigs = NULL;
@@ -2661,22 +2844,27 @@ dbg_xfrin_exec_detail(
ret = xfrin_apply_add_rrsig(changes, chset->add[i],
node, &rrset, &rrsigs,
contents);
+ assert(ret != KNOT_EOK);
} else {
ret = xfrin_apply_add_normal(changes, chset->add[i],
- node, &rrset, contents);
+ node, &rrset, contents,
+ chset->flags);
+ assert(ret <= 3);
}
- assert(ret != KNOT_EOK);
+ // Not correct anymore, add_normal() returns KNOT_EOK if the
+ // changeset RR should be removed
+ //assert(ret != KNOT_EOK);
- dbg_xfrin_detail("xfrin_apply_..() returned %s, rrset: %p\n",
- knot_strerror(ret), rrset);
+ dbg_xfrin_detail("xfrin_apply_..() returned %d, rrset: %p\n",
+ ret, rrset);
if (ret > 0) {
if (ret == 1) {
// the ADD RRSet was used, i.e. it should be
// removed from the changeset and saved in the
// list of new RRSets
- ret = xfrin_changes_check_rrsets(
+ ret = knot_changes_rrsets_reserve(
&changes->new_rrsets,
&changes->new_rrsets_count,
&changes->new_rrsets_allocated, 1);
@@ -2694,7 +2882,7 @@ dbg_xfrin_exec_detail(
chset->add[i]);
// connect the RDATA to the list of new RDATA
- int res = xfrin_changes_check_rdata(
+ int res = knot_changes_rdata_reserve(
&changes->new_rdata,
&changes->new_rdata_types,
changes->new_rdata_count,
@@ -2703,7 +2891,7 @@ dbg_xfrin_exec_detail(
return res;
}
- xfrin_changes_add_rdata(changes->new_rdata,
+ knot_changes_add_rdata(changes->new_rdata,
changes->new_rdata_types,
&changes->new_rdata_count,
knot_rrset_get_rdata(chset->add[i]),
@@ -2721,6 +2909,11 @@ dbg_xfrin_exec_detail(
// stored in the list of new RDATA, because
// it is joined to the copy of RDATA, that is
// already stored there
+ } else if (ret == 3) {
+ // the RRSet was used and both RRSet and RDATA
+ // were properly stored. Just clear the place
+ // in the changeset
+ chset->add[i] = NULL;
} else {
assert(0);
}
@@ -2743,97 +2936,18 @@ static int xfrin_apply_replace_soa(knot_zone_contents_t *contents,
knot_node_t *node = knot_zone_contents_get_apex(contents);
assert(node != NULL);
- int ret = 0;
-
assert(node != NULL);
- // set the node copy as the apex of the contents
- contents->apex = node;
-
- // remove the SOA RRSet from the apex
- dbg_xfrin_verb("Removing SOA.\n");
- knot_rrset_t *rrset = knot_node_remove_rrset(node, KNOT_RRTYPE_SOA);
- assert(rrset != NULL);
-
- // add the old RRSet to the list of old RRSets
- ret = xfrin_changes_check_rrsets(&changes->old_rrsets,
- &changes->old_rrsets_count,
- &changes->old_rrsets_allocated, 1);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add old RRSet to list.\n");
- return ret;
- }
-
- // save also the SOA RDATA, because RDATA are not deleted with the
- // RRSet
- ret = xfrin_changes_check_rdata(&changes->old_rdata,
- &changes->old_rdata_types,
- changes->old_rdata_count,
- &changes->old_rdata_allocated, 1);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add old RDATA to list.\n");
- return ret;
- }
- // save the SOA to the new RRSet, so that it is deleted if the
- // apply fails
- ret = xfrin_changes_check_rrsets(&changes->new_rrsets,
- &changes->new_rrsets_count,
- &changes->new_rrsets_allocated, 1);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add new RRSet to list.\n");
- return ret;
+ int ret = xfrin_replace_rrset_in_node(node, chset->soa_to, changes,
+ contents);
+ if (ret == KNOT_EOK) {
+ // remove the SOA from the changeset, so it will not be deleted
+ // after successful apply
+ chset->soa_to = NULL;
}
- int count = knot_rrset_rdata_rr_count(rrset);
- count += knot_rrset_rdata_rr_count(chset->soa_to);
- // save the new SOA RDATA
- ret = xfrin_changes_check_rdata(&changes->new_rdata,
- &changes->new_rdata_types,
- changes->new_rdata_count,
- &changes->new_rdata_allocated, count);
- if (ret != KNOT_EOK) {
- dbg_xfrin("Failed to add new RDATA to list.\n");
- return ret;
- }
-
- changes->old_rrsets[changes->old_rrsets_count++] = rrset;
-
- /*! \todo Maybe check if the SOA does not have more RDATA? */
- dbg_xfrin_verb("Adding RDATA from old SOA to the list of old RDATA.\n");
- xfrin_changes_add_rdata(changes->old_rdata, changes->old_rdata_types,
- &changes->old_rdata_count, rrset->rdata,
- KNOT_RRTYPE_SOA);
-
- // store RRSIGs from the old SOA to the new SOA
- knot_rrset_set_rrsigs(chset->soa_to, knot_rrset_get_rrsigs(rrset));
-
- // insert the new SOA RRSet to the node
- dbg_xfrin_verb("Adding SOA.\n");
- //ret = knot_node_add_rrset(node, chset->soa_to, 0);
- ret = knot_zone_contents_add_rrset(contents, chset->soa_to, &node,
- KNOT_RRSET_DUPL_SKIP, 1);
-
- if (ret < 0) {
- dbg_xfrin("Failed to add RRSet to node.\n");
- return KNOT_ERROR;
- }
- assert(ret == 0);
-
- changes->new_rrsets[changes->new_rrsets_count++] = chset->soa_to;
-
- dbg_xfrin_verb("Adding RDATA from new SOA to the list of new RDATA.\n");
- xfrin_changes_add_rdata(changes->new_rdata,
- changes->new_rdata_types,
- &changes->new_rdata_count,
- knot_rrset_get_rdata(chset->soa_to),
- knot_rrset_type(chset->soa_to));
-
- // remove the SOA from the changeset, so it will not be deleted after
- // successful apply
- chset->soa_to = NULL;
-
- return KNOT_EOK;
+ return ret;
}
/*----------------------------------------------------------------------------*/
@@ -2885,7 +2999,7 @@ static void xfrin_mark_empty(knot_node_t *node, void *data)
if (knot_node_rrset_count(node) == 0
&& knot_node_children(node) == 0) {
- int ret = xfrin_changes_check_nodes(&changes->old_nodes,
+ int ret = knot_changes_nodes_reserve(&changes->old_nodes,
&changes->old_nodes_count,
&changes->old_nodes_allocated);
if (ret != KNOT_EOK) {
@@ -2919,7 +3033,7 @@ static void xfrin_mark_empty_nsec3(knot_node_t *node, void *data)
if (knot_node_rrset_count(node) == 0
&& knot_node_children(node) == 0) {
- int ret = xfrin_changes_check_nodes(&changes->old_nsec3,
+ int ret = knot_changes_nodes_reserve(&changes->old_nsec3,
&changes->old_nsec3_count,
&changes->old_nsec3_allocated);
if (ret != KNOT_EOK) {
@@ -3069,22 +3183,15 @@ static int xfrin_check_contents_copy(knot_zone_contents_t *old_contents)
/*----------------------------------------------------------------------------*/
-int xfrin_apply_changesets(knot_zone_t *zone,
- knot_changesets_t *chsets,
- knot_zone_contents_t **new_contents)
+int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
+ knot_zone_contents_t **new_contents,
+ knot_changes_t **changes)
{
- if (zone == NULL || chsets == NULL || chsets->count == 0
- || new_contents == NULL) {
+ if (old_contents == NULL || new_contents == NULL || changes == NULL) {
return KNOT_EINVAL;
}
- knot_zone_contents_t *old_contents = knot_zone_get_contents(zone);
- if (!old_contents) {
- dbg_xfrin("Cannot apply changesets to empty zone.\n");
- return KNOT_EINVAL;
- }
-
- dbg_xfrin("Applying changesets to zone...\n");
+ dbg_xfrin("Preparing zone copy...\n");
/*
* Ensure that the zone generation is set to 0.
@@ -3116,15 +3223,15 @@ int xfrin_apply_changesets(knot_zone_t *zone,
return ret;
}
- knot_changes_t *changes = (knot_changes_t *)malloc(
+ knot_changes_t *chgs = (knot_changes_t *)malloc(
sizeof(knot_changes_t));
- if (changes == NULL) {
+ if (chgs == NULL) {
dbg_xfrin("Failed to allocate structure for changes!\n");
- xfrin_rollback_update(old_contents, &contents_copy, &changes);
+ xfrin_rollback_update(old_contents, &contents_copy, &chgs);
return KNOT_ENOMEM;
}
- memset(changes, 0, sizeof(knot_changes_t));
+ memset(chgs, 0, sizeof(knot_changes_t));
/*!
* \todo Check if all nodes have their copy.
@@ -3132,7 +3239,7 @@ int xfrin_apply_changesets(knot_zone_t *zone,
ret = xfrin_check_contents_copy(old_contents);
if (ret != KNOT_EOK) {
dbg_xfrin("Contents copy check failed!\n");
- xfrin_rollback_update(old_contents, &contents_copy, &changes);
+ xfrin_rollback_update(old_contents, &contents_copy, &chgs);
return ret;
}
@@ -3145,30 +3252,23 @@ int xfrin_apply_changesets(knot_zone_t *zone,
dbg_xfrin("Switching ptrs pointing to old nodes to the new nodes.\n");
ret = xfrin_switch_nodes(contents_copy);
assert(knot_zone_contents_apex(contents_copy) != NULL);
+
+ *new_contents = contents_copy;
+ *changes = chgs;
+
+ return KNOT_EOK;
+}
- /*
- * Apply the changesets.
- */
- dbg_xfrin("Applying changesets.\n");
- dbg_xfrin_verb("Old contents apex: %p, new apex: %p\n",
- old_contents->apex, contents_copy->apex);
- for (int i = 0; i < chsets->count; ++i) {
- if ((ret = xfrin_apply_changeset(contents_copy, changes,
- &chsets->sets[i]))
- != KNOT_EOK) {
- xfrin_rollback_update(old_contents,
- &contents_copy, &changes);
- dbg_xfrin("Failed to apply changesets to zone: "
- "%s\n", knot_strerror(ret));
- return ret;
- }
- }
- assert(knot_zone_contents_apex(contents_copy) != NULL);
-
- /*!
- * \todo Test failure of IXFR.
- */
+/*----------------------------------------------------------------------------*/
+int xfrin_finalize_updated_zone(knot_zone_contents_t *contents_copy,
+ knot_changes_t *changes,
+ knot_zone_contents_t *old_contents)
+{
+ if (contents_copy == NULL || changes == NULL || old_contents == NULL) {
+ return KNOT_EINVAL;
+ }
+
/*
* Finalize the new zone contents:
* - delete empty nodes
@@ -3181,11 +3281,11 @@ int xfrin_apply_changesets(knot_zone_t *zone,
* Select and remove empty nodes from zone trees. Do not free them right
* away as they may be referenced by some domain names.
*/
- ret = xfrin_remove_empty_nodes(contents_copy, changes);
+ int ret = xfrin_remove_empty_nodes(contents_copy, changes);
if (ret != KNOT_EOK) {
dbg_xfrin("Failed to remove empty nodes: %s\n",
knot_strerror(ret));
- xfrin_rollback_update(old_contents, &contents_copy, &changes);
+// xfrin_rollback_update(old_contents, &contents_copy, &changes);
return ret;
}
@@ -3196,7 +3296,7 @@ int xfrin_apply_changesets(knot_zone_t *zone,
if (ret != KNOT_EOK) {
dbg_xfrin("Failed to finalize zone contents: %s\n",
knot_strerror(ret));
- xfrin_rollback_update(old_contents, &contents_copy, &changes);
+// xfrin_rollback_update(old_contents, &contents_copy, &changes);
return ret;
}
assert(knot_zone_contents_apex(contents_copy) != NULL);
@@ -3205,6 +3305,71 @@ int xfrin_apply_changesets(knot_zone_t *zone,
ret = knot_zone_contents_check_loops(contents_copy);
if (ret != KNOT_EOK) {
dbg_xfrin("CNAME loop check failed: %s\n", knot_strerror(ret));
+// xfrin_rollback_update(old_contents, &contents_copy, &changes);
+ return ret;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
+
+int xfrin_apply_changesets(knot_zone_t *zone,
+ knot_changesets_t *chsets,
+ knot_zone_contents_t **new_contents)
+{
+ if (zone == NULL || chsets == NULL || chsets->count == 0
+ || new_contents == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ knot_zone_contents_t *old_contents = knot_zone_get_contents(zone);
+ if (!old_contents) {
+ dbg_xfrin("Cannot apply changesets to empty zone.\n");
+ return KNOT_EINVAL;
+ }
+
+ dbg_xfrin("Applying changesets to zone...\n");
+
+ dbg_xfrin_verb("Creating shallow copy of the zone...\n");
+ knot_zone_contents_t *contents_copy = NULL;
+ knot_changes_t *changes = NULL;
+ int ret = xfrin_prepare_zone_copy(old_contents, &contents_copy,
+ &changes);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to prepare zone copy: %s\n",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ /*
+ * Apply the changesets.
+ */
+ dbg_xfrin("Applying changesets.\n");
+ dbg_xfrin_verb("Old contents apex: %p, new apex: %p\n",
+ old_contents->apex, contents_copy->apex);
+ for (int i = 0; i < chsets->count; ++i) {
+ if ((ret = xfrin_apply_changeset(contents_copy, changes,
+ &chsets->sets[i]))
+ != KNOT_EOK) {
+ xfrin_rollback_update(old_contents,
+ &contents_copy, &changes);
+ dbg_xfrin("Failed to apply changesets to zone: "
+ "%s\n", knot_strerror(ret));
+ return ret;
+ }
+ }
+ assert(knot_zone_contents_apex(contents_copy) != NULL);
+
+ /*!
+ * \todo Test failure of IXFR.
+ */
+
+ dbg_xfrin_verb("Finalizing updated zone...\n");
+ ret = xfrin_finalize_updated_zone(contents_copy, changes, old_contents);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to finalize updated zone: %s\n",
+ knot_strerror(ret));
xfrin_rollback_update(old_contents, &contents_copy, &changes);
return ret;
}
@@ -3237,7 +3402,7 @@ int xfrin_switch_zone(knot_zone_t *zone,
old, (old) ? old->apex : NULL, new_contents->apex);
// switch pointers in domain names, now only the new zone is used
- if (transfer_type == XFR_TYPE_IIN) {
+ if (transfer_type == XFR_TYPE_IIN || transfer_type == XFR_TYPE_UPDATE) {
// Traverse also the dname table and change the node pointers
// in dnames
int ret = knot_zone_contents_dname_table_apply(
diff --git a/src/libknot/updates/xfr-in.h b/src/libknot/updates/xfr-in.h
index a762b81..c65e708 100644
--- a/src/libknot/updates/xfr-in.h
+++ b/src/libknot/updates/xfr-in.h
@@ -185,6 +185,14 @@ int xfrin_apply_changesets(knot_zone_t *zone,
knot_changesets_t *chsets,
knot_zone_contents_t **new_contents);
+int xfrin_prepare_zone_copy(knot_zone_contents_t *old_contents,
+ knot_zone_contents_t **new_contents,
+ knot_changes_t **changes);
+
+int xfrin_finalize_updated_zone(knot_zone_contents_t *contents_copy,
+ knot_changes_t *changes,
+ knot_zone_contents_t *old_contents);
+
int xfrin_switch_zone(knot_zone_t *zone,
knot_zone_contents_t *new_contents,
int deep_free);
@@ -195,6 +203,18 @@ void xfrin_rollback_update(knot_zone_contents_t *old_contents,
knot_zone_contents_t **new_contents,
knot_changes_t **changes);
+int xfrin_copy_rrset(knot_node_t *node, knot_rr_type_t type,
+ knot_rrset_t **rrset, knot_changes_t *changes,
+ int save_new);
+
+int xfrin_copy_old_rrset(knot_rrset_t *old, knot_rrset_t **copy,
+ knot_changes_t *changes, int save_new);
+
+int xfrin_replace_rrset_in_node(knot_node_t *node,
+ knot_rrset_t *rrset_new,
+ knot_changes_t *changes,
+ knot_zone_contents_t *contents);
+
#endif /* _KNOTXFR_IN_H_ */
/*! @} */
diff --git a/src/libknot/util/debug.h b/src/libknot/util/debug.h
index 731fed8..abe335b 100644
--- a/src/libknot/util/debug.h
+++ b/src/libknot/util/debug.h
@@ -68,6 +68,7 @@
#ifdef KNOT_XFR_DEBUG
#define KNOT_XFRIN_DEBUG
#define KNOT_TSIG_DEBUG
+ #define KNOT_DDNS_DEBUG
#endif
/* KNOT_DNAME_DEBUG -- in configure.ac */
@@ -758,27 +759,33 @@ void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone);
#ifdef DEBUG_ENABLE_BRIEF
#define dbg_ddns(msg...) log_msg(LOG_ZONE, LOG_DEBUG, msg)
#define dbg_ddns_hex(data, len) hex_log(LOG_ZONE, (data), (len))
+#define dbg_ddns_exec(cmds) do { cmds } while (0)
#else
#define dbg_ddns(msg...)
#define dbg_ddns_hex(data, len)
+#define dbg_ddns_exec(cmds)
#endif
/* Verbose messages. */
#ifdef DEBUG_ENABLE_VERBOSE
#define dbg_ddns_verb(msg...) log_msg(LOG_ZONE, LOG_DEBUG, msg)
#define dbg_ddns_hex_verb(data, len) hex_log(LOG_ZONE, (data), (len))
+#define dbg_ddns_exec_verb(cmds) do { cmds } while (0)
#else
#define dbg_ddns_verb(msg...)
#define dbg_ddns_hex_verb(data, len)
+#define dbg_ddns_exec_verb(cmds)
#endif
/* Detail messages. */
#ifdef DEBUG_ENABLE_DETAILS
#define dbg_ddns_detail(msg...) log_msg(LOG_ZONE, LOG_DEBUG, msg)
#define dbg_ddns_hex_detail(data, len) hex_log(LOG_ZONE, (data), (len))
+#define dbg_ddns_exec_detail(cmds) do { cmds } while (0)
#else
#define dbg_ddns_detail(msg...)
#define dbg_ddns_hex_detail(data, len)
+#define dbg_ddns_exec_detail(cmds)
#endif
/* No messages. */
@@ -786,9 +793,12 @@ void knot_zone_contents_dump(knot_zone_contents_t *zone, char loaded_zone);
#define dbg_ddns(msg...)
#define dbg_ddns_hex(data, len)
#define dbg_ddns_verb(msg...)
+#define dbg_ddns_exec(cmds)
#define dbg_ddns_hex_verb(data, len)
+#define dbg_ddns_exec_verb(cmds)
#define dbg_ddns_detail(msg...)
#define dbg_ddns_hex_detail(data, len)
+#define dbg_ddns_exec_detail(cmds)
#endif
#ifdef KNOT_TSIG_DEBUG
diff --git a/src/libknot/util/descriptor.c b/src/libknot/util/descriptor.c
index 1588a2e..8cab6b4 100644
--- a/src/libknot/util/descriptor.c
+++ b/src/libknot/util/descriptor.c
@@ -68,6 +68,15 @@ static knot_lookup_table_t dns_rrclasses[] = {
{ 0, NULL }
};
+/*! \brief DS digest lengths. */
+enum knot_ds_algorithm_len
+{
+ KNOT_DS_DIGEST_LEN_SHA1 = 20, /* 20B - RFC 3658 */
+ KNOT_DS_DIGEST_LEN_SHA256 = 32, /* 32B - RFC 4509 */
+ KNOT_DS_DIGEST_LEN_GOST = 32, /* 32B - RFC 5933 */
+ KNOT_DS_DIGEST_LEN_SHA384 = 48 /* 48B - RFC 6605 */
+};
+
/*! \brief RR type descriptors. */
static knot_rrtype_descriptor_t
knot_rrtype_descriptors[KNOT_RRTYPE_DESCRIPTORS_LENGTH] = {
@@ -572,5 +581,21 @@ int knot_rrtype_is_metatype(uint16_t type)
|| type == KNOT_RRTYPE_OPT);
}
+size_t knot_ds_digest_length(uint8_t algorithm)
+{
+ switch (algorithm) {
+ case KNOT_DS_ALG_SHA1:
+ return KNOT_DS_DIGEST_LEN_SHA1;
+ case KNOT_DS_ALG_SHA256:
+ return KNOT_DS_DIGEST_LEN_SHA256;
+ case KNOT_DS_ALG_GOST:
+ return KNOT_DS_DIGEST_LEN_GOST;
+ case KNOT_DS_ALG_SHA384:
+ return KNOT_DS_DIGEST_LEN_SHA384;
+ default:
+ return 0;
+ }
+}
+
/*! @} */
diff --git a/src/libknot/util/descriptor.h b/src/libknot/util/descriptor.h
index 6364e5b..23b5d13 100644
--- a/src/libknot/util/descriptor.h
+++ b/src/libknot/util/descriptor.h
@@ -237,6 +237,41 @@ typedef enum knot_rdata_zoneformat knot_rdata_zoneformat_t;
/*! \brief Enum containing wireformat codes. */
typedef enum knot_rdatawireformat knot_rdata_wireformat_t;
+/*! \brief Constants for DNSSEC algorithm types.
+ *
+ * Source: http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xml
+ */
+enum knot_dnssec_algorithm
+{
+ KNOT_DNSSEC_ALG_RSAMD5 = 1,
+ KNOT_DNSSEC_ALG_DH = 2,
+ KNOT_DNSSEC_ALG_DSA = 3,
+ KNOT_DNSSEC_ALG_RSASHA1 = 5,
+ KNOT_DNSSEC_ALG_DSA_NSEC3_SHA1 = 6,
+ KNOT_DNSSEC_ALG_RSASHA1_NSEC3_SHA1 = 7,
+ KNOT_DNSSEC_ALG_RSASHA256 = 8,
+ KNOT_DNSSEC_ALG_RSASHA512 = 10,
+ KNOT_DNSSEC_ALG_ECC_GOST = 12,
+ KNOT_DNSSEC_ALG_ECDSAP256SHA256 = 13,
+ KNOT_DNSSEC_ALG_ECDSAP384SHA384 = 14
+};
+
+typedef enum knot_dnssec_algorithm knot_dnssec_algorithm_t;
+
+/*! \brief Constants for DNSSEC algorithm types.
+ *
+ * Source: http://www.iana.org/assignments/ds-rr-types/ds-rr-types.xml
+ */
+enum knot_ds_algorithm
+{
+ KNOT_DS_ALG_SHA1 = 1, /* 20B - RFC 3658 */
+ KNOT_DS_ALG_SHA256 = 2, /* 32B - RFC 4509 */
+ KNOT_DS_ALG_GOST = 3, /* 32B - RFC 5933 */
+ KNOT_DS_ALG_SHA384 = 4 /* 48B - RFC 6605 */
+};
+
+typedef enum knot_ds_algorithm knot_ds_algorithm_t;
+
/*! \brief Structure holding RR descriptor. */
struct knot_rrtype_descriptor {
uint16_t type; /*!< RR type */
@@ -328,6 +363,8 @@ size_t knot_wireformat_size(unsigned int wire_type);
int knot_rrtype_is_metatype(uint16_t type);
+size_t knot_ds_digest_length(uint8_t algorithm);
+
#endif /* _KNOT_DESCRIPTOR_H_ */
/*! @} */
diff --git a/src/libknot/zone/zone-contents.c b/src/libknot/zone/zone-contents.c
index 61e9e51..bdc268b 100644
--- a/src/libknot/zone/zone-contents.c
+++ b/src/libknot/zone/zone-contents.c
@@ -408,6 +408,40 @@ static void knot_zone_contents_adjust_rdata_item(knot_rdata_t *rdata,
}
/*----------------------------------------------------------------------------*/
+
+static int knot_zone_contents_adjust_rdata(knot_rdata_t *rdata,
+ knot_rrtype_descriptor_t *desc,
+ knot_zone_contents_t *zone,
+ knot_node_t *node)
+{
+ for (int i = 0; i < rdata->count; ++i) {
+ if (desc->wireformat[i]
+ == KNOT_RDATA_WF_COMPRESSED_DNAME
+ || desc->wireformat[i]
+ == KNOT_RDATA_WF_UNCOMPRESSED_DNAME
+ || desc->wireformat[i]
+ == KNOT_RDATA_WF_LITERAL_DNAME) {
+ dbg_zone_detail("Adjusting domain name at position %d"
+ " of the RDATA\n", i);
+ knot_zone_contents_adjust_rdata_item(rdata, zone, node,
+ i);
+ }
+ }
+
+ /*
+ * DS digest length check (#2050).
+ */
+ int ret;
+ if (desc->type == KNOT_RRTYPE_DS
+ && (ret = knot_rdata_ds_check(rdata)) != KNOT_EOK) {
+ dbg_zone("DS RDATA check failed: %s\n", knot_strerror(ret));
+ return KNOT_EMALF;
+ }
+
+ return KNOT_EOK;
+}
+
+/*----------------------------------------------------------------------------*/
/*!
* \brief Adjusts all RDATA in the given RRSet by replacing domain names by ones
* present in the zone.
@@ -419,9 +453,9 @@ static void knot_zone_contents_adjust_rdata_item(knot_rdata_t *rdata,
* \param rrset RRSet to adjust RDATA in.
* \param zone Zone to which the RRSet belongs.
*/
-static void knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset,
- knot_zone_contents_t *zone,
- knot_node_t *node)
+static int knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset,
+ knot_zone_contents_t *zone,
+ knot_node_t *node)
{
uint16_t type = knot_rrset_type(rrset);
@@ -433,49 +467,37 @@ static void knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset,
knot_rdata_t *rdata = rdata_first;
if (rdata == NULL) {
- return;
+ return KNOT_EOK;
}
+ int i = 1;
+ int ret;
while (rdata->next != rdata_first) {
- for (int i = 0; i < rdata->count; ++i) {
- if (desc->wireformat[i]
- == KNOT_RDATA_WF_COMPRESSED_DNAME
- || desc->wireformat[i]
- == KNOT_RDATA_WF_UNCOMPRESSED_DNAME
- || desc->wireformat[i]
- == KNOT_RDATA_WF_LITERAL_DNAME) {
- dbg_zone("Adjusting domain name at "
- "position %d of RDATA of record with owner "
- "%s and type %s.\n",
- i, rrset->owner->name,
- knot_rrtype_to_string(type));
-
- knot_zone_contents_adjust_rdata_item(rdata,
- zone, node,
- i);
- }
+dbg_zone_exec_detail(
+ char *name = knot_dname_to_str(knot_rrset_owner(rrset));
+ dbg_zone_detail("Adjusting domain name at %d. RDATA of RRSet "
+ "with owner %s and type %s.\n", i, name,
+ knot_rrtype_to_string(type));
+ free(name);
+);
+ ret = knot_zone_contents_adjust_rdata(rdata, desc,
+ zone, node);
+ if (ret != KNOT_EOK) {
+ return ret;
}
- rdata = rdata->next;
- }
- for (int i = 0; i < rdata->count; ++i) {
- if (desc->wireformat[i]
- == KNOT_RDATA_WF_COMPRESSED_DNAME
- || desc->wireformat[i]
- == KNOT_RDATA_WF_UNCOMPRESSED_DNAME
- || desc->wireformat[i]
- == KNOT_RDATA_WF_LITERAL_DNAME) {
- dbg_zone("Adjusting domain name at "
- "position %d of RDATA of record with owner "
- "%s and type %s.\n",
- i, rrset->owner->name,
- knot_rrtype_to_string(type));
-
- knot_zone_contents_adjust_rdata_item(rdata, zone, node,
- i);
- }
+ rdata = rdata->next;
+ ++i;
}
+dbg_zone_exec_detail(
+ char *name = knot_dname_to_str(knot_rrset_owner(rrset));
+ dbg_zone_detail("Adjusting domain name at %d. RDATA of RRSet "
+ "with owner %s and type %s.\n", i, name,
+ knot_rrtype_to_string(type));
+ free(name);
+);
+ return knot_zone_contents_adjust_rdata(rdata, desc, zone, node);
}
/*----------------------------------------------------------------------------*/
@@ -489,28 +511,37 @@ static void knot_zone_contents_adjust_rdata_in_rrset(knot_rrset_t *rrset,
* \param node Zone node to adjust the RRSets in.
* \param zone Zone to which the node belongs.
*/
-static void knot_zone_contents_adjust_rrsets(knot_node_t *node,
- knot_zone_contents_t *zone)
+static int knot_zone_contents_adjust_rrsets(knot_node_t *node,
+ knot_zone_contents_t *zone)
{
knot_rrset_t **rrsets = knot_node_get_rrsets(node);
short count = knot_node_rrset_count(node);
assert(count == 0 || rrsets != NULL);
+ int ret = KNOT_EOK;
for (int r = 0; r < count; ++r) {
assert(rrsets[r] != NULL);
dbg_zone("Adjusting next RRSet.\n");
- knot_zone_contents_adjust_rdata_in_rrset(rrsets[r], zone,
- node);
+ ret = knot_zone_contents_adjust_rdata_in_rrset(rrsets[r], zone,
+ node);
+
knot_rrset_t *rrsigs = rrsets[r]->rrsigs;
- if (rrsigs != NULL) {
+ if (ret == KNOT_EOK && rrsigs != NULL) {
dbg_zone("Adjusting next RRSIGs.\n");
- knot_zone_contents_adjust_rdata_in_rrset(rrsigs, zone,
- node);
+ ret = knot_zone_contents_adjust_rdata_in_rrset(rrsigs,
+ zone,
+ node);
+ }
+
+ if (ret != KNOT_EOK) {
+ break;
}
}
free(rrsets);
+
+ return ret;
}
/*----------------------------------------------------------------------------*/
/*!
@@ -529,12 +560,15 @@ static void knot_zone_contents_adjust_rrsets(knot_node_t *node,
* in the inserting function, though that may not be always used (e.g.
* old changeset processing).
*/
-static void knot_zone_contents_adjust_node(knot_node_t *node,
- knot_zone_contents_t *zone)
+static int knot_zone_contents_adjust_node(knot_node_t *node,
+ knot_zone_contents_t *zone)
{
// adjust domain names in RDATA
/*! \note Enabled again after a LONG time. Should test thoroughly. */
- knot_zone_contents_adjust_rrsets(node, zone);
+ int ret = knot_zone_contents_adjust_rrsets(node, zone);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
// assure that owner has proper node
if (knot_dname_node(knot_node_owner(node)) == NULL) {
@@ -566,6 +600,8 @@ static void knot_zone_contents_adjust_node(knot_node_t *node,
knot_node_is_deleg_point(node) ? "yes" : "no");
dbg_zone_detail("Non-authoritative: %s\n",
knot_node_is_non_auth(node) ? "yes" : "no");
+
+ return KNOT_EOK;
}
/*----------------------------------------------------------------------------*/
@@ -621,7 +657,12 @@ dbg_zone_exec_verb(
* 2) Do other adjusting (flags, closest enclosers, wildcard children,
* etc.).
*/
- knot_zone_contents_adjust_node(node, zone);
+ ret = knot_zone_contents_adjust_node(node, zone);
+ if (ret != KNOT_EOK) {
+ dbg_xfrin("Failed to adjust node: %s\n", knot_strerror(ret));
+ args->err = ret;
+ return;
+ }
}
/*----------------------------------------------------------------------------*/
@@ -816,10 +857,10 @@ dbg_zone_exec_verb(
dbg_zone("\n");
char *name_b32 = NULL;
- size_t size = base32hex_encode_alloc((char *)hashed_name, hash_size,
- &name_b32);
+ int32_t b32_ret = base32hex_encode_alloc(hashed_name, hash_size,
+ (uint8_t **)(&name_b32));
- if (size == 0) {
+ if (b32_ret <= 0) {
char *n = knot_dname_to_str(name);
dbg_zone("Error while encoding hashed name %s to base32.\n", n);
free(n);
@@ -829,6 +870,8 @@ dbg_zone_exec_verb(
return KNOT_ECRYPTO;
}
+ size_t size = b32_ret;
+
assert(name_b32 != NULL);
free(hashed_name);
@@ -1316,22 +1359,21 @@ dbg_zone_exec_detail(
return ret;
}
- ret = knot_zone_tree_insert(zone->nodes, node);
- if (ret != KNOT_EOK) {
- dbg_zone("Failed to insert node into zone tree.\n");
- return ret;
- }
-
if (use_domain_table) {
ret = knot_zone_contents_dnames_from_node_to_table(
zone->dname_table, node);
if (ret != KNOT_EOK) {
- /*! \todo Remove node from the tree and hash table.*/
dbg_zone("Failed to add dnames into table.\n");
return ret;
}
}
+ ret = knot_zone_tree_insert(zone->nodes, node);
+ if (ret != KNOT_EOK) {
+ dbg_zone("Failed to insert node into zone tree.\n");
+ return ret;
+ }
+
#ifdef USE_HASH_TABLE
// add the node also to the hash table if authoritative, or deleg. point
if (zone->table != NULL
@@ -1339,7 +1381,10 @@ dbg_zone_exec_detail(
(const char *)node->owner->name,
node->owner->size, (void *)node) != 0) {
dbg_zone("Error inserting node into hash table!\n");
- /*! \todo Remove the node from the tree. */
+ knot_zone_tree_node_t *removed;
+ (void)knot_zone_tree_remove(zone->nodes, knot_node_owner(node),
+ &removed);
+ assert(removed->node == node);
return KNOT_EHASH;
}
#endif
@@ -2119,7 +2164,7 @@ const knot_node_t *knot_zone_contents_find_previous_nsec3(
static void knot_zone_contents_left_chop(char *name, size_t *size)
{
- short label_size = name[0];
+ short label_size = (unsigned char)name[0];
memmove(name, name + label_size + 1, *size -label_size - 1);
*size = *size - label_size - 1;
diff --git a/src/libknot/zone/zone-diff.c b/src/libknot/zone/zone-diff.c
index 734c7c2..a4efff9 100644
--- a/src/libknot/zone/zone-diff.c
+++ b/src/libknot/zone/zone-diff.c
@@ -163,11 +163,11 @@ static int knot_zone_diff_changeset_add_rrset(knot_changeset_t *changeset,
}
if (rrset_copy->rrsigs != NULL) {
knot_rrset_deep_free(&rrset_copy->rrsigs, 1, 1, 1);
- }
+ }
assert(knot_rrset_rrsigs(rrset_copy) == NULL);
ret = knot_changeset_add_new_rr(changeset, rrset_copy,
- XFRIN_CHANGESET_ADD);
+ KNOT_CHANGESET_ADD);
if (ret != KNOT_EOK) {
/* We have to free the copy now! */
knot_rrset_deep_free(&rrset_copy, 1, 1, 1);
@@ -193,6 +193,7 @@ static int knot_zone_diff_changeset_remove_rrset(knot_changeset_t *changeset,
}
if (knot_rrset_rdata_rr_count(rrset) == 0) {
+ /* RDATA are the same, however*/
dbg_zonediff_detail("zone_diff: Nothing to remove.\n");
return KNOT_EOK;
}
@@ -209,11 +210,12 @@ static int knot_zone_diff_changeset_remove_rrset(knot_changeset_t *changeset,
}
if (rrset_copy->rrsigs != NULL) {
knot_rrset_deep_free(&rrset_copy->rrsigs, 1, 1, 1);
- }
+ }
+
assert(knot_rrset_rrsigs(rrset_copy) == NULL);
ret = knot_changeset_add_new_rr(changeset, rrset_copy,
- XFRIN_CHANGESET_REMOVE);
+ KNOT_CHANGESET_REMOVE);
if (ret != KNOT_EOK) {
/* We have to free the copy now. */
knot_rrset_deep_free(&rrset_copy, 1, 1, 1);
@@ -251,6 +253,19 @@ static int knot_zone_diff_add_node(const knot_node_t *node,
free(rrsets);
return ret;
}
+
+ if (knot_rrset_rrsigs(rrsets[i])) {
+ /* Add RRSIGs of the new node. */
+ ret = knot_zone_diff_changeset_add_rrset(changeset,
+ knot_rrset_rrsigs(rrsets[i]));
+ if (ret != KNOT_EOK) {
+ dbg_zonediff("zone_diff: add_node: Cannot "
+ "add RRSIG (%s).\n",
+ knot_strerror(ret));
+ free(rrsets);
+ return ret;
+ }
+ }
}
free(rrsets);
@@ -292,6 +307,18 @@ dbg_zonediff_exec_detail(
free(rrsets);
return ret;
}
+ if (knot_rrset_rrsigs(rrsets[i])) {
+ /* Remove RRSIGs of the old node. */
+ ret = knot_zone_diff_changeset_remove_rrset(changeset,
+ knot_rrset_rrsigs(rrsets[i]));
+ if (ret != KNOT_EOK) {
+ dbg_zonediff("zone_diff: remove_node: Cannot "
+ "remove RRSIG (%s).\n",
+ knot_strerror(ret));
+ free(rrsets);
+ return ret;
+ }
+ }
}
free(rrsets);
@@ -356,8 +383,8 @@ static int knot_zone_diff_rdata_return_changes(const knot_rrset_t *rrset1,
* because the list was traversed - there's no match.
*/
dbg_zonediff("zone_diff: diff_rdata: "
- "No match for RR (type=%s owner=%s).\n",
- knot_rrtype_to_string(knot_rrset_type(rrset1)),
+ "No match for RR (type=%u owner=%s).\n",
+ knot_rrset_type(rrset1),
knot_dname_to_str(rrset1->owner));
/* Make a copy of tmp_rdata. */
knot_rdata_t *tmp_rdata_copy =
@@ -457,7 +484,7 @@ static int knot_zone_diff_rdata(const knot_rrset_t *rrset1,
}
int ret = knot_zone_diff_changeset_remove_rrset(changeset,
- to_remove);
+ to_remove);
if (ret != KNOT_EOK) {
knot_rrset_deep_free(&to_remove, 1, 1, 1);
dbg_zonediff("zone_diff: diff_rdata: Could not remove RRs. "
@@ -587,12 +614,13 @@ static int knot_zone_diff_rrsets(const knot_rrset_t *rrset1,
int ret = knot_zone_diff_rdata(knot_rrset_rrsigs(rrset1),
knot_rrset_rrsigs(rrset2), changeset);
if (ret != KNOT_EOK) {
- dbg_zonediff("zone_diff: diff_rrsets (%s:%s): Failed to diff RRSIGs. "
- "They were: %p %p. (%s).\n",
- knot_dname_to_str(rrset1->owner),
- knot_rrtype_to_string(rrset1->type),
- rrset1->rrsigs,
- rrset2->rrsigs, knot_strerror(ret));
+ dbg_zonediff("zone_diff: diff_rrsets (%s:%s): "
+ "Failed to diff RRSIGs. "
+ "They were: %p %p. (%s).\n",
+ knot_dname_to_str(rrset1->owner),
+ knot_rrtype_to_string(rrset1->type),
+ rrset1->rrsigs,
+ rrset2->rrsigs, knot_strerror(ret));
}
/* RRs (=rdata) have to be cross-compared, unfortunalely. */
@@ -706,6 +734,20 @@ static void knot_zone_diff_node(knot_node_t *node, void *data)
free(rrsets);
return;
}
+
+ /* Remove RRSet's RRSIGs as well. */
+ if (knot_rrset_rrsigs(rrset)) {
+ ret = knot_zone_diff_changeset_remove_rrset(
+ param->changeset,
+ knot_rrset_rrsigs(rrset));
+ if (ret != KNOT_EOK) {
+ dbg_zonediff("zone_diff: diff_node+: "
+ "Failed to remove RRSIGs.\n");
+ param->ret = ret;
+ free(rrsets);
+ return;
+ }
+ }
} else {
dbg_zonediff("zone_diff: diff_node: There is a counterpart "
"for RRSet of type %s in second tree.\n",
@@ -790,6 +832,18 @@ static void knot_zone_diff_node(knot_node_t *node, void *data)
free(rrsets);
return;
}
+ if (knot_rrset_rrsigs(rrset)) {
+ int ret = knot_zone_diff_changeset_add_rrset(
+ param->changeset,
+ knot_rrset_rrsigs(rrset));
+ if (ret != KNOT_EOK) {
+ dbg_zonediff("zone_diff: diff_node: "
+ "Failed to add RRSIGs.\n");
+ param->ret = ret;
+ free(rrsets);
+ return;
+ }
+ }
} else {
/* Already handled. */
;
@@ -989,7 +1043,10 @@ int knot_zone_diff_create_changesets(const knot_zone_contents_t *z1,
return KNOT_EINVAL;
}
/* Create changesets. */
- int ret = knot_changeset_allocate(changesets);
+ /* Setting type to IXFR - that's the default, DDNS triggers special
+ * processing when applied. See #2110 and #2111.
+ */
+ int ret = knot_changeset_allocate(changesets, KNOT_CHANGESET_TYPE_IXFR);
if (ret != KNOT_EOK) {
dbg_zonediff("zone_diff: create_changesets: "
"Could not allocate changesets."
@@ -1014,14 +1071,13 @@ int knot_zone_diff_create_changesets(const knot_zone_contents_t *z1,
dbg_zonediff_exec_detail(
knot_zone_diff_dump_changeset((*changesets)->sets);
);
-
return KNOT_EOK;
}
-/* Mostly just for testing. We only shall diff zones in memory later. */
+///* Mostly just for testing. We only shall diff zones in memory later. */
//int knot_zone_diff_zones(const char *zonefile1, const char *zonefile2)
//{
- /* Compile test zones. */
+// /* Compile test zones. */
// int ret = zone_read("example.com.", "/home/jan/test/testzone1", "tmpzone1.db", 0);
// assert(ret == KNOT_EOK);
// ret = zone_read("example.com.", "/home/jan/test/testzone2", "tmpzone2.db", 0);
@@ -1040,29 +1096,29 @@ dbg_zonediff_exec_detail(
// assert(knot_zone_contents_diff(z1->contents, z2->contents,
// changeset) == KNOT_EOK);
// dbg_zonediff("Changeset created: From=%d to=%d.\n", changeset.serial_from,
-// changeset.serial_to);
-//// knot_changesets_t chngsets;
-//// chngsets->sets = malloc(sizeof(knot_changeset_t));
-//// chngsets->sets[0] = changeset;
-//// chngsets->count = 1;
-//// chngsets->allocated = 1;
-//// knot_zone_contents_t *new_zone = NULL;
-//// ret = xfrin_apply_changesets(z1, chngsets, &new_zone);
-//// if (ret != KNOT_EOK) {
-//// dbg_zonediff("Application of changesets failed. (%s)\n",
-//// knot_strerror(ret));
-//// }
+// changeset.serial_to);
+// // knot_changesets_t chngsets;
+// // chngsets->sets = malloc(sizeof(knot_changeset_t));
+// // chngsets->sets[0] = changeset;
+// // chngsets->count = 1;
+// // chngsets->allocated = 1;
+// // knot_zone_contents_t *new_zone = NULL;
+// // ret = xfrin_apply_changesets(z1, chngsets, &new_zone);
+// // if (ret != KNOT_EOK) {
+// // dbg_zonediff("Application of changesets failed. (%s)\n",
+// // knot_strerror(ret));
+// // }
-//// assert(new_zone);
+// // assert(new_zone);
// /* Dump creted zone. */
-//// FILE *f = fopen("testovani", "w");
-//// zone_dump_text(new_zone, f);
+// // FILE *f = fopen("testovani", "w");
+// // zone_dump_text(new_zone, f);
// knot_zone_deep_free(&z2, 0);
// knot_zone_deep_free(&z1, 0);
-//// knot_zone_contents_deep_free(&new_zone, 1);
-//// knot_zone_free(&z1);
+// // knot_zone_contents_deep_free(&new_zone, 1);
+// // knot_zone_free(&z1);
// knot_free_changeset(&changeset);
// exit(0);
diff --git a/src/tests/common/base32hex_tests.c b/src/tests/common/base32hex_tests.c
new file mode 100644
index 0000000..cdfec69
--- /dev/null
+++ b/src/tests/common/base32hex_tests.c
@@ -0,0 +1,166 @@
+/* Copyright (C) 2011 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 "tests/common/base32hex_tests.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "common/base32hex.h"
+
+#define BUF_LEN 256
+
+static int base32hex_tests_count(int argc, char *argv[]);
+static int base32hex_tests_run(int argc, char *argv[]);
+
+unit_api base32hex_tests_api = {
+ "Base32hex encoding",
+ &base32hex_tests_count,
+ &base32hex_tests_run
+};
+
+static int base32hex_tests_count(int argc, char *argv[])
+{
+ return 42;
+}
+
+static int base32hex_tests_run(int argc, char *argv[])
+{
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN];
+ uint32_t in_len, ref_len;
+
+ // 1. test vector -> ENC -> DEC
+ strcpy((char *)in, "");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "1. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "1. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+
+ // 2. test vector -> ENC -> DEC
+ strcpy((char *)in, "f");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CO======");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "2. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "2. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+
+ // 3. test vector -> ENC -> DEC
+ strcpy((char *)in, "fo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNG====");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "3. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "3. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+
+ // 4. test vector -> ENC -> DEC
+ strcpy((char *)in, "foo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMU===");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "4. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "4. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+
+ // 5. test vector -> ENC -> DEC
+ strcpy((char *)in, "foob");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOG=");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "5. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "5. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+
+ // 6. test vector -> ENC -> DEC
+ strcpy((char *)in, "fooba");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOJ1");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "6. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "6. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+
+ // 7. test vector -> ENC -> DEC
+ strcpy((char *)in, "foobar");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "CPNMUOJ1E8======");
+ ref_len = strlen((char *)ref);
+ ret = base32hex_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "7. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ ret = base32hex_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "7. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+
+ // Bad paddings
+ ret = base32hex_decode((uint8_t *)"AAAAAA==", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 2");
+ ret = base32hex_decode((uint8_t *)"AAA=====", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 5");
+ ret = base32hex_decode((uint8_t *)"A======", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 7");
+ ret = base32hex_decode((uint8_t *)"=======", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 8");
+
+ // Bad data length
+ ret = base32hex_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 1");
+ ret = base32hex_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 2");
+ ret = base32hex_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 3");
+ ret = base32hex_decode((uint8_t *)"AAAA", 4, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 4");
+ ret = base32hex_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 5");
+ ret = base32hex_decode((uint8_t *)"AAAAAA", 6, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 6");
+ ret = base32hex_decode((uint8_t *)"AAAAAAA", 7, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 7");
+ ret = base32hex_decode((uint8_t *)"AAAAAAAAA", 9, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 9");
+
+ // Bad data character
+ ret = base32hex_decode((uint8_t *)"AAAAAAA$", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad data character dollar");
+ ret = base32hex_decode((uint8_t *)"AAAAAAA ", 8, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad data character space");
+
+ return 0;
+}
diff --git a/src/tests/common/base32hex_tests.h b/src/tests/common/base32hex_tests.h
new file mode 100644
index 0000000..57d4153
--- /dev/null
+++ b/src/tests/common/base32hex_tests.h
@@ -0,0 +1,25 @@
+/* Copyright (C) 2011 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/>.
+ */
+
+#ifndef _KNOTD_BASE32HEX_TESTS_H_
+#define _KNOTD_BASE32HEX_TESTS_H_
+
+#include "common/libtap/tap_unit.h"
+
+/* Unit API. */
+unit_api base32hex_tests_api;
+
+#endif /* _KNOTD_BASE32HEX_TESTS_H_ */
diff --git a/src/tests/common/base64_tests.c b/src/tests/common/base64_tests.c
new file mode 100644
index 0000000..9453c98
--- /dev/null
+++ b/src/tests/common/base64_tests.c
@@ -0,0 +1,154 @@
+/* Copyright (C) 2011 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 "tests/common/base64_tests.h"
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "common/base64.h"
+
+#define BUF_LEN 256
+
+static int base64_tests_count(int argc, char *argv[]);
+static int base64_tests_run(int argc, char *argv[]);
+
+unit_api base64_tests_api = {
+ "Base64 encoding",
+ &base64_tests_count,
+ &base64_tests_run
+};
+
+static int base64_tests_count(int argc, char *argv[])
+{
+ return 36;
+}
+
+static int base64_tests_run(int argc, char *argv[])
+{
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN];
+ uint32_t in_len, ref_len;
+
+ // 1. test vector -> ENC -> DEC
+ strcpy((char *)in, "");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "1. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "1. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+
+ // 2. test vector -> ENC -> DEC
+ strcpy((char *)in, "f");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zg==");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "2. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "2. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+
+ // 3. test vector -> ENC -> DEC
+ strcpy((char *)in, "fo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm8=");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "3. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "3. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+
+ // 4. test vector -> ENC -> DEC
+ strcpy((char *)in, "foo");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9v");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "4. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "4. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+
+ // 5. test vector -> ENC -> DEC
+ strcpy((char *)in, "foob");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYg==");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "5. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "5. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+
+ // 6. test vector -> ENC -> DEC
+ strcpy((char *)in, "fooba");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYmE=");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "6. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "6. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+
+ // 7. test vector -> ENC -> DEC
+ strcpy((char *)in, "foobar");
+ in_len = strlen((char *)in);
+ strcpy((char *)ref, "Zm9vYmFy");
+ ref_len = strlen((char *)ref);
+ ret = base64_encode(in, in_len, out, BUF_LEN);
+ cmp_ok(ret, "==", ref_len, "7. test vector - ENC output length");
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ ret = base64_decode(out, ret, out2, BUF_LEN);
+ cmp_ok(ret, "==", in_len, "7. test vector - DEC output length");
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+
+ // Bad paddings
+ ret = base64_decode((uint8_t *)"A===", 4, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 3");
+ ret = base64_decode((uint8_t *)"====", 4, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad padding length 4");
+
+ // Bad data length
+ ret = base64_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 1");
+ ret = base64_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 2");
+ ret = base64_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 3");
+ ret = base64_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ cmp_ok(ret, "==", -1, "Bad data length 5");
+
+ // Bad data character
+ ret = base64_decode((uint8_t *)"AAA$", 4, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad data character dollar");
+ ret = base64_decode((uint8_t *)"AAA ", 4, out, BUF_LEN);
+ cmp_ok(ret, "==", -2, "Bad data character space");
+
+ return 0;
+}
diff --git a/src/tests/common/base64_tests.h b/src/tests/common/base64_tests.h
new file mode 100644
index 0000000..57a0edc
--- /dev/null
+++ b/src/tests/common/base64_tests.h
@@ -0,0 +1,25 @@
+/* Copyright (C) 2011 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/>.
+ */
+
+#ifndef _KNOTD_BASE64_TESTS_H_
+#define _KNOTD_BASE64_TESTS_H_
+
+#include "common/libtap/tap_unit.h"
+
+/* Unit API. */
+unit_api base64_tests_api;
+
+#endif /* _KNOTD_BASE64_TESTS_H_ */
diff --git a/src/tests/libknot/libknot/response_tests.c b/src/tests/libknot/libknot/response_tests.c
index 9c477c8..834d838 100644
--- a/src/tests/libknot/libknot/response_tests.c
+++ b/src/tests/libknot/libknot/response_tests.c
@@ -77,7 +77,7 @@ static int test_response_init_query()
int errors = 0;
int lived = 0;
lives_ok({
- if (knot_response_init_from_query(NULL, NULL) !=
+ if (knot_response_init_from_query(NULL, NULL, 1) !=
KNOT_EINVAL) {
diag("Calling response_init_query with NULL packet and "
"NULL query did not return KNOT_EINVAL!");
@@ -91,7 +91,7 @@ static int test_response_init_query()
KNOT_PACKET_PREALLOC_RESPONSE);
knot_response_init(response);
lived = 0;
- if (knot_response_init_from_query(response, NULL) !=
+ if (knot_response_init_from_query(response, NULL, 1) !=
KNOT_EINVAL) {
diag("Calling response_init_query with NULL query "
"did not return KNOT_EINVAL!");
@@ -100,7 +100,7 @@ static int test_response_init_query()
lived = 1;
knot_packet_t *query =
knot_packet_new(KNOT_PACKET_PREALLOC_QUERY);
- if (knot_response_init_from_query(NULL, query) !=
+ if (knot_response_init_from_query(NULL, query, 1) !=
KNOT_EINVAL) {
diag("Calling response_init_query with NULL response "
"did not return KNOT_EINVAL!");
diff --git a/src/tests/libknot/realdata/libknot/response_tests_realdata.c b/src/tests/libknot/realdata/libknot/response_tests_realdata.c
index 0413238..63961f7 100644
--- a/src/tests/libknot/realdata/libknot/response_tests_realdata.c
+++ b/src/tests/libknot/realdata/libknot/response_tests_realdata.c
@@ -69,7 +69,7 @@ static int test_response_init_from_query(list query_list)
assert(query);
knot_packet_set_max_size(response, 1024 * 10);
if (knot_response_init_from_query(response,
- query) != KNOT_EOK) {
+ query, 1) != KNOT_EOK) {
diag("Could not init response from query!");
errors++;
}
diff --git a/src/tests/unittests_main.c b/src/tests/unittests_main.c
index 21eae14..17ea3b4 100644
--- a/src/tests/unittests_main.c
+++ b/src/tests/unittests_main.c
@@ -24,6 +24,8 @@
#include "tests/common/events_tests.h"
#include "tests/common/acl_tests.h"
#include "tests/common/fdset_tests.h"
+#include "tests/common/base64_tests.h"
+#include "tests/common/base32hex_tests.h"
#include "tests/knot/dthreads_tests.h"
#include "tests/knot/journal_tests.h"
#include "tests/knot/server_tests.h"
@@ -41,17 +43,19 @@ int main(int argc, char *argv[])
// Build test set
unit_api *tests[] = {
/* Core data structures. */
- &journal_tests_api, //! Journal unit
- &slab_tests_api, //! SLAB allocator unit
- &skiplist_tests_api, //! Skip list unit
- &dthreads_tests_api, //! DThreads testing unit
- &events_tests_api, //! Events testing unit
- &acl_tests_api, //! ACLs
- &fdset_tests_api, //! FDSET polling wrapper
+ &journal_tests_api, //! Journal unit
+ &slab_tests_api, //! SLAB allocator unit
+ &skiplist_tests_api, //! Skip list unit
+ &dthreads_tests_api, //! DThreads testing unit
+ &events_tests_api, //! Events testing unit
+ &acl_tests_api, //! ACLs
+ &fdset_tests_api, //! FDSET polling wrapper
+ &base64_tests_api, //! Base64 encoding
+ &base32hex_tests_api, //! Base32hex encoding
/* Server parts. */
- &conf_tests_api, //! Configuration parser tests
- &server_tests_api, //! Server unit
+ &conf_tests_api, //! Configuration parser tests
+ &server_tests_api, //! Server unit
NULL
};
diff --git a/src/zcompile/parser-util.c b/src/zcompile/parser-util.c
index 955a7b0..4cdb4e3 100644
--- a/src/zcompile/parser-util.c
+++ b/src/zcompile/parser-util.c
@@ -1802,8 +1802,12 @@ uint16_t * zparser_conv_b32(const char *b32)
/*!< \todo BLEEDING EYES! */
b32_copy[strlen(b32)] = '\0';
- if (!base32hex_decode(b32_copy,
- strlen(b32_copy), (char *)buffer + 1, &i)) {
+ int32_t b32_ret = base32hex_decode((uint8_t *)b32_copy, strlen(b32_copy),
+ buffer + 1, i);
+
+ i = b32_ret;
+
+ if (b32_ret <= 0) {
zc_error_prev_line("invalid base32 data");
parser->error_occurred = 1;
} else {
diff --git a/src/zcompile/zcompile.c b/src/zcompile/zcompile.c
index e2f05e3..636a877 100644
--- a/src/zcompile/zcompile.c
+++ b/src/zcompile/zcompile.c
@@ -270,7 +270,7 @@ dbg_zp_exec_detail(
if (knot_node_rrset(knot_zone_contents_apex(contents),
KNOT_RRTYPE_SOA) != NULL) {
/* Receiving another SOA. */
- if (!knot_rrset_compare(current_rrset,
+ if (!knot_rrset_match(current_rrset,
knot_node_rrset(knot_zone_contents_apex(contents),
KNOT_RRTYPE_SOA), KNOT_RRSET_COMPARE_WHOLE)) {
return KNOTDZCOMPILE_ESOA;
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..bd3e9aa
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,16 @@
+BASE = ../src
+CFLAGS += -std=gnu99 -g -I.. -I$(BASE) -I$(BASE)/libknot -I/usr/local/include
+
+chkjournal: chkjournal.c $(BASE)/libknotd.la
+ libtool --mode=link $(CC) $(CFLAGS) chkjournal.c $(BASE)/libknotd.la -o chkjournal
+chkjournal-i386:
+ @echo "!!! Make sure knot is compiled with -fpack-struct=4"
+ @grep -q -- "-fpack-struct=4" $(BASE)/Makefile || exit 1
+ libtool --mode=link $(CC) $(CFLAGS) -fpack-struct=4 chkjournal.c $(BASE)/libknotd.la -o chkjournal-i386
+chkjournal-amd64:
+ @echo "!!! Make sure knot is compiled with -fpack-struct=8"
+ @grep -q -- "-fpack-struct=8" $(BASE)/Makefile || exit 1
+ libtool --mode=link $(CC) $(CFLAGS) -fpack-struct=8 chkjournal.c $(BASE)/libknotd.la -o chkjournal-amd64
+clean:
+ rm chkjournal chkjournal-i386 chkjournal-amd64
+
diff --git a/tests/chkjournal.c b/tests/chkjournal.c
new file mode 100644
index 0000000..b429a87
--- /dev/null
+++ b/tests/chkjournal.c
@@ -0,0 +1,541 @@
+/* Copyright (C) 2011 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/>.
+
+ Usage: chkjournal --help
+
+ How to make:
+ (for this computer): make chkjournal
+ (for 32bit journals): make chkjournal-i386
+ (for 64bit journal): make chkjournal-amd64
+
+ !!! For specific versions, make sure the libknotd.la is compiled
+ with -fpack-struct=4 for 32bit or -fpack-struct=8 for 64bit chkjournal.
+ f.e.:
+ $ cd <knot_root>
+ $ CFLAGS="-fpack-struct=4" ./configure
+ $ make clean && make -j8
+ $ cd tests
+ $ make chkjournal-i386
+ */
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <assert.h>
+#include <sys/stat.h>
+
+#ifndef KNOT_RRSET_DEBUG
+#define KNOT_RRSET_DEBUG 1
+#endif
+
+//#define KNOT_RDATA_DEBUG 1
+#include "src/common/log.h"
+#include "src/common/crc.h"
+#include "src/common/errcode.h"
+#include "src/knot/server/journal.h"
+#include "src/knot/server/zones.h"
+#include "src/libknot/updates/changesets.h"
+#include "src/libknot/util/debug.h"
+#include "src/libknot/util/debug.c"
+#include "config.h"
+
+/* Alignment. */
+static size_t ALIGNMENT = 1;
+static inline size_t a(size_t s) {
+ return s + s % ALIGNMENT;
+}
+static size_t PADDING = 4;
+
+/*! \brief Return 'serial_from' part of the key. */
+static inline uint32_t ixfrdb_key_from(uint64_t k)
+{
+ /* 64 32 0
+ * key = [TO | FROM]
+ * Need: Least significant 32 bits.
+ */
+ return (uint32_t)(k & ((uint64_t)0x00000000ffffffff));
+}
+
+/*----------------------------------------------------------------------------*/
+
+/*! \brief Return 'serial_to' part of the key. */
+static inline uint32_t ixfrdb_key_to(uint64_t k)
+{
+ /* 64 32 0
+ * key = [TO | FROM]
+ * Need: Most significant 32 bits.
+ */
+ return (uint32_t)(k >> (uint64_t)32);
+}
+
+/*----------------------------------------------------------------------------*/
+
+#define MAGIC_LENGTH 7
+
+enum {
+ SHOW = 0,
+ UPDATE,
+ FIXCRC,
+ DUMP,
+ XDUMP
+};
+
+void help(int argc, char **argv)
+{
+ printf("Usage: chkjournal [parameters] <journal_file>\n");
+ printf("Parameters:\n"
+ " -p, --padding=N Padding after each node.\n"
+ " -a, --align=N Expect journal structures aligned to N bytes.\n"
+ " -f, --fixcrc Recompute CRC32.\n"
+ " -u, --update Update version to latest.\n"
+ " -x, --xdump=id Dump changeset (hexdump).\n"
+ " -d, --dump=id Dump changeset (parsed).\n"
+ " -h, --help Print help and usage.\n"
+ );
+}
+
+/* Show. */
+int walkf(journal_t *j, journal_node_t *n) {
+ printf("entry '%zu' flags=0x%hu fpos=%u len=%u\n", n->id, n->flags, n->pos, n->len);
+ return 0;
+}
+
+int show(const char *fname)
+{
+ /* Open journal. */
+ journal_t *j = journal_open(fname, -1, 0, 0);
+ if (j == NULL) {
+ fprintf(stderr, "error: couldn't open journal '%s'\n", fname);
+ return 1;
+ }
+
+ printf("journal: %s max_nodes=%hu queue=%u..%u\n",
+ fname, j->max_nodes, j->qtail, j->qhead);
+ journal_walk(j, walkf);
+ journal_close(j);
+ return 0;
+}
+
+/* Fix CRC. */
+int fixcrc(const char *fname)
+{
+ int fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ return 1;
+ }
+
+ int ret = 1;
+ if (journal_update_crc(fd) == 0) {
+ ret = 0;
+ }
+
+ close(fd);
+ return ret;
+}
+
+/* Fix file positions. */
+static int FPOSDELTA = 0;
+int walkfix(journal_t *j, journal_node_t *n) {
+ n->pos += FPOSDELTA;
+ journal_update(j, n);
+ return 0;
+}
+
+int fixfpos(const char *fname, int delta)
+{
+ /* Open journal. */
+ journal_t *j = journal_open(fname, -1, 0, 0);
+ if (j == NULL) {
+ fprintf(stderr, "error: couldn't open journal '%s'\n", fname);
+ return 1;
+ }
+ FPOSDELTA = delta;
+ journal_walk(j, walkfix);
+ journal_close(j);
+ return 0;
+}
+
+/* Update journal file. */
+int update(const char *fname)
+{
+ int fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ return 1;
+ }
+
+ /* Check source magic bytes. */
+ int rb = 0;
+ int ret = 0;
+ char buf[4096];
+ char mbytes[MAGIC_LENGTH] = {};
+ read(fd, mbytes, MAGIC_LENGTH);
+ if (memcmp(mbytes, "knot100", MAGIC_LENGTH) == 0) {
+ /* 100 -> 101 +crc after MB. */
+ char *nfname = malloc(strlen(fname) + 4 + 1);
+ assert(nfname != NULL);
+ strncpy(nfname, fname, strlen(fname));
+ strncat(nfname, ".new", 4);
+ int nfd = open(nfname, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ const char nmbytes[] = "knot101";
+ if (nfd >= 0) {
+ /* Extend header. */
+ write(nfd, nmbytes, MAGIC_LENGTH);
+ write(nfd, buf, sizeof(crc_t));
+ read(fd, buf, sizeof(uint16_t) * 3);
+ write(nfd, buf, sizeof(uint16_t) * 3);
+
+ /* Copy nodes. */
+ uint16_t ncount = *((uint16_t*)buf) + 1;
+ printf("Will update %hu nodes.\n", ncount - 1);
+ for (uint16_t i = 0; i < ncount; ++i) {
+ /* Copy id+flags. */
+ read(fd, buf, a(sizeof(uint64_t)+sizeof(uint16_t)));
+ write(nfd, buf, a(sizeof(uint64_t)+sizeof(uint16_t)));
+ read(fd, buf, a(2*sizeof(uint32_t)));
+ *((uint32_t*)buf) += sizeof(crc_t);
+ write(nfd, buf, a(2*sizeof(uint32_t)));
+ /* Copy padding. */
+ read(fd, buf, PADDING);
+ write(nfd, buf, PADDING);
+ }
+
+ /* Copy remaining. */
+ while((rb = read(fd, buf, sizeof(buf))) > 0) {
+ if (write(nfd, buf, rb) != rb) {
+ ret = 1;
+ break;
+ }
+ }
+ /* Update CRC. */
+ if (ret == 0) {
+ journal_update_crc(nfd);
+ }
+ }
+
+ /* Replace if success. */
+ close(nfd);
+ close(fd);
+ if (ret == 0) {
+ remove(fname);
+ rename(nfname, fname);
+ printf("Converted journal v1.0.0 -> v1.0.1\n");
+ }
+ free(nfname);
+ } else if (memcmp(mbytes, "knot101", MAGIC_LENGTH) == 0) {
+ /* 101 -> 102, transactions, +uint16 'next' in jnode */
+ char *nfname = malloc(strlen(fname) + 4 + 1);
+ assert(nfname != NULL);
+ strncpy(nfname, fname, strlen(fname));
+ strncat(nfname, ".new", 4);
+ int nfd = open(nfname, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ size_t hs102 = (MAGIC_LENGTH + sizeof(crc_t) + sizeof(uint16_t) * 3);
+ const char nmbytes[] = "knot102";
+
+ if (nfd >= 0) {
+ /* Copy header. */
+ lseek(fd, 0, SEEK_SET);
+ read(fd, buf, hs102);
+ write(nfd, buf, hs102);
+ lseek(nfd, 0, SEEK_SET);
+ write(nfd, nmbytes, MAGIC_LENGTH);
+
+ /* Read node count. */
+ lseek(fd, MAGIC_LENGTH + sizeof(crc_t), SEEK_SET);
+ uint16_t ncount = 0;
+ read(fd, &ncount, sizeof(uint16_t));
+ printf("Will update %hu nodes.\n", ncount);
+ ncount += 1; /* Free segment. */
+ lseek(fd, hs102, SEEK_SET);
+ lseek(nfd, hs102, SEEK_SET);
+
+ /* Extend nodes. */
+ /*! \todo Calculate offset from difference of struct size. */
+ for (uint16_t i = 0; i < ncount; ++i) {
+ /* Copy id+flags. */
+ read(fd, buf, a(sizeof(uint64_t)+sizeof(uint16_t)));
+ write(nfd, buf, sizeof(uint64_t)+sizeof(uint16_t));
+ /* Append 'next'. */
+ memset(buf, 0, sizeof(uint16_t));
+ write(nfd, buf, sizeof(uint16_t));
+
+ /* Copy rest. */
+ read(fd, buf, a(2*sizeof(uint32_t)));
+ //*((uint32_t*)buf) += offs;
+ write(nfd, buf, a(2*sizeof(uint32_t)));
+ /* Copy padding. */
+ read(fd, buf, PADDING);
+ write(nfd, buf, PADDING);
+ }
+
+ /* Copy remaining. */
+ while((rb = read(fd, buf, sizeof(buf))) > 0) {
+ if (write(nfd, buf, rb) != rb) {
+ ret = 1;
+ break;
+ }
+ }
+ /* Update CRC. */
+ if (ret == 0) {
+ journal_update_crc(nfd);
+ }
+ }
+
+ /* Replace if success. */
+ close(nfd);
+ close(fd);
+ if (ret == 0) {
+ remove(fname);
+ rename(nfname, fname);
+ printf("Converted journal v1.0.1-> v1.0.2\n");
+ }
+ free(nfname);
+ } else if (memcmp(mbytes, "knot102", MAGIC_LENGTH) == 0) {
+ /* Update magic bytes. */
+ const char nmbytes[] = "knot104";
+ int nfd = open(fname, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ lseek(nfd, 0, SEEK_SET);
+ write(nfd, nmbytes, MAGIC_LENGTH);
+ journal_update_crc(nfd);
+ close(nfd);
+ printf("Converted journal v1.0.2-> v.1.0.4\n");
+ } else if (memcmp(mbytes, "knot104", MAGIC_LENGTH) == 0) {
+ /* Update magic bytes and add 4bytes to each journal node. */
+ const char nmbytes[] = "knot105";
+ int nfd = open(fname, O_RDWR, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ lseek(nfd, 0, SEEK_SET);
+ write(nfd, nmbytes, MAGIC_LENGTH);
+ journal_update_crc(nfd);
+ close(nfd);
+
+ /* Fix crc. */
+ fixcrc(fname);
+
+ /* Open as source journal. */
+ journal_t *src = journal_open(fname, 0, 0, 0);
+ assert(src != NULL);
+
+ /* Recreate as new journal. */
+ char *nfname = malloc(strlen(fname) + 4 + 1);
+ assert(nfname != NULL);
+ strncpy(nfname, fname, strlen(fname));
+ strncat(nfname, ".new", 4);
+ journal_create(nfname, src->max_nodes);
+ journal_t *dst = journal_open(nfname, 0, 0, 0);
+ assert(dst != NULL);
+
+ /* Convert journal entries, adding dummy flags. */
+ uint32_t flags = 1;
+ size_t i = src->qhead;
+ for(; i != src->qtail; i = (i + 1) % src->max_nodes) {
+ journal_node_t *n = src->nodes + i;
+ char *ibuf = malloc(n->len + sizeof(uint32_t));
+ memset(ibuf, &flags, sizeof(uint32_t));
+ journal_read(src, n->id, NULL, ibuf + sizeof(uint32_t));
+ journal_write(dst, n->id, ibuf, n->len + sizeof(uint32_t));
+ free(ibuf);
+ }
+ journal_close(src);
+ journal_close(dst);
+
+ /* Switch journals. */
+ remove(fname);
+ rename(nfname, fname);
+ free(nfname);
+ printf("Converted journal v1.0.4-> v.1.0.5\n");
+ } else {
+ close(fd);
+ }
+
+
+ return ret;
+}
+
+/* Hexdump. */
+int xdump(const char *fname, uint64_t id)
+{
+ /* Open journal. */
+ journal_t *j = journal_open(fname, -1, 0, 0);
+ if (j == NULL) {
+ fprintf(stderr, "error: couldn't open journal '%s'\n", fname);
+ return 1;
+ }
+
+ int ret = 1;
+ journal_node_t *n = NULL;
+ journal_fetch(j, id, NULL, &n);
+ if (n != NULL) {
+ char *buf = malloc(n->len);
+ assert(buf != NULL);
+ journal_read(j, id, NULL, buf);
+ size_t rf = 0;
+ while(rf < n->len) {
+ if (rf % 16 == 0) printf("\n%08lx |", (unsigned long)rf);
+ printf(" %02x", (unsigned)buf[rf] & 0xffU);
+ ++rf;
+ }
+ printf("\n");
+ printf("-- index %llu fpos=%u length=%u\n",
+ (unsigned long long)id, n->pos, n->len);
+ free(buf);
+ ret = 0;
+ }
+
+ journal_close(j);
+ return ret;
+}
+
+/* Hexdump. */
+int dump(const char *fname, uint64_t id)
+{
+ /* Open journal. */
+ journal_t *j = journal_open(fname, -1, 0, 0);
+ if (j == NULL) {
+ fprintf(stderr, "error: couldn't open journal '%s'\n", fname);
+ return 1;
+ }
+
+ journal_node_t *n = NULL;
+ journal_fetch(j, id, NULL, &n);
+ if (n == NULL) {
+ journal_close(j);
+ return 1;
+ }
+
+ /* Reserve and read changeset. */
+ knot_changesets_t* chsets = malloc(sizeof(knot_changesets_t));
+ assert(chsets != NULL);
+ memset(chsets, 0, sizeof(knot_changesets_t));
+ chsets->count = 1;
+ knot_changesets_check_size(chsets);
+ assert(chsets->sets != NULL);
+ knot_changeset_t *chs = chsets->sets;
+ memset(chs, 0, sizeof(knot_changeset_t));
+ chs->serial_from = ixfrdb_key_from(n->id);
+ chs->serial_to = ixfrdb_key_to(n->id);
+ chs->data = malloc(n->len);
+ assert(chs->data != NULL);
+ journal_read(j, n->id, NULL, chs->data);
+ chs->size = chs->allocated = n->len;
+
+ /* Unpack */
+ int ks = zones_changesets_from_binary(chsets);
+ printf("=== index %llu fpos=%u length=%u\n",
+ (unsigned long long)id, n->pos, n->len);
+
+ /* TODO: dump wireformat? */
+ printf("--- %zu records\n", chs->remove_count);
+ for (unsigned i = 0; i < chs->remove_count; ++i) {
+ knot_rrset_dump(chs->remove[i], 1);
+ }
+ printf("+++ %zu records\n", chs->add_count);
+ for (unsigned i = 0; i < chs->add_count; ++i) {
+ knot_rrset_dump(chs->add[i], 1);
+ }
+ printf("=== index %llu fpos=%u length=%u\n",
+ (unsigned long long)id, n->pos, n->len);
+
+ /* Close. */
+ //knot_free_changesets(&chsets);
+ journal_close(j);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ /* Long options. */
+ struct option opts[] = {
+ {"padding",required_argument, 0, 'p'},
+ {"align", required_argument, 0, 'a'},
+ {"fixcrc", no_argument, 0, 'f'},
+ {"update", no_argument, 0, 'u'},
+ {"dump", required_argument, 0, 'd'},
+ {"xdump", required_argument, 0, 'x'},
+ {"help", no_argument, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ int c = 0, li = 0;
+ int action = SHOW;
+ uint64_t dump_id = 0;
+ while ((c = getopt_long(argc, argv, "p:a:fuhd:x:", opts, &li)) != -1) {
+ switch (c)
+ {
+ case 'p':
+ PADDING = strtoull(optarg, NULL, 10);
+ break;
+ case 'a':
+ ALIGNMENT = strtoull(optarg, NULL, 10);
+ break;
+ case 'f':
+ action = FIXCRC;
+ break;
+ case 'u':
+ action = UPDATE;
+ break;
+ case 'd':
+ action = DUMP;
+ dump_id = strtoull(optarg, NULL, 10);
+ break;
+ case 'x':
+ action = XDUMP;
+ dump_id = strtoull(optarg, NULL, 10);
+ break;
+ case 'h':
+ case '?':
+ default:
+ help(argc, argv);
+ return 1;
+ }
+ }
+
+ /* Check if there's at least one remaining non-option. */
+ if (argc - optind < 1) {
+ help(argc, argv);
+ return 1;
+ }
+ const char *fname = argv[optind];
+
+ /* Init log. */
+ log_init();
+ log_levels_set(LOGT_SYSLOG, LOG_ANY, 0);
+ log_levels_set(LOGT_STDERR, LOG_ANY, 0);
+ log_levels_set(LOGT_STDOUT, LOG_ANY, ~0);
+
+ /* Execute operation. */
+ int ret = 0;
+ switch(action) {
+ case SHOW:
+ ret = show(fname);
+ break;
+ case UPDATE:
+ ret = update(fname);
+ break;
+ case FIXCRC:
+ ret = fixcrc(fname);
+ break;
+ case DUMP:
+ ret = dump(fname, dump_id);
+ break;
+ case XDUMP:
+ ret = xdump(fname, dump_id);
+ break;
+ default:
+ fprintf(stderr, "Unsupported operation.\n");
+ break;
+ }
+
+ return ret;
+}