diff --git a/samples/knot.full.conf b/samples/knot.full.conf index 230ea92ee4b89b0ea31daf3c629d6510ef66074d..e2fc15cb54f8dd38413d56b1376ab1656b3bafb5 100644 --- a/samples/knot.full.conf +++ b/samples/knot.full.conf @@ -23,10 +23,14 @@ system { # Working directory of the server # Used to store compiled zones and PID file storage "/tmp/knot-sample"; +} + +# Section 'keys' contains list of TSIG keys +keys { # TSIG key # - # format: key key-type "<key>"; + # format: name key-type "<key>"; # where key-type may be one of the following: # hmac-md5 # hmac-sha1 @@ -35,8 +39,10 @@ system { # hmac-sha384 # hmac-sha512 # and <key> is the private key - key hmac-md5 "Wg=="; + key0.server0 hmac-md5 "Wg=="; + # TSIG key for zone + key0.example.com hmac-md5 "==gW"; } # Section 'interfaces' contains definitions of listening interfaces. @@ -77,6 +83,7 @@ remotes { server0 { # <name> is an arbitrary symbolic name address 127.0.0.1; # <address> may be ither IPv4 or IPv6 address port 53531; # port is optional (default: 53) + key key0.server0; # (optional) specification of TSIG key associated for this remote } # Format 2: <name> { address <address>@<port>; } @@ -144,6 +151,10 @@ zones { # Default value: 5 notify-retries 5; + # TSIG key associated with this zone (optional) + # Keys associated with remote override keys associated with zone + key key0.example.com; + # Timeout for syncing changes from zone database to zonefile # Possible values: <1..INT_MAX> (seconds) # Default value: inherited from zones.zonefile-sync diff --git a/src/knot/conf/cf-lex.l b/src/knot/conf/cf-lex.l index fb7637e9c156d62592c3644d7410da6570ba6cbb..8d965427f2bbf824a59dd6462cd8d43373523be3 100644 --- a/src/knot/conf/cf-lex.l +++ b/src/knot/conf/cf-lex.l @@ -58,6 +58,7 @@ identity { lval.t = yytext; return IDENTITY; } version { lval.t = yytext; return VERSION; } storage { lval.t = yytext; return STORAGE; } key { lval.t = yytext; return KEY; } +keys { lval.t = yytext; return KEYS; } remotes { lval.t = yytext; return REMOTES; } zones { lval.t = yytext; return ZONES; } diff --git a/src/knot/conf/cf-parse.y b/src/knot/conf/cf-parse.y index 43cbaa04a7f506672c8124a83ee73c33e3587908..87c36d4ece4e15d0c3a6a9ac30e157be2a8228ac 100644 --- a/src/knot/conf/cf-parse.y +++ b/src/knot/conf/cf-parse.y @@ -60,7 +60,7 @@ static void conf_acl_item(void *scanner, char *item) /* Append to list if found. */ if (!found) { - char buf[256]; + char buf[512]; snprintf(buf, sizeof(buf), "remote '%s' is not defined", item); cf_error(scanner, buf); } else { @@ -87,6 +87,22 @@ static void conf_acl_item(void *scanner, char *item) free(item); } +static void conf_key_item(void *scanner, conf_key_t **key, char *item) +{ + /* Find existing node in keys. */ + conf_key_t* r = 0; + WALK_LIST (r, new_config->keys) { + if (strcmp(r->name, item) == 0) { + *key = r; + return; + } + } + + char buf[512]; + snprintf(buf, sizeof(buf), "key '%s' is not defined", item); + cf_error(scanner, buf); +} + %} %pure-parser @@ -110,7 +126,7 @@ static void conf_acl_item(void *scanner, char *item) %token <tok> SIZE %token <tok> BOOL -%token <tok> SYSTEM IDENTITY VERSION STORAGE KEY +%token <tok> SYSTEM IDENTITY VERSION STORAGE KEY KEYS %token <tok> TSIG_ALGO_NAME %token <tok> REMOTES @@ -222,11 +238,24 @@ system: | system IDENTITY TEXT ';' { new_config->identity = $3.t; } | system STORAGE TEXT ';' { new_config->storage = $3.t; } | system KEY TSIG_ALGO_NAME TEXT ';' { - new_config->key.algorithm = $3.alg; - new_config->key.secret = $4.t; - } + fprintf(stderr, "warning: Config option 'system.key' is deprecated " + "and has no effect.\n"); + free($4.t); + } ; +keys: + KEYS '{' + | keys TEXT TSIG_ALGO_NAME TEXT ';' { + conf_key_t *k = malloc(sizeof(conf_key_t)); + memset(k, 0, sizeof(conf_key_t)); + k->name = $2.t; + k->algorithm = $3.alg; + k->secret = $4.t; + add_tail(&new_config->keys, &k->n); + ++new_config->key_count; +} + remote_start: | TEXT { conf_start_remote($1.t); } | LOG_SRC { conf_start_remote(strdup($1.t)); } @@ -285,6 +314,14 @@ remote: } } } + | remote KEY TEXT ';' { + if (this_remote->key != 0) { + cf_error(scanner, "only one key definition is allowed in remote section\n"); + } else { + conf_key_item(scanner, &this_remote->key, $3.t); + free($3.t); + } + } ; remotes: @@ -422,6 +459,14 @@ zone: this_zone->notify_timeout = $3.i; } } + | zone KEY TEXT ';' { + if (this_zone->key != 0) { + cf_error(scanner, "only one key definition is allowed in zone section\n"); + } else { + conf_key_item(scanner, &this_zone->key, $3.t); + free($3.t); + } + } ; zones: @@ -537,7 +582,7 @@ log_start: log: LOG '{' log_start log_end; -conf: ';' | system '}' | interfaces '}' | remotes '}' | zones '}' | log '}'; +conf: ';' | system '}' | interfaces '}' | keys '}' | remotes '}' | zones '}' | log '}'; %% diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index a3025ed54e2e686b1ccad01876295cb9cdc65430..53584262a6a27656199dea70cacdbecb1a469e00 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -81,6 +81,18 @@ void cf_error(void *scanner, const char *msg) * Config helper functions. */ +/*! \brief Free TSIG key. */ +static void key_free(conf_key_t *k) +{ + /* Secure erase. */ + if (k->secret) { + memset(k->secret, 0, strlen(k->secret)); + } + free(k->secret); + free(k->name); + free(k); +} + /*! \brief Free config interfaces. */ static void iface_free(conf_iface_t *iface) { @@ -396,6 +408,7 @@ conf_t *conf_new(const char* path) init_list(&c->zones); init_list(&c->hooks); init_list(&c->remotes); + init_list(&c->keys); // Defaults c->zone_checks = 0; @@ -478,11 +491,10 @@ void conf_truncate(conf_t *conf, int unload_hooks) init_list(&conf->hooks); } - // Free key - if (conf->key.secret) { - free(conf->key.secret); + // Free keys + WALK_LIST_DELSAFE(n, nxt, conf->keys) { + key_free((conf_key_t *)n); } - memset(&conf->key, 0, sizeof(conf_key_t)); // Free interfaces WALK_LIST_DELSAFE(n, nxt, conf->ifaces) { diff --git a/src/knot/conf/conf.h b/src/knot/conf/conf.h index da639ec5a45ace84da8e4436fae07c65c22faf34..5a479c605d063dee96ba279267eae58d4e1b5576 100644 --- a/src/knot/conf/conf.h +++ b/src/knot/conf/conf.h @@ -28,6 +28,32 @@ #define CONFIG_NOTIFY_TIMEOUT 60 /*!< 60s (suggested in RFC1996) */ #define CONFIG_DBSYNC_TIMEOUT (60*60) /*!< 1 hour. */ +/*! + * \brief List of TSIG algoritms. + * + * Master list of TSIG algoritms as per IANA registry + * http://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xhtml + */ +typedef enum tsig_alg_t { + GSS_TSIG, + HMAC_MD5, + HMAC_SHA1, + HMAC_SHA224, + HMAC_SHA256, + HMAC_SHA384, + HMAC_SHA512 +} tsig_alg_t; + +/*! + * \brief Configuration for the TSIG key. + */ +typedef struct conf_key_t { + node n; /*!< List node. */ + char *name; /*!< Key name. */ + tsig_alg_t algorithm; /*!< Key algorithm. */ + char *secret; /*!< Key data. */ +} conf_key_t; + /*! * \brief Configuration for the interface * @@ -41,6 +67,7 @@ typedef struct conf_iface_t { char *address; /*!< IP (IPv4/v6) address for this interface */ int port; /*!< Port number for this interface */ int family; /*!< Address family. */ + conf_key_t *key; /*!< TSIG key (remotes only). */ } conf_iface_t; /*! @@ -53,30 +80,6 @@ typedef struct conf_remote_t { conf_iface_t *remote; /*!< Pointer to interface descriptor. */ } conf_remote_t; -/*! - * \brief List of TSIG algoritms. - * - * Master list of TSIG algoritms as per IANA registry - * http://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xhtml - */ -typedef enum tsig_alg_t { - GSS_TSIG, - HMAC_MD5, - HMAC_SHA1, - HMAC_SHA224, - HMAC_SHA256, - HMAC_SHA384, - HMAC_SHA512 -} tsig_alg_t; - -/*! - * \brief Configuration for the TSIG key. - */ -typedef struct conf_key_t { - tsig_alg_t algorithm; /*!< Key algorithm. */ - char *secret; /*!< Key data. */ -} conf_key_t; - /*! * \brief Zone configuration. * @@ -91,15 +94,16 @@ typedef struct conf_key_t { typedef struct conf_zone_t { node n; char *name; /*!< Zone name. */ - enum knot_rr_class cls; /*!< Zone class (IN or CH). */ + enum knot_rr_class cls; /*!< Zone class (IN or CH). */ char *file; /*!< Path to a zone file. */ char *db; /*!< Path to a database file. */ char *ixfr_db; /*!< Path to a IXFR database file. */ - size_t ixfr_fslimit; /*!< File size limit for IXFR journal. */ + size_t ixfr_fslimit; /*!< File size limit for IXFR journal. */ int dbsync_timeout; /*!< Interval between syncing to zonefile.*/ int enable_checks; /*!< Semantic checks for parser.*/ int notify_retries; /*!< NOTIFY query retries. */ int notify_timeout; /*!< Timeout for NOTIFY response (s). */ + conf_key_t *key; /*!< TSIG key for zone. */ struct { list xfr_in; /*!< Remotes accepted for for xfr-in.*/ list xfr_out; /*!< Remotes accepted for xfr-out.*/ @@ -152,7 +156,6 @@ typedef struct conf_t { char *version; /*!< Version for CH TXT version.{bind|server} */ char *storage; /*!< Persistent storage path for databases and such. */ char *pidfile; /*!< PID file path. */ - conf_key_t key; /*!< Server TSIG key. */ /* * Log @@ -167,7 +170,13 @@ typedef struct conf_t { int ifaces_count; /*!< Count of interfaces. */ /* - * Remotse + * TSIG keys + */ + list keys; /*!< List of TSIG keys. */ + int key_count; /*!< Count of TSIG keys. */ + + /* + * Remotes */ list remotes; /*!< List of remotes. */ int remotes_count;/*!< Count of remotes. */ diff --git a/src/tests/files/sample_conf b/src/tests/files/sample_conf index d7d54b4fa8f070a44f3918f419c4d55db4e935e7..ca7350c8143159edc37c5636237d3c38d1f1ad98 100644 --- a/src/tests/files/sample_conf +++ b/src/tests/files/sample_conf @@ -9,21 +9,26 @@ system { identity "I have no mouth and must scream"; version "Infinitesimal"; storage "/var/run/knot/"; - key hmac-md5 "Wg=="; +} + +keys { + key0 hmac-md5 "Wg=="; # key special for one remote + key1 hmac-md5 "==gW"; # implicit key for whole zone +} +remotes { + remote0 { address 1.2.3.4; key key0; } } zones { example.net { file "/var/lib/knot/example.net"; + xfr-out remote0; + key key1; } } interfaces { - # short notation - # name address@port - # interface0 10.10.1.1@53; - # will expand into long notation interface0 { address 10.10.1.1; port 53531; diff --git a/src/tests/knot/conf_tests.c b/src/tests/knot/conf_tests.c index 6f3443574e4ceb5761f7281f58640601d023acee..5a15d262779620a9de180cfa0ea702e72762dd7e 100644 --- a/src/tests/knot/conf_tests.c +++ b/src/tests/knot/conf_tests.c @@ -22,7 +22,7 @@ unit_api conf_tests_api = { */ static int conf_tests_count(int argc, char *argv[]) { - return 20; + return 22; } /*! Run all scheduled tests for given parameters. @@ -58,8 +58,14 @@ static int conf_tests_run(int argc, char *argv[]) cmp_ok(iface->port, "==", 53, "interface1 default port check"); // Test 9,10: Check server key - cmp_ok(conf->key.algorithm, "==", HMAC_MD5, "TSIG key algorithm check"); - is(conf->key.secret, "Wg==", "TSIG key secret check"); + if(conf->key_count <= 0) { + ok(0, "TSIG key algorithm check - NO KEY FOUND"); + ok(0, "TSIG key secret check - NO KEY FOUND"); + } else { + conf_key_t *k = (conf_key_t *)HEAD(conf->keys); + cmp_ok(k->algorithm, "==", HMAC_MD5, "TSIG key algorithm check"); + is(k->secret, "Wg==", "TSIG key secret check"); + } // Test 11,12,13,14,15,16,17,18: Check logging facilities cmp_ok(conf->logs_count, "==", 4, "log facilites count check"); @@ -98,6 +104,11 @@ static int conf_tests_run(int argc, char *argv[]) } endskip; + // Test 21,22: remote,zone -> key binding + conf_key_t *k = ((conf_iface_t*)HEAD(conf->remotes))->key; + is(k ? k->name : "", "key0", "remote is connected with correct key"); + k = ((conf_zone_t*)HEAD(conf->zones))->key; + is(k ? k->name : "", "key1", "zone is connected with correct key"); } endskip;