diff options
author | Ondřej Surý <ondrej@sury.org> | 2013-02-18 12:38:54 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2013-02-18 12:38:54 +0100 |
commit | 57f6772329cadf9c491cad62096138b85accd8b7 (patch) | |
tree | 5c0c1ab917a45ddb992d58758494a8ee844a2051 | |
parent | 9fd746d685910496beff321536ed55ed857740e7 (diff) | |
parent | 2729227efaac366293e70770b6e82e7e775a2605 (diff) | |
download | knot-57f6772329cadf9c491cad62096138b85accd8b7.tar.gz |
Merge tag 'upstream/1.2_rc2' into debian-sid
Upstream version 1.2~rc2
99 files changed, 9084 insertions, 3817 deletions
@@ -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 ========== @@ -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 @@ -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 + @@ -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; +} |