Integration between bind and dns is working

This commit is contained in:
notplants 2021-05-17 20:15:36 +02:00
parent ff87de1150
commit 03745f4fb2
16 changed files with 626 additions and 131 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/target
**/*.rs.bk
ns_tests/*.key

338
Cargo.lock generated
View File

@ -135,12 +135,48 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
"byte-tools",
]
[[package]]
name = "bstr"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
dependencies = [
"memchr",
]
[[package]]
name = "bumpalo"
version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.4.3"
@ -194,6 +230,16 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "chrono-tz"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2554a3155fec064362507487171dcc4edc3df60cb10f3a1fb10ed8094822b120"
dependencies = [
"chrono",
"parse-zoneinfo",
]
[[package]]
name = "clap"
version = "2.33.3"
@ -277,6 +323,17 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"lazy_static",
]
[[package]]
name = "data-encoding"
version = "2.3.2"
@ -295,6 +352,12 @@ dependencies = [
"syn 1.0.71",
]
[[package]]
name = "deunicode"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690"
[[package]]
name = "devise"
version = "0.3.0"
@ -325,6 +388,15 @@ dependencies = [
"syn 1.0.71",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
]
[[package]]
name = "discard"
version = "1.0.4"
@ -424,6 +496,12 @@ dependencies = [
"synstructure",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "figment"
version = "0.10.5"
@ -587,6 +665,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "getrandom"
version = "0.2.2"
@ -610,6 +697,30 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "globset"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags",
"ignore",
"walkdir",
]
[[package]]
name = "h2"
version = "0.3.3"
@ -687,6 +798,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05842d0d43232b23ccb7060ecb0f0626922c21f30012e97b767b30afd4a5d4b9"
[[package]]
name = "humansize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
[[package]]
name = "humantime"
version = "1.3.0"
@ -737,6 +854,24 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "ignore"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b287fb45c60bb826a0dc68ff08742b9d88a2fea13d6e0c286b3172065aaf878c"
dependencies = [
"crossbeam-utils 0.8.4",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "indexmap"
version = "1.6.2"
@ -862,6 +997,12 @@ dependencies = [
"serde_json",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
version = "0.1.8"
@ -1091,6 +1232,12 @@ version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "parking_lot"
version = "0.9.0"
@ -1142,6 +1289,15 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "parse-zoneinfo"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41"
dependencies = [
"regex",
]
[[package]]
name = "peach-dyndns-host"
version = "0.1.0"
@ -1156,6 +1312,7 @@ dependencies = [
"rocket_contrib",
"serde 1.0.125",
"structopt",
"tera",
"tokio",
"tokio-executor",
"tokio-tcp",
@ -1193,6 +1350,49 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
"pest",
"pest_meta",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.71",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
"maplit",
"pest",
"sha-1",
]
[[package]]
name = "pin-project"
version = "1.0.7"
@ -1573,6 +1773,15 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -1672,6 +1881,18 @@ dependencies = [
"yaml-rust",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"fake-simd",
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.0"
@ -1693,6 +1914,15 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "slug"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
dependencies = [
"deunicode",
]
[[package]]
name = "smallvec"
version = "0.6.14"
@ -1900,6 +2130,28 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "tera"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64b021b8d3ab1f59ceae9e6cd1c26c8e7ce0322a9ebfff6c0e22b3b66938935"
dependencies = [
"chrono",
"chrono-tz",
"globwalk",
"humansize",
"lazy_static",
"percent-encoding",
"pest",
"pest_derive",
"rand 0.8.3",
"regex",
"serde 1.0.125",
"serde_json",
"slug",
"unic-segment",
]
[[package]]
name = "termcolor"
version = "1.1.2"
@ -1938,6 +2190,15 @@ dependencies = [
"syn 1.0.71",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
@ -2038,7 +2299,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
dependencies = [
"crossbeam-utils",
"crossbeam-utils 0.7.2",
"futures 0.1.31",
]
@ -2070,7 +2331,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
dependencies = [
"crossbeam-utils",
"crossbeam-utils 0.7.2",
"futures 0.1.31",
"lazy_static",
"log",
@ -2256,6 +2517,12 @@ dependencies = [
"unchecked-index",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ubyte"
version = "0.10.1"
@ -2265,6 +2532,12 @@ dependencies = [
"serde 1.0.125",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uncased"
version = "0.9.6"
@ -2281,6 +2554,56 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23"
dependencies = [
"unic-ucd-segment",
]
[[package]]
name = "unic-ucd-segment"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicode-bidi"
version = "0.3.5"
@ -2353,6 +2676,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi 0.3.9",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"

View File

@ -21,6 +21,7 @@ rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "master" }
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", branch = "master" }
serde = "1.0.125"
dotenv = "0.15.0"
tera = "1"
[[bin]]
@ -29,7 +30,7 @@ path = "src/client.rs"
[[bin]]
name = "zone"
path = "src/utils.rs"
path = "src/generate_zone.rs"
[[bin]]
name = "main"

View File

@ -1,6 +1,7 @@
# peach-dyndns-host
a DNS server to host the names of guests with changing IP addresses
a dynamic DNS server to host the names of guests with changing IP addresses. provides an http API
for updating bind9 configurations.
_work in progress_
@ -15,7 +16,7 @@ cargo run -- -vvv # DEBUG log verbosity
in another terminal
```shell
dig @localhost -p 12323 test.dyn.peach.cloud
nslookup blue.dyn.peachcloud.org ns.peachcloud.org
```
or
@ -23,3 +24,8 @@ or
```shell
curl http://localhost:3000
```
## testing
contains bash scripts for testing and debugging dynamic dns server behavior using nslookup

17
bind_config/setup.md Normal file
View File

@ -0,0 +1,17 @@
The following goes into `/etc/sudoers.d/bindctl` to enable peach-dyndns to reload bind.
```
#
# Allow server to reload bind
#
# User alias for bind-ctl which can reload bind
User_Alias BIND_CTRL = peach-dynds
# Command alias for reboot and shutdown
Cmnd_Alias RELOADBIND = /bin/reloadbind
# Allow BIND_CTRL users to execute RELOADBIND command without password
BIND_CTRL ALL=(ALL) NOPASSWD: RELOADBIND
```

16
ns_tests/grayns.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
MYIP="1.1.1.55"
KEY=green.dyn.commoninternet.net.key
NS=ns.commoninternet.net
DOMAIN=gray.dyn.commoninternet.net.
ZONE=gray.dyn.commoninternet.net
nsupdate -k $KEY -v << EOF
server $NS
zone $ZONE
update delete $DOMAIN A
update add $DOMAIN 30 A $MYIP
send
EOF

16
ns_tests/greenns.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
MYIP="1.1.1.44"
KEY=green.dyn.commoninternet.net.key
NS=ns.commoninternet.net
DOMAIN=green.dyn.commoninternet.net.
ZONE=green.dyn.commoninternet.net
nsupdate -k $KEY -v << EOF
server $NS
zone $ZONE
update delete $DOMAIN A
update add $DOMAIN 30 A $MYIP
send
EOF

16
ns_tests/ns1.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
MYIP="1.1.1.11"
KEY=ddns.key
NS=ns.commoninternet.net
DOMAIN=orange.time.commoninternet.net.
ZONE=time.commoninternet.net
nsupdate -k $KEY -v << EOF
server $NS
zone $ZONE
update delete $DOMAIN A
update add $DOMAIN 30 A $MYIP
send
EOF

View File

@ -2,33 +2,31 @@ use futures::try_join;
use std::io;
use tokio::task;
use std::net::Ipv4Addr;
use std::str::FromStr;
use trust_dns_client::client::{Client, SyncClient};
use trust_dns_client::udp::UdpClientConnection;
use trust_dns_client::tcp::TcpClientConnection;
use trust_dns_client::op::DnsResponse;
use trust_dns_client::op::update_message;
use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordType, RecordSet};
use trust_dns_client::op::DnsResponse;
use trust_dns_client::rr::{DNSClass, Name, RData, Record, RecordSet, RecordType};
use trust_dns_client::tcp::TcpClientConnection;
use trust_dns_client::udp::UdpClientConnection;
use trust_dns_server::authority::{
AuthLookup, Authority, LookupError, MessageRequest, UpdateResult,
};
pub fn check_domain_available(domain: &str) -> bool {
pub fn check_domain_available_using_nslookup(domain: &str) -> bool {
let address = "167.99.136.83:53".parse().unwrap();
let conn = UdpClientConnection::new(address).unwrap();
let client = SyncClient::new(conn);
let name = Name::from_str(domain).unwrap();
info!("++ making query {:?}", domain);
println!("++ making query {:?}", domain);
let response: DnsResponse = client.query(&name, DNSClass::IN, RecordType::A).unwrap();
info!("++ received response");
println!("++ received response");
let answers: &[Record] = response.answers();
if answers.len() > 0 {
info!("found: {:?}", answers[0].rdata());
println!("found: {:?}", answers[0].rdata());
true
} else {
false
@ -40,7 +38,6 @@ fn update_test() {
let conn = UdpClientConnection::new(address).unwrap();
let client = SyncClient::new(conn);
// Specify the name, note the final '.' which specifies it's an FQDN
let name = Name::from_str("test.time.commoninternet.net").unwrap();
@ -48,13 +45,13 @@ fn update_test() {
let rrset: RecordSet = record.clone().into();
let zone_origin = Name::from_str("time.commoninternet.net").unwrap();
let response: DnsResponse = client.create(rrset, zone_origin).expect("failed to create record");
let response: DnsResponse = client
.create(rrset, zone_origin)
.expect("failed to create record");
println!("response: {:?}", response);
}
fn main() {
check_domain_available("test");
// update_test();
check_domain_available_using_nslookup("test");
// update_test();
}

171
src/generate_zone.rs Normal file
View File

@ -0,0 +1,171 @@
/* For each subdomain,
- generate a new ddns key (tsig-keygen -a hmac-md5 {{subdomain}}.dyn.commoninternet.net) and append it to /etc/bind/dyn.commoninternet.net.keys
- add a zone section to /etc/bind/named.conf.local, associating the key with the subdomain
- add a minimal zone file to /var/lib/bind/subdomain.dyn.commoninternet.net
- reload bind and return the secret key to the client
*/
use std::fs::File;
use std::fs::OpenOptions;
use std::io::Write;
use std::process::Command;
use std::string::FromUtf8Error;
use tera::{Tera, Context};
const BASE_DOMAIN: &str = "dyn.commoninternet.net";
#[derive(Debug)]
pub enum PeachDynError {
GenerateTsigIoError(std::io::Error),
GenerateTsigParseError(std::string::FromUtf8Error),
DomainAlreadyExistsError(String),
BindConfigurationError(String),
}
impl From<std::io::Error> for PeachDynError {
fn from(err: std::io::Error) -> PeachDynError {
PeachDynError::GenerateTsigIoError(err)
}
}
impl From<FromUtf8Error> for PeachDynError {
fn from(err: std::string::FromUtf8Error) -> PeachDynError {
PeachDynError::GenerateTsigParseError(err)
}
}
/// helper function to generate the text of a TSIG key file
pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
let output = Command::new("/usr/sbin/tsig-keygen")
.arg("-a")
.arg("hmac-md5")
.arg(full_domain)
.output()?;
let key_file_text = String::from_utf8(output.stdout)?;
Ok(key_file_text)
}
/// helper function which returns true if all three conditions are true
/// - no already extant tsig key for the given domain
/// - no zone file for the given domain in /var/lib/bind
/// - no zone section for the given domain in named.conf.local
pub fn check_domain_available(full_domain: &str) -> bool {
let status1 = Command::new("/bin/grep")
.arg(full_domain)
.arg("/etc/bind/named.conf.local")
.status().expect("error running grep on /etc/bind/named.conf.local");
let code1 = status1.code().expect("error getting code from grep");
let status2 = Command::new("/bin/grep")
.arg(full_domain)
.arg("/etc/bind/dyn.commoninternet.net.keys")
.status().expect("error running grep on /etc/bind/dyn.commoninternet.net.keys");
let code2 = status2.code().expect("error getting code from grep");
let condition3 = std::path::Path::new(&format!("/var/lib/bind/{}", full_domain)).exists();
// domain is only available if domain does not exist in either named.conf.local or dyn.commoninternet.netkeys
// and a file with that name is not found in /var/lib/bind/
let domain_available = (code1 == 1) & (code2 == 1) & (!condition3);
// return
domain_available
}
/// helper function which generates all necessary bind configuration to serve the given
/// subdomain using dynamic DNS authenticated via a new TSIG key which is unique to that subdomain
/// and thus only the possessor of that key can use nsupdate to modify the records
/// for that subodmain
pub fn generate_zone(subdomain: &str) -> Result<String, PeachDynError> {
// construct full_domain
let full_domain = format!("{}.{}", subdomain, BASE_DOMAIN);
println!("[generating zone for {}]", full_domain);
// first safety check if the domain is available
let is_available = check_domain_available(&full_domain);
if !is_available {
return Err(PeachDynError::DomainAlreadyExistsError(full_domain));
}
// generate string with text for TSIG key file
let key_file_text = generate_tsig_key(&full_domain).expect("failed to generate tsig key");
// append key_file_text to /etc/bind/dyn.commoninternet.net.keys
let key_file_path = "/etc/bind/dyn.commoninternet.net.keys";
let mut file = OpenOptions::new().append(true).open(key_file_path)
.expect(&format!("failed to open {}", key_file_path));
if let Err(e) = writeln!(file, "{}", key_file_text) {
eprintln!("Couldn't write to file: {}", e);
}
// append zone section to /etc/bind/named.conf.local
let bind_conf_path = "/etc/bind/named.conf.local";
let mut file = OpenOptions::new()
.append(true)
.open(bind_conf_path)
.expect(&format!("failed to open {}", bind_conf_path));
let zone_section_text = format!(
"\
zone \"{full_domain}\" {{
type master;
file \"/var/lib/bind/{full_domain}\";
update-policy {{
grant {full_domain} self {full_domain};
}};
}};
",
full_domain = full_domain
);
if let Err(e) = writeln!(file, "{}", zone_section_text) {
eprintln!("Couldn't write to file: {}", e);
}
// use tera to render the zone file
let tera = match Tera::new("templates/*.tera") {
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
let mut context = Context::new();
context.insert("full_domain", &full_domain);
let result = tera.render("zonefile.tera", &context).expect("error loading zonefile.tera");
// write new zone file to /var/lib/bind
let zone_file_path = format!("/var/lib/bind/{}", full_domain);
let mut file = File::create(&zone_file_path)
.expect(&format!("failed to create {}", zone_file_path));
if let Err(e) = writeln!(file, "{}", result) {
eprintln!("Couldn't write to file: {}", e)
};
// restart bind
// we use the /etc/sudoers.d/bindctl to allow peach-dyndns user to restart bind as sudo without entering a password
// using a binary at /bin/reloadbind which runs 'systemctl reload bind9'
let status = Command::new("sudo")
.arg("/bin/reloadbind")
.status().expect("error restarting bind9");
if !status.success() {
return Err(PeachDynError::BindConfigurationError("There was an error in the bind configuration".to_string()));
// TODO: for extra safety consider to revert bind configurations to whatever they were before
}
// return success
Ok(key_file_text)
}
// this main function is only used for the purpose of testing
fn main() {
let result = generate_zone("purple");
match result {
Ok(key_text) => {
println!("successfully created zone");
println!("{}", key_text);
}
Err(err) => {
println!("error creating zone {:?}", err);
}
}
}

View File

@ -6,10 +6,10 @@
* /update-domain (update the IP for the domain, passing the associated secret)
*
*/
use rocket_contrib::json::Json;
use crate::generate_zone::{check_domain_available, generate_zone};
use rocket_contrib::json::{Json, JsonValue};
use serde::Deserialize;
use std::thread;
use crate::client::check_domain_available;
#[get("/")]
pub fn index() -> &'static str {
@ -25,15 +25,19 @@ pub struct RegisterDomainPost {
pub async fn register_domain(data: Json<RegisterDomainPost>) -> &'static str {
info!("++ post request to register new domain: {:?}", data);
// TODO: first confirm domain is in the right format ("*.dyn.peachcloud.org")
let handle = thread::spawn(move || {
let domain_already_exists = check_domain_available(&data.domain);
domain_already_exists
});
let domain_already_exists = handle.join().unwrap();
if domain_already_exists {
"can't register domain already exists"
let is_domain_available = check_domain_available(&data.domain);
if !is_domain_available{
"can't register domain that already exists"
} else {
// TODO: use bash to generate a tsig key, update bind config, and then return the secret
"New domain registered"
let result = generate_zone(&data.domain);
match result {
Ok(key_file_text) => {
// TODO: figure out how to return key_file_text
"successfully created zone"
}
Err(err) => {
"there was an error registering the domain"
}
}
}
}
}

View File

@ -3,19 +3,19 @@
#[macro_use]
extern crate rocket;
use crate::http::{index, register_domain};
use futures::try_join;
use std::io;
use tokio::task;
use crate::http::{index, register_domain};
mod cli;
mod http;
mod client;
mod http;
mod generate_zone;
#[tokio::main]
async fn main() {
let rocket_result= rocket::build()
let rocket_result = rocket::build()
.mount("/", routes![index, register_domain])
.launch()
.await;
@ -23,5 +23,4 @@ async fn main() {
if let Err(err) = rocket_result {
error!("++ error launching rocket server: {:?}", err);
}
}

View File

@ -1,93 +0,0 @@
/* For each subdomain,
- generate a new ddns key (tsig-keygen -a hmac-md5 {{subdomain}}.dyn.commoninternet.net) and append it to /etc/bind/dyn.commoninternet.net.keys
- add a zone section to /etc/bind/named.conf.local, associating the key with the subdomain
- add a minimal zone file to /var/lib/bind/subdomain.dyn.commoninternet.net
- reload bind and return the secret key to the client
*/
use std::process::Command;
use std::io::Error;
use std::io::Write;
use std::string::FromUtf8Error;
use std::{fs::OpenOptions};
use std::fs::File;
const BASE_DOMAIN : &str = "dyn.commoninternet.net";
#[derive(Debug)]
pub enum PeachDynError {
GenerateTsigIoError(std::io::Error),
GenerateTsigParseError(std::string::FromUtf8Error),
}
impl From<std::io::Error> for PeachDynError {
fn from(err: std::io::Error) -> PeachDynError {
PeachDynError::GenerateTsigIoError(err)
}
}
impl From<FromUtf8Error> for PeachDynError {
fn from(err: std::string::FromUtf8Error) -> PeachDynError {
PeachDynError::GenerateTsigParseError(err)
}
}
/// helper function to generate a TSIG key file
pub fn generate_tsig_key(full_domain: &str) -> Result<String, PeachDynError> {
let output = Command::new("/usr/sbin/tsig-keygen")
.arg("-a")
.arg("hmac-md5")
.arg(full_domain)
.output()?;
let key_file_text = String::from_utf8(output.stdout)?;
Ok(key_file_text)
}
fn generate_zone(subdomain: &str) {
let full_domain=format!("{}.{}", subdomain, BASE_DOMAIN);
println!("[generating zone for {}]", subdomain);
// generate key_file_text
let key_file_text = generate_tsig_key(&full_domain).unwrap();
println!("key_file_text: {}", key_file_text);
// write key_file_text to file
let key_file_path = "/etc/bind/dyn.commoninternet.net.keys";
let mut file = OpenOptions::new()
.append(true)
.open(key_file_path).unwrap();
if let Err(e) = writeln!(file, "{}", key_file_text) {
eprintln!("Couldn't write to file: {}", e);
}
// append to named.local.conf
let bind_conf_path = "/etc/bind/named.local.conf";
let mut file = OpenOptions::new()
.append(true)
.open(bind_conf_path).unwrap();
let zone_section_text = format!("\
zone \"{full_domain}\" {{
type master;
file \"/var/lib/bind/{full_domain}\";
update-policy {{
grant {full_domain} self {full_domain};
}};
}};
", full_domain=full_domain);
if let Err(e) = writeln!(file, "{}", zone_section_text) {
eprintln!("Couldn't write to file: {}", e);
}
}
fn main() {
generate_zone("blue");
}

10
templates/zonefile.tera Normal file
View File

@ -0,0 +1,10 @@
$ORIGIN .
$TTL 30 ; 30 seconds
{{full_domain}} IN SOA ns.commoninternet.net. root.commoninternet.net. (
2016062804 ; serial
3600 ; refresh (1 hour)
600 ; retry (10 minutes)
2600 ; expire (43 minutes 20 seconds)
30 ; minimum (30 seconds)
)
NS ns.commoninternet.net.