1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/*
 * Copyright (C) 2015 Benjamin Fry <benjaminfry@me.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Configuration module for the server binary, `named`.

use std::fs::File;
use std::io::Read;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::Duration;

use log::LogLevel;
use rustc_serialize::Decodable;
use toml::{Decoder, Value};

use trust_dns::error::*;
use trust_dns::rr::Name;
use trust_dns::rr::dnssec::{Algorithm, KeyFormat};

use authority::ZoneType;
use error::{ConfigErrorKind, ConfigResult, ConfigError};

static DEFAULT_PATH: &'static str = "/var/named"; // TODO what about windows (do I care? ;)
static DEFAULT_PORT: u16 = 53;
static DEFAULT_TLS_PORT: u16 = 853;
static DEFAULT_TCP_REQUEST_TIMEOUT: u64 = 5;

/// Server configuration
#[derive(RustcDecodable, Debug)]
pub struct Config {
    /// The list of IPv4 addresses to listen on
    listen_addrs_ipv4: Vec<String>,
    /// This list of IPv6 addresses to listen on
    listen_addrs_ipv6: Vec<String>,
    /// Port on which to listen (associated to all IPs)
    listen_port: Option<u16>,
    /// Secure port to listen on
    tls_listen_port: Option<u16>,
    /// Timeout associated to a request before it is closed.
    tcp_request_timeout: Option<u64>,
    /// Level at which to log, default is INFO
    log_level: Option<String>,
    /// Base configuration directory, i.e. root path for zones
    directory: Option<String>,
    /// List of configurations for zones
    zones: Vec<ZoneConfig>,
    /// Certificate to associate to TLS connections
    tls_cert: Option<TlsCertConfig>,
}

impl Config {
    /// read a Config file from the file specified at path.
    pub fn read_config(path: &Path) -> ConfigResult<Config> {
        let mut file: File = try!(File::open(path));
        let mut toml: String = String::new();
        try!(file.read_to_string(&mut toml));
        toml.parse()
    }

    /// set of listening ipv4 addresses (for TCP and UDP)
    pub fn get_listen_addrs_ipv4(&self) -> Vec<Ipv4Addr> {
        self.listen_addrs_ipv4
            .iter()
            .map(|s| s.parse().unwrap())
            .collect()
    }
    /// set of listening ipv6 addresses (for TCP and UDP)
    pub fn get_listen_addrs_ipv6(&self) -> Vec<Ipv6Addr> {
        self.listen_addrs_ipv6
            .iter()
            .map(|s| s.parse().unwrap())
            .collect()
    }
    /// port on which to listen for connections on specified addresses
    pub fn get_listen_port(&self) -> u16 {
        self.listen_port.unwrap_or(DEFAULT_PORT)
    }
    /// port on which to listen for TLS connections
    pub fn get_tls_listen_port(&self) -> u16 {
        self.tls_listen_port.unwrap_or(DEFAULT_TLS_PORT)
    }
    /// default timeout for all TCP connections before forceably shutdown
    pub fn get_tcp_request_timeout(&self) -> Duration {
        Duration::from_secs(self.tcp_request_timeout
                                .unwrap_or(DEFAULT_TCP_REQUEST_TIMEOUT))
    }

    // TODO: also support env_logger
    /// specify the log level which should be used, ["Trace", "Debug", "Info", "Warn", "Error"]
    pub fn get_log_level(&self) -> LogLevel {
        if let Some(ref level_str) = self.log_level {
            match level_str as &str {
                "Trace" => LogLevel::Trace,
                "Debug" => LogLevel::Debug,
                "Info" => LogLevel::Info,
                "Warn" => LogLevel::Warn,
                "Error" => LogLevel::Error,
                _ => LogLevel::Info,
            }
        } else {
            LogLevel::Info
        }
    }
    /// the path for all zone configurations, defaults to `/var/named`
    pub fn get_directory(&self) -> &Path {
        self.directory
            .as_ref()
            .map_or(Path::new(DEFAULT_PATH), |s| Path::new(s))
    }
    /// the set of zones which should be loaded
    pub fn get_zones(&self) -> &[ZoneConfig] {
        &self.zones
    }
    /// the tls certificate to use for accepting tls connections
    pub fn get_tls_cert(&self) -> Option<&TlsCertConfig> {
        self.tls_cert.as_ref()
    }
}

impl FromStr for Config {
    type Err = ConfigError;

    fn from_str(toml: &str) -> ConfigResult<Config> {
        let value: Value = try!(toml.parse()
                                    .map_err(|vec| ConfigErrorKind::VecParserError(vec)));
        let mut decoder: Decoder = Decoder::new(value);
        Ok(try!(Self::decode(&mut decoder)))
    }
}

/// Configuration for a zone
#[derive(RustcDecodable, PartialEq, Debug)]
pub struct ZoneConfig {
    zone: String, // TODO: make Domain::Name decodable
    zone_type: ZoneType,
    file: String,
    allow_update: Option<bool>,
    enable_dnssec: Option<bool>,
    keys: Vec<KeyConfig>,
}

impl ZoneConfig {
    /// Return a new zone configuration
    ///
    /// # Arguments
    ///
    /// * `zone` - name of a zone, e.g. example.com
    /// * `zone_type` - Type of zone, e.g. Master
    /// * `file` - relative to Config base path, to the zone file
    /// * `allow_update` - enable dynamic updates
    /// * `enable_dnssec` - enable signing of the zone for DNSSec
    /// * `keys` - list of private and public keys used to sign a zone
    pub fn new(zone: String,
               zone_type: ZoneType,
               file: String,
               allow_update: Option<bool>,
               enable_dnssec: Option<bool>,
               keys: Vec<KeyConfig>)
               -> Self {
        ZoneConfig {
            zone: zone,
            zone_type: zone_type,
            file: file,
            allow_update: allow_update,
            enable_dnssec: enable_dnssec,
            keys: keys,
        }
    }

    // TODO this is a little ugly for the parse, b/c there is no terminal char
    /// retuns the name of the Zone, i.e. the `example.com` of `www.example.com.`
    pub fn get_zone(&self) -> ParseResult<Name> {
        Name::parse(&self.zone, Some(&Name::new()))
    }

    /// the type of the zone
    pub fn get_zone_type(&self) -> ZoneType {
        self.zone_type
    }

    /// path to the zone file, i.e. the base set of original records in the zone
    ///
    /// this is ony used on first load, if dynamic update is enabled for the zone, then the journal
    /// file is the actual source of truth for the zone.
    pub fn get_file(&self) -> PathBuf {
        PathBuf::from(&self.file)
    }

    /// enable dynamic updates for the zone (see SIG0 and the registered keys)
    pub fn is_update_allowed(&self) -> bool {
        self.allow_update.unwrap_or(false)
    }

    /// declare that this zone should be signed, see keys for configuration of the keys for signing
    pub fn is_dnssec_enabled(&self) -> bool {
        self.enable_dnssec.unwrap_or(false)
    }

    /// the configuration for the keys used for auth and/or dnssec zone signing.
    pub fn get_keys(&self) -> &[KeyConfig] {
        &self.keys
    }
}

/// Key pair configuration for DNSSec keys for signing a zone
#[derive(RustcDecodable, PartialEq, Debug)]
pub struct KeyConfig {
    key_path: String,
    password: Option<String>,
    algorithm: String,
    signer_name: Option<String>,
    is_zone_signing_key: Option<bool>,
    is_zone_update_auth: Option<bool>,
    create_if_absent: Option<bool>,
}

impl KeyConfig {
    /// Return a new KeyConfig
    ///
    /// # Arguments
    ///
    /// * `key_path` - file path to the key
    /// * `password` - password to use to read the key
    /// * `algorithm` - the type of key stored, see `Algorithm`
    /// * `signer_name` - the name to use when signing records, e.g. ns.example.com
    /// * `is_zone_signing_key` - specify that this key should be used for signing a zone
    /// * `is_zone_update_auth` - specifies that this key can be used for dynamic updates in the zone
    /// * `do_auto_generate` - if the key does not exist, generate a new one (it will need to be signed)
    pub fn new(key_path: String,
               password: Option<String>,
               algorithm: Algorithm,
               signer_name: String,
               is_zone_signing_key: bool,
               is_zone_update_auth: bool,
               do_auto_generate: bool)
               -> Self {
        KeyConfig {
            key_path: key_path,
            password: password,
            algorithm: algorithm.to_str().to_string(),
            signer_name: Some(signer_name),
            is_zone_signing_key: Some(is_zone_signing_key),
            is_zone_update_auth: Some(is_zone_update_auth),
            create_if_absent: Some(do_auto_generate),
        }
    }

    /// path to the key file, either relative to the zone file, or a explicit from the root.
    pub fn key_path(&self) -> &Path {
        Path::new(&self.key_path)
    }

    /// Converts key into
    pub fn format(&self) -> ParseResult<KeyFormat> {
        let extension = try!(self.key_path()
            .extension()
            .ok_or(ParseErrorKind::Msg(format!("file lacks extension, e.g. '.p12': {:?}",
                                               self.key_path())
                .into())));

        match extension.to_str() {
            Some("der") => Ok(KeyFormat::Der),
            Some("key") => Ok(KeyFormat::Pem), // TODO: deprecate this...
            Some("pem") => Ok(KeyFormat::Pem),
            Some("pk8") => Ok(KeyFormat::Pkcs8),
            e @ _ => {
                Err(ParseErrorKind::Msg(format!("extension not understood, '{:?}': {:?}",
                                                e,
                                                self.key_path()))
                            .into())
            }
        }
    }

    /// Returns the password used to read the key
    pub fn password(&self) -> Option<&str> {
        self.password.as_ref().map(|s| s.as_str())
    }

    /// algorithm for for the key, see `Algorithm` for supported algorithms.
    pub fn algorithm(&self) -> ParseResult<Algorithm> {
        Algorithm::from_str(&self.algorithm).map_err(|e| e.into())
    }

    /// the signer name for the key, this defaults to the $ORIGIN aka zone name.
    pub fn signer_name(&self) -> ParseResult<Option<Name>> {
        if let Some(ref signer_name) = self.signer_name.as_ref() {
            let name = try!(Name::parse(signer_name, None));
            return Ok(Some(name));
        }

        Ok(None)
    }

    /// specifies that this key should be used to sign the zone
    ///
    /// The public key for this must be trusted by a resolver to work. The key must have a private
    /// portion associated with it. It will be registered as a DNSKEY in the zone.
    pub fn is_zone_signing_key(&self) -> bool {
        self.is_zone_signing_key.unwrap_or(false)
    }

    /// this is at least a public_key, and can be used for SIG0 dynamic updates.
    ///
    /// it will be registered as a KEY record in the zone.
    pub fn is_zone_update_auth(&self) -> bool {
        self.is_zone_update_auth.unwrap_or(false)
    }

    /// auto generate/create the key if it doesn't already exist (the public portion can be
    /// retrieved by a DNS query to the zone for DNSKEY and KEY records).
    pub fn create_if_absent(&self) -> bool {
        self.create_if_absent.unwrap_or(false)
    }
}

/// Configuration for a TLS certificate
#[derive(RustcDecodable, PartialEq, Debug)]
pub struct TlsCertConfig {
    path: String,
    password: Option<String>,
    create_if_absent: Option<bool>,
    subject_name: String,
}

impl TlsCertConfig {
    /// path to the pkcs12 der formated certificate file
    pub fn get_path(&self) -> &Path {
        Path::new(&self.path)
    }
    /// optional password for open the pkcs12, none assumes no password
    pub fn get_password(&self) -> Option<&str> {
        self.password.as_ref().map(|s| s.as_str())
    }
    /// if it does not exist, one will be generated (with an EC key)
    pub fn create_if_absent(&self) -> bool {
        self.create_if_absent.unwrap_or(false)
    }
    /// the certificate's subject name, e.g. "ns.example.com"
    pub fn get_subject_name(&self) -> &str {
        &self.subject_name
    }
}